举例讲解Java中的Stream流概念

1、基本的输入流和输出流
流是 Java 中最重要的基本概念之一。文件读写、网络收发、进程通信,几乎所有需要输入输出的地方,都要用到流。

流是做什么用的呢?就是做输入输出用的。为什么输入输出要用“流”这种方式呢?因为程序输入输出的基本单位是字节,输入就是获取一串字节,输出就是发送一串字节。但是很多情况下,程序不可能接收所有的字节之后再进行处理,而是接收一点处理一点。比方你下载魔兽世界,不可能全部下载到内存里再保存到硬盘上,而是下载一点就保存一点。这时,流这种方式就非常适合。

在 Java 中,每个流都是一个对象。流分为两种:输入流(InputStream)和输出流(OutputStream)。对于输入流,你只要从流当中不停地把字节取出来就是了;而对于输出流,你只要把准备好的字节串传给它就行。

流对象是怎么获得的呢?不同的外部系统,获取流的方式也不同。例如,文件读写就要创建 FileInputStream/FileOutputStream 对象,而网络通信是通过 Socket 对象来获取输入输出流的。一般来说,如果一个类有 getInputStream() 或 getOutputStream() 这样的方法,就表明它是通过流对象来进行输入输出的。
 
InputStream 是输入流,下面是一个通过 InputStream 读取文件的例子:

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.FileNotFoundException;
import java.util.Arrays; 

/**
 * 通过流读取文件
 */
public class ReadFileDemo { 

 // 程序入口
 public static void main(String[] args) {
  String path = "c:/boot.ini";
  File file = new File(path); 

  // 创建输入流
  InputStream is;
  try {
   is = new FileInputStream(file);
  } catch (FileNotFoundException e) {
   System.err.println("文件 " + path + " 不存在。");
   return;
  } 

  // 开始读取
  byte[] content = new byte[0];  // 保存读取出来的文件内容
  byte[] buffer = new byte[10240]; // 定义缓存 

  try {
   int eachTime = is.read(buffer); // 第一次读取。如果返回值为 -1 就表示没有内容可读了。
   while (eachTime != -1) {
    // 读取出来的内容放在 buffer 中,现在将其合并到 content。
    content = concatByteArrays(content, buffer, eachTime);
    eachTime = is.read(buffer); // 继续读取
   }
  } catch (IOException e) {
   System.err.println("读取文件内容失败。");
   e.printStackTrace();
  } finally {
   try {
    is.close();
   } catch (IOException e) {
    // 这里的异常可以忽略不处理
   }
  } 

  // 输出文件内容
  String contentStr = new String(content);
  System.out.println(contentStr);
 } 

 /**
  * 合并两个字节串
  *
  * @param bytes1  字节串1
  * @param bytes2  字节串2
  * @param sizeOfBytes2 需要从 bytes2 中取出的长度
  *
  * @return bytes1 和 bytes2 中的前 sizeOfBytes2 个字节合并后的结果
  */
 private static byte[] concatByteArrays(byte[] bytes1, byte[] bytes2, int sizeOfBytes2) {
  byte[] result = Arrays.copyOf(bytes1, (bytes1.length + sizeOfBytes2));
  System.arraycopy(bytes2, 0, result, bytes1.length, sizeOfBytes2);
  return result;
 }
}

虽然写得很啰嗦,但这确实是 InputStream 的基本用法。注意,这只是一个例子,说明如何从输入流中读取字节串。实际上,Java 提供更简单的方式来读取文本文件。以后将会介绍。

相比从流中读取,使用 OutputStream 输出则是非常简单的事情。下面是一个例子:

import java.io.OutputStream;
import java.io.FileOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.Date; 

/**
 * 将当前日期保存到文件
 */
public class SaveFileDemo { 

 public static void main(String[] args) throws IOException {
  String path = "c:/now.txt"; 

  File file = new File(path);
  if (!file.exists() && !file.createNewFile()) {
   System.err.println("无法创建文件。");
   return;
  } 

  OutputStream os = new FileOutputStream(file); // 创建输出流(前提是文件存在)
  os.write(new Date().toString().getBytes());  // 将当前时间写入文件
  os.close();          // 必须关闭流,内容才会写入文件。
  System.out.println("文件写入完成。");
 }
}

Java 还提供其它的流操作方式,但它们都是对 InputStream 和 OutputStream 进行扩展或包装。所以这两个类是基础,必须要理解它们的使用。

2、Reader 和 Writer
介绍了 InputStream 和 OutputStream,接下来介绍 Reader 和 Writer。这两个类其实就是将 InputStream 和 OutputStream 包装了一下。不过它们处理的不是字节(byte),而是字符(char)。如果一个流当中的内容都是文本,那么用 Reader/Writer 处理起来会简单些。下面是一个用 Reader 读取文本文件的例子:

import java.io.FileReader;
import java.io.IOException;
import java.io.Reader; 

/**
 * 读取文本文件
 */
public class ReadTextFileDemo { 

 // 程序入口
 public static void main(String[] args) {
  String path = "c:/boot.ini";
  String content = ""; 

  try {
   Reader reader = new FileReader(path);
   char[] buffer = new char[10240];
   int count; 

   while ((count = reader.read(buffer)) != -1) {
    content += new String(buffer, 0, count);
   }
  } catch (IOException e) {
   System.err.println("读取文件失败。");
   e.printStackTrace();
  } 

  System.out.println(content);
 } 

}

至于如何用 Writer 将文本内容写入文件,这里就不给出例子了,看官自己试着写一下吧。

上面这个例子,仍然不是读取文本文件最常用的方式。Java 提供 BufferedReader,我们通常用它来读取文本文件。下面是一个例子:

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException; 

/**
 * 用 BufferedReader 读取文本文件
 */
public class ReadTextDemo2 { 

 public static void main(String[] args) {
  String path = "c:/boot.ini";
  String content = ""; 

  try {
   BufferedReader reader = new BufferedReader(new FileReader(path));
   String line;
   while ((line = reader.readLine()) != null) {
    content += line + "/n";
   }
  } catch (IOException e) {
   System.err.println("读取文件失败。");
   e.printStackTrace();
  } 

  System.out.println(content);
 }
}

3、对象序列化
对象序列化也是流应用的一个重要方面。序列化就是把一个对象转换成一串字节,既可以保存起来,也可以传给另外的 Java 程序使用。ObjectOutputStream 和 ObjectInputStream 就是专门用来进行序列化和反序列化的。下面就是一个简单的例子:

import java.io.ObjectOutputStream;
import java.io.FileOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.io.ObjectInputStream;
import java.io.FileInputStream;
import java.io.EOFException; 

/**
 * ObjectOutputStream/ObjectInputStream 示例。
 * 这两个类分别用于序列化和反序列化。
 */
public class SerializationDemo { 

  public static void main(String[] args) throws Exception {
    String path = "c:/persons.data";
    File f = new File(path);
    if (!f.exists()) {
      f.createNewFile();
    } 

    writePersons(path);
    readPersons(path);
  } 

  // 从指定的文件中读取 Person 对象
  private static void readPersons(String path) throws IOException, ClassNotFoundException {
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path)); 

    Person p;
    while (true) {
      try {
        p = (Person) ois.readObject();
        System.out.println(p);
      } catch (EOFException e) {
        break;
      }
    }
  } 

  // 将 Person 对象保存到指定的文件中
  private static void writePersons(String path) throws IOException {
    Person[] persons = new Person[]{
        new Person("张三", 23),
        new Person("李四", 24)
    }; 

    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path));
    for (Person person : persons) {
      oos.writeObject(person);
    }
    oos.close();
  } 

  /////////////////////////////////////////////////////////// 

  private static class Person implements Serializable { 

    private String name; 

    private int age; 

    public Person() {
    } 

    public Person(String name, int age) {
      this.name = name;
      this.age = age;
    } 

    public String getName() {
      return name;
    } 

    public void setName(String name) {
      this.name = name;
    } 

    public int getAge() {
      return age;
    } 

    public void setAge(int age) {
      this.age = age;
    } 

    @Override
    public String toString() {
      return "Person{" +
          "name='" + name + '/'' +
          ", age=" + age +
          '}';
    }
  }
}

这个例子里面看不到字节和字符的读写,因为这两个类都包装好了。上面只是一个简单的例子,序列化要写好的话还是有不少讲究的。想深入了解序列化,可以看看这里。本文只关注跟流有关的部分。其实序列化用的很少,因为序列化降低了灵活性,所以可以不用的话一般都不会用。

(0)

相关推荐

  • java IO流文件的读写具体实例

    引言: 关于java IO流的操作是非常常见的,基本上每个项目都会用到,每次遇到都是去网上找一找就行了,屡试不爽.上次突然一个同事问了我java文件的读取,我一下子就懵了第一反应就是去网上找,虽然也能找到,但自己总感觉不是很踏实,所以今天就抽空看了看java IO流的一些操作,感觉还是很有收获的,顺便总结些资料,方便以后进一步的学习... IO流的分类:1.根据流的数据对象来分:高端流:所有的内存中的流都是高端流,比如:InputStreamReader  低端流:所有的外界设备中的流都是低端流

  • Java中重定向输出流实现用文件记录程序日志

    System中的out,error都是final类型的,不能做改动.但通过setOut()可以设置新的输出流,从而实现写日志的功能. import java.io.PrintStream; import java.io.FileNotFoundException; public class RedirectOutputStream { public static void main(String arg[]){ try{ PrintStream out = System.out; //保留原输出

  • Java编程中字节流与字符流IO操作示例

     IO流基本概念 IO流用来处理设备之间的数据传输 Java对数据的操作是通过流的方式 Java用于操作流的对象都是在IO包上 流按操作数据分为两种:字节流和字符流 流按流向分为:输入流,输出流. 字节流的抽象基类:InputStream,OutputStream 字符流的抽象基类:Reader,Writer 注:由这4个类派生出来的子类名称都是以其父类名作为子类名的后缀. 如:InputStream的子类:FileInputStream 如:Reader的子类FileReader 如创建一个F

  • java实现输入输出流代码分享

    1,编写一个程序,读取文件test.txt的内容并在控制台输出.如果源文件不存在,则显示相应的错误信息. package src; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; public class Test { public static void main(String[] args) { File f =

  • Java基于IO流读取文件的方法

    本文实例讲述了Java基于IO流读取文件的方法.分享给大家供大家参考,具体如下: public static void readFile(){ String pathString = TEST.class.getResource("/simu").getFile(); try { pathString = URLDecoder.decode(pathString, "utf-8"); } catch (UnsupportedEncodingException e1)

  • 详细解读Java编程中面向字符的输入流

    字符流是针对字符数据的特点进行过优化的,因而提供一些面向字符的有用特性,字符流的源或目标通常是文本文件. Reader和Writer是java.io包中所有字符流的父类.由于它们都是抽象类,所以应使用它们的子类来创建实体对象,利用对象来处理相关的读写操作.Reader和Writer的子类又可以分为两大类:一类用来从数据源读入数据或往目的地写出数据(称为节点流),另一类对数据执行某种处理(称为处理流). 面向字符的输入流类都是Reader的子类,其类层次结构如图所示. 下表列出了 Reader 的

  • Java8中使用流方式查询数据库的方法

    由于关系型数据库操作语言和面向对象语言之间的差异,如今我们仍然需要花费许多时间建立数据库与 Java 应用之间互相沟通的桥梁.通常,我们可以编写自己的映射层(mapping layer),或者使用第三方的 ORM(Object Relational Mapper)对象关系映射框架,比如 Hibernate.ORM 框架虽然使用起来很方便,但是如何正确地配置和提高框架操作数据库的性能却不太容易,ORM 框架往往会使我们的应用性能下降. 最近,我贡献了一个新的开源项目--Speedment,它能使我

  • 详解Java编程中面向字符的输出流

    面向字符的输出流都是类 Writer 的子类,其类层次结构如图所示. 下表列出了 Writer 的主要子类及说明. 使用 FileWriter 类写入文件 FileWriter 类是 Writer 子类 OutputStreamWriter 类的子类,因此 FileWriter 类既可以使用 Writer类的方法也可以使用 OutputStreamWriter 类的方法来创建对象. 在使用 FileWriter 类写入文件时,必须先调用 FileWriter()构造方法创建 FileWriter

  • 简单总结Java IO中stream流的使用方法

    Java语言的输入输出功能是十分强大而灵活的,对于数据的输入和输出操作以"流"(stream)的方式进行.J2SDK提供了各种各样的"流"类,用以获取不同种类的数据,定义在包java.io中.程序中通过标准的方法输入或输出数据. Java中的流可以从不同的角度进行分类: 按照流的方向不同:分为输入流和输出流. 按照处理数据单位的不同:分为字节流(8位)和字符流(16位). 按照功能不同:分为节点流和处理流. 节点流:是可以从一个特定的数据源(节点)读写数据的流(例如

  • 深入解析Java编程中面向字节流的一些应用

    文件输入输出流 文件输入输出流 FileInputStream 和 FileOutputStream 负责完成对本地磁盘文件的顺序输入输出操作. [例]通过程序创建一个文件,从键盘输入字符,当遇到字符"#"时结束,在屏幕上显示该文件的所有内容 import java.io.*; class ep10_5{ public static void main(String args[]){ char ch; int data; try{ FileInputStream a=new FileI

随机推荐