举例讲解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 +
          '}';
    }
  }
}

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

时间: 2015-09-11

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

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

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

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

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编程中面向字符的输出流

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

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

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

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

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

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

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

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

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中输入输出流实例详解

java语言的输入输出功能是十分强大而灵活的,美中不足的是看上去输入输出的代码并不是很简洁,因为你往往需要包装许多不同的对象.在Java类库中,IO部分的内容是很庞大的,因为它涉及的领域很广泛:标准输入输出,文件的操作,网络上的数据流,字符串流,对象流,zip文件流....本文的目的是为大家介绍JAVA中输入输出流实例详解. 流的层次结构 定义:        java将读取数据对象成为输入流,能向其写入的对象叫输出流.结构图如下: 1.输入输出: 输入/输出(Input/Output)是指对某

java中使用双向链表实现贪吃蛇程序源码分享

使用双向链表实现贪吃蛇程序 1.链表节点定义: package snake; public class SnakeNode { private int x; private int y; private SnakeNode next; private SnakeNode ahead; public SnakeNode() { } public SnakeNode(int x, int y) { super(); this.x = x; this.y = y; } public int getX(

Powershell使用WINDOWS事件日志记录程序日志

通常,人们使用基于文件的日志.这样做没有什么问题,但是使用WINDOWS提供系统内部日志会更加简单. 如果你有管理权限,你可以随时创建一个新的日志: 复制代码 代码如下: New-EventLog -LogName myLog -Source JobDue, JobDone, Remark 该命令创造了一个名为Mylog的日志,这个事件源自"JobDUE","JobDone"和"Remark".管理员权限只是为了创造日志,剩下的操作其它用户都可以

Java中Map集合(接口)的基本方法程序演示

本文实例为大家分享了Java中Map集合的基本方法程序演示的具体代码,供大家参考,具体内容如下 package pack02; import java.util.*; public class MapDemo { public static void main(String[] args) { //定义一个Map接口类型的引用,指向HashMap类型的对象 Map<String,String> ma = new HashMap<String, String>(); ma.put(&

Java中的程序计数器是什么

程序计数器是当前线程正在执行的字节码的地址.程序计数器是线程隔离的,每一个线程在工作的时候都有一个独立的计数器. JAVA虚拟机管理的内存区域图 1.什么是程序计数器? 程序计数器是当前线程正在执行的字节码的地址.程序计数器是线程隔离的,每一个线程在工作的时候都有一个独立的计数器. 2.字节码的执行原理 编译后的字节码在没有经过JIT(实时编译器)编译前,是通过字节码解释器进行解释执行.其执行原理为:字节码解释器读取内存中的字节码,按照顺序读取字节码指令,读取一个指令就将其翻译成固定的操作,根据

带你了解Java中的异常处理(上)

当当当当当当,各位看官,好久不见,甚是想念. 今天我们来聊聊Java里的一个小妖精,那就是异常. 什么是异常?什么是异常处理? 异常嘛,顾名思义就是不正常,(逃),是Java程序运行时,发生的预料之外的事情,它阻止了程序按照程序员的预期正常执行. 异常处理,应该说异常处理机制,就是专门用来制服这个小妖精的法宝.Java中的异常处理机制能让程序在异常发生时,按照代码的预先设定的异常处理逻辑,针对性地处理异常,让程序尽最大可能恢复正常并继续执行,且保持代码的清晰. 简而言之,Java异常处理就是能让

SQL Server误区30日谈 第19天 Truncate表的操作不会被记录到日志

误区 #19:Truncate表的操作不会被记录到日志 错误 在用户表中的操作都会被记录到日志.在SQL Server中唯一不会被记录到日志的操作是TempDB中的行版本控制. Truncate Table语句会将整个表中的所有数据删除.但删除的方式并不是一行一行的删除,而是将组成表的数据页释放,将组成表的相关页释放的操作交给一个后台的线程进行队列处理的过程被称为deferred-drop.使用后台线程处理deferred-drop的好处是这个操作不会使得其所在的事务需要执行很长时间,因此也就不

举例讲解Java中Piped管道输入输出流的线程通信控制

PipedOutputStream和PipedInputStream 在java中,PipedOutputStream和PipedInputStream分别是管道输出流和管道输入流. 它们的作用是让多线程可以通过管道进行线程间的通讯.在使用管道通信时,必须将PipedOutputStream和PipedInputStream配套使用. 使用管道通信时,大致的流程是:我们在线程A中向PipedOutputStream中写入数据,这些数据会自动的发送到与PipedOutputStream对应的Pip

基于Protobuf动态解析在Java中的应用 包含例子程序

最近在做ProtoBuf相关的项目,其中用到了动态解析,网上看了下相关资料和博文都比较少,自己来写一个记录一下学习过程. Protocol Buffers是结构化数据格式标准,提供序列化和反序列方法,用于存储和交换.语言中立,平台无关.可扩展.目前官方提供了C++.Java.Python API,也有其他语言的开源api(比如php).可通过 .proto文件生成对应语言的类代码 如果已知protobuf内容对应的是哪个类对象,则可以直接使用反序列化方法搞定(Xxx.parseFrom(inpu