写作目的

自学Java,Java中流的概念比较复杂,故专门作一整理。期望开始学习的童鞋,看完本文后对Java流有一个大致的认识。主要分三个小篇:

一、概述、输入/输出字节流

二、输入/输出字符流、装饰者模式与处理流

三、其他

本文首先大致介绍以下流的概念和分类,然后介绍字节流的接口,最后介绍一些三种常用的字节流,并举例说明它们的用法。

一、什么是流

首先,给一个图,给大家一个感性的认识。大家至少接触过面向对象语言,在面向对象语言中讲到输入输出(I/O)时,一般都是流。那什么是流呢?你能准确描述一下吗?其实我也不能准确描述描述,下面是我的理解。首先说说IO,IO就是将数据从一个地方搬运到另一个地方,如从一个文件运到内存中,从一台机器运到另一台机器。再来说说流,流就是隐藏这一搬运细节的一个对象(或者说将搬运细节封装起来了)。如上图所示,流对象就像桥一样,连接源数据和目标存放位置,我们程序员呢,什么都不用管,只要将源数据按照流对象规定的格式存到流对象中或从流对象读出来。

二、Java中的流

每学一门编程语言,I/O总是很头疼的一块,虽然我一直用C++写代码,但对C++的流我也没有搞懂。像控制结构、基本数据类型、运算类型等等一般都是相同的,但是流在不同的语言中会有很大差别。因为是自学,所以就把它弄清楚一些。JDK本身提供了丰富的流类型。我们首先用思维导图的方式来将这些类整理一下:

是不是很壮观,先大体上说下Java流的分类:

  • 按流向分,可分为输入流和输出流(输入/输出是针对CPU而言的);

  • 按处理数据的类型,可字节流和字符流(一般字节流就够用了,涉及到文本编码,尤其是非ASCII编码的会用到字符流)。在此,要强调的一点:磁盘上存储的所有数据都是面向字节的,字符只存在程序(或者说内存)中。

  • 按功能分,可分为节点流和处理流。节点流提供基本功能,处理流提供扩展功能,增加性能。

  所有这些类中,最重要的就是InputStream和OutputStream,接着是Reader和Writer。首先来详细介绍以下InputStream的接口。

2.1 InputStream类提供的接口

接口一、从流中读入一个字节的数据

  上面这个方法是InputStream唯一的抽象方法,子类必须实现这一方法,其功能是读入一个字节的数据。不同的输入流(有不同的数据源),读入的方式当然是不一样的,接下来要介绍的几个接口都依赖于这一接口。此接口返回值有两种可能:(1) 读到字节的值; (2) -1表示已经读到流的结束。

接口二、从流中读入若干个字节

  这个函数是从流中读入若干字节到缓存数组b[]中,返回值也有两种情况:

  (1) 实际读到字节数组b中的字节数(可能<b.length);

  (2) -1,已经到流的结尾啦,没有字节可读了!

  一般而言,上述两个函数是我们在使用输入流时,最常用的两个函数。另外,JDK还给我们提供了另一个更为灵活的读入若干字节的接口:

  这个接口与上一个接口的功能是类似的,只是增加了off和len两个参数,使用户可以指定读入字节的起始位置和长度。如果len + off > b.length,会抛出一个IndexOutofBoundsException异常。

2.2 OutputStream类提供的接口

  接下来,介绍输出流的接口。OutputStream的接口与InputStream的接口是一一对应的。你有read一个字节,那我就有write一个字节;你有read若干个字节,那我就有write若干个字节。接口如下(不做详细介绍了):

public abstract void write(int b) throws IOException;
public void write(byte b[]) throws IOException;
public void write(byte b[], int off, int len) throws IOException;

2.3 字节流的派生类及作用

2.4 流的使用

下面通过几个实例来看看第3节介绍的三种流的用法。

1. 文件输入流

 public static void main(String args[]) throws IOException {
// 定义文件输入流
FileInputStream fis = new FileInputStream("test.txt"); // 定义读入缓存字节
byte buf[] = new byte[1024]; // 使用read接口读入
fis.read(buf); // 输出读入的字节
System.out.println(new String(buf));
}

【注】这里读入的buf字节数组,将其按系统默认字符集编码转为String字符串,然后输出。若文件的字符集编码与系统默认字符集编码不一致,则需要在构造String指定编码,否则可能出现乱码。

2. 字节数组输入流

  前面已经指出,该流主要用于接收并解析网络数据,在单机上没啥意思。另外,需要配合处理流(如:DataInputStream)一起使用,在这里我们通过一个简单的例子来演示一下。

 public static void main(String args[]) throws IOException {
// 定义输入缓冲区,这部分内容通常由网络传输过来的。
String sBuf = new String("ByteArrayInputStream Test.");
byte buf[] = sBuf.getBytes(); // 定义输入字节数组流
ByteArrayInputStream bais = new ByteArrayInputStream(buf); // 读入其中的8个字节并输出
byte[] obuf = new byte[8];
bais.read(obuf);
System.out.println(new String(obuf)); // 再次强调:上述三行代码仅仅演示,一般字节数组流不单独使用。
}

3. 管道流

  配合使用,最好在线程中使用,便于线程的通信。为了演示,首先派生两个线程类,如下:

 //实现两个线程类
class ThreadRead extends Thread { private PipedInputStream mPis = null; public ThreadRead(PipedInputStream pis) {
mPis = pis;
} // 读取管道流内容并输出
public void run() {
byte[] buf = new byte[100];
try {
mPis.read(buf);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(new String(buf));
}
} class ThreadWrite extends Thread { PipedOutputStream mPos = null; public ThreadWrite(PipedOutputStream pos) {
mPos = pos;
} public void run() {
String s = new String("你好!PipedOutputStream.");
try { mPos.write(s.getBytes());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
主函数如下:
 public class PipedStreamDemo01 {

public static void main(String args[]) {
PipedOutputStream pos = new PipedOutputStream();
PipedInputStream pis = null;
try {
pis = new PipedInputStream(pos);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} Thread th1 = new ThreadRead(pis);
Thread th2 = new ThreadWrite(pos); th2.start();
th1.start();
}

三、小结

  到此,字节流大致介绍完毕。