本篇我们介绍Java的IO流。
IO流
流是一种抽象概念,它代表了数据的无结构化传递。按照流的方式进行输入输出,数据被当成无结构的字节序或字符序列。从流中取得数据的操作称为提取操作,而向流中添加数据的操作称为插入操作。用来进行输入输出操作的流就称为IO流。换句话说,IO流就是以流的方式进行输入输出。 --百度百科
Java中的IO流(都在java.io包中)可以分为字节流和字符流,字节流又分为输入流InputStream及其子类和输出流OutputStream及其子类,字符流分为输入流Reader及其子类和输出流Writer及其子类。
输入流只能读取数据,即定义中的从流中取得数据的操作;输出流只能写入数据,即定义中的向流中添加数据。输入输出流的功能与我们所认为的输入输出(我们认为输入=写入,输出=读取)正好是相反的,因为我们是站在硬盘的角度来看,而输入输出流是站在内存的角度来看的。
上面提到的四个类及其子类都分别使用了装饰者模式,这也是导致java.io包中有很多类的原因之一。下面我会分别介绍这四个类及其子类以及在装饰者模式中所扮演的角色。如果对装饰者模式不了解可以看这篇文章。
字节流
字节流是最基本的,处理单元为1个字节,适用于各种类型的数据。字节流分为输入流InputStream及其子类和输出流OutputStream及其子类。
输入流
输入流类图(没写方法,因为太多了):
- 抽象构件角色:InputStream类;
- 具体构件角色:除ObjectInputStream以及FilterInputStream外的所有InputStream的直接子类;
- 装饰角色:FilterInputStream类;
- 具体装饰角色:FilterInputStream的子类以及ObjectInputStream类。
下面只介绍常用的输入流。
InputStream
InputStream是所有字节输入流的基类,读到末尾返回-1。
//从输入流中读取下一个字节
public abstract int read() throws IOException;
//从输入流中读取多个字节并将其存储在byte数组中,返回值为读取的字节数量
public int read(byte b[]) throws IOException;
//从输入流中读取len个字节并存储在下标为off开始的byte数组中,返回值为读取的字节数量
public int read(byte b[], int off, int len);
//跳过并丢弃输入流中的n个字节的数据,返回值实际跳过的字节数
public long skip(long n) throws IOException;
//返回输入流下一个方法调用可以不受阻塞地从此输入流读取(或跳过)的估计字节数
public int available() throws IOException;
//关闭此输入流并释放与流相关联的任何系统资源
public void close() throws IOException;
//标记此输入流中的当前位置
public synchronized void mark(int readlimit);
//将此流重新定位到上次在此输入流上调用 mark方法时的位置
public synchronized void reset() throws IOException;
//此输入流是否支持 mark和 reset方法,支持返回true,否则返回false
public boolean markSupported();
ByteArrayInputStream
ByteArrayInputStream包含一个内部缓冲区,其中包含可以从流中读取的字节。 内部计数器跟踪read
方法要提供的下一个字节。关闭ByteArrayInputStream没有任何效果。 在关闭流之后,可以调用此类中的方法,而不生成IOException 。
ByteArrayInputStream的方法都是从InputStream中继承而来的。
成员变量和构造函数:
//由数据流的创建者提供的字节数组
protected byte buf[];
//从缓冲区中读取的下一个字符的索引
protected int pos;
//标记位置
protected int mark = 0;
//索引一大于输入流缓冲区中的最后一个有效字符
protected int count;
//使用传入的byte数组作为缓冲区数组
public ByteArrayInputStream(byte buf[]) {
this.buf = buf;
this.pos = 0;
this.count = buf.length;
}
public ByteArrayInputStream(byte buf[], int offset, int length) {
this.buf = buf;
this.pos = offset;
this.count = Math.min(offset + length, buf.length);
this.mark = offset;
}
测试程序及输出结果:
//测试程序
public static void main(String[] args) {
byte[] byteArr = {'a','b','c','d','e','f'};
InputStream byteArrayInputStream = new ByteArrayInputStream(byteArr);
System.out.println("ByteArrayInputStream流是否支持标记:"+byteArrayInputStream.markSupported());
try {
System.out.println("输入流下一个字节为:"+byteArrayInputStream.read());
System.out.println("剩余可读字节数:"+byteArrayInputStream.available());
System.out.println("********标记当前位置********");
byteArrayInputStream.mark(2);
byte[] bytes = new byte[7];
System.out.println("将3个字节数据读入bytes数组中:"+byteArrayInputStream.read(bytes,0,3));
System.out.println("剩余可读字节数:"+byteArrayInputStream.available());
System.out.println("实际跳过的字节数:"+byteArrayInputStream.skip(1));
System.out.println("剩余可读字节数:"+byteArrayInputStream.available());
System.out.println("********将缓冲区重置为标记位置********");
byteArrayInputStream.reset();
System.out.println("剩余可读字节数:"+byteArrayInputStream.available());
} catch (IOException e) {
e.printStackTrace();
}
}
//输出结果
ByteArrayInputStream流是否支持标记:true
输入流下一个字节为:97
剩余可读字节数:5
********标记当前位置********
将3个字节数据读入bytes数组中:3
剩余可读字节数:2
实际跳过的字节数:1
剩余可读字节数:1
********将缓冲区重置为标记位置********
剩余可读字节数:5
FileInputStream
FileInputStream
从文件系统中的文件获取输入字节。 什么文件可用取决于主机环境。用于读取诸如图像数据的原始字节流。 要阅读字符串,请考虑使用FileReader
。
成员变量和构造函数:
//该输入流连接的文件路径
private final String path;
//通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名
public FileInputStream(String name) throws FileNotFoundException;
//通过打开与实际文件的连接创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名
public FileInputStream(File file) throws FileNotFoundException;
//创建 FileInputStream通过使用文件描述符 fdObj ,其表示在文件系统中的现有连接到一个实际的文件
public FileInputStream(FileDescriptor fdObj);
测试文件内容为"abcdefg",路径为“D:\FileInputStream\hello.txt”。
测试程序及输出结果:
//测试程序
public static void main(String[] args) {
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream("D:\\FileInputStream\\hello.txt");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
System.out.println("FileInputStream流是否支持标记:"+fileInputStream.markSupported());
try {
System.out.println("输入流下一个字节为:"+fileInputStream.read());
System.out.println("剩余可读字节数:"+fileInputStream.available());
System.out.println("********标记当前位置********");
fileInputStream.mark(2);
byte[] bytes = new byte[7];
System.out.println("将3个字节数据读入bytes数组中:"+fileInputStream.read(bytes,0,3));
System.out.println("剩余可读字节数:"+fileInputStream.available());
System.out.println("实际跳过的字节数:"+fileInputStream.skip(1));
System.out.println("剩余可读字节数:"+fileInputStream.available());
if (fileInputStream.markSupported()){
System.out.println("********将缓冲区重置为标记位置********");
fileInputStream.reset();
System.out.println("剩余可读字节数:"+fileInputStream.available());
}
fileInputStream.skip(2);
System.out.println("读到末尾返回:"+fileInputStream.read());
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//输出结果
FileInputStream流是否支持标记:false
输入流下一个字节为:97
剩余可读字节数:6
********标记当前位置********
将3个字节数据读入bytes数组中:3
剩余可读字节数:3
实际跳过的字节数:1
剩余可读字节数:2
读到末尾返回:-1
ObjectInputStream
将对象序列化,这个类在这篇文章里与反序列化一起介绍,在此不再赘述。
PipedInputStream
PipedInputStream因为要与PipedOutputStream配套使用,所以在这篇文章中一起介绍。
FilterInputStream
FilterInputStream作为装饰者模式中的装饰角色,保存一个抽象构件角色即InputStream的引用。
protected volatile InputStream in;
//构造方法需要传入一个具体构件角色的引用
protected FilterInputStream(InputStream in) {
this.in = in;
}
装饰者角色只是对具体装饰者的抽象,因此我们重点介绍具体装饰者角色FilterInputStream的子类。
BufferedInputStream
BufferedInputStream提供了一个byte类型的数组作为缓冲区,使用BufferedInputStream装饰InputStream后,一次读取n个(构造函数可传入byte数组长度,默认8192)字节到缓冲区,程序读取数据超过缓冲区大小时,缓冲区从流中读取下一段数据。使用缓冲区减少了磁盘IO次数,也就提高了读取效率。
成员变量和构造函数:
//作为缓冲区的byte数组
protected volatile byte buf[];
//传入被装饰的输入流
public BufferedInputStream(InputStream in);
//传入被装饰的输入流和缓冲区的大小
public BufferedInputStream(InputStream in, int size);
测试程序及输出结果:
//测试程序
public static void main(String[] args) {
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream("D:\\FileInputStream\\hello.txt");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
FilterInputStream inputStream = new BufferedInputStream(fileInputStream,8);
try {
System.out.println("输入流下一个字节为:"+inputStream.read());
System.out.println("剩余可读字节数:"+inputStream.available());
System.out.println("是否支持标记:"+inputStream.markSupported());
} catch (IOException e) {
e.printStackTrace();
}
}
//输出结果
输入流下一个字节为:97
剩余可读字节数:6
是否支持标记:true
与原本FileInputStream使用的区别就只是使用BufferedInputStream类装饰了一下,装饰后的FileInputStream就有了缓冲区的功能(当然看不大出来)。
其他装饰类用到再补充吧。
输出流
输出流类图:
- 抽象构件角色:OutputStream类;
- 具体构件角色:除ObjectOutputStream以及FilterOutputStream外OutputStream的所有直接子类;
- 装饰角色:FilterOutputStream类;
- 具体装饰角色:FilterOutputStream的所有子类以及ObjectOutputStream类。
下面只介绍常用的输出流。
OutputStream
OutputStream是所有字节输出流的子类。
//将指定字节写入输出流
public abstract void write(int b) throws IOException;
//将指定字节数组写入输出流
public void write(byte b[]) throws IOException;
//将byte数组的下标从off开始的len个字节写入输出流
public void write(byte b[], int off, int len) throws IOException;
//刷新此输出流并强制写出所有缓冲的输出字节
public void flush() throws IOException;
//关闭此输出流并释放与此流相关联的任何系统资源
public void close() throws IOException;
ByteArrayOutputStream
该类实现了将数据写入字节数组的输出流。 当数据写入缓冲区时,缓冲区会自动增长。 数据可以使用toByteArray()
和toString()
。关闭ByteArrayOutputStream没有任何效果。 该流中的方法可以在流关闭后调用,而不生成IOException 。
成员变量及构造函数:
//用作缓冲区的byte数组
protected byte buf[];
//缓冲区中有效字节的数量
protected int count;
//size为缓冲区大小
public ByteArrayOutputStream(int size);
//不传参缓冲区大小默认为32个字节
public ByteArrayOutputStream();
特有成员方法:
//将此字节数组输出流输出的内容写入指定的输出流参数中
public synchronized void writeTo(OutputStream out) throws IOException;
//设置缓存区中有效字节数量为0
public synchronized void reset();
//返回一个缓存区的byte数组的复制
public synchronized byte toByteArray()[];
//获取缓存区有效字节大小
public synchronized int size();
//将缓存区转换为String
public synchronized String toString();
测试程序及输出结果:
//测试程序
public static void main(String[] args) {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ByteArrayOutputStream anotherByteArrayOutputStream = new ByteArrayOutputStream();
byte[] byteArr = {'a','b','c','d','e','f'};
try {
byteArrayOutputStream.write(byteArr);
System.out.println("缓存区中有效字节大小:"+byteArrayOutputStream.size());
System.out.println("缓存区中的内容:"+byteArrayOutputStream.toString());
System.out.println("缓存区复制的byte数组内容:"+new String(byteArrayOutputStream.toByteArray()));
byteArrayOutputStream.write(97);
System.out.println("写入一个字节后缓存区中有效字节大小:"+byteArrayOutputStream.size());
System.out.println("********将此输出流缓冲区内容写入到另一个输出流********");
byteArrayOutputStream.writeTo(anotherByteArrayOutputStream);
System.out.println("另一个输出流的缓冲区内容:"+anotherByteArrayOutputStream.toString());
System.out.println("********重置缓冲区有效字节数量********");
byteArrayOutputStream.reset();
System.out.println("重置后缓存区中有效字节大小:"+byteArrayOutputStream.size());
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
anotherByteArrayOutputStream.close();
byteArrayOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//输出结果
缓存区中有效字节大小:6
缓存区中的内容:abcdef
缓存区复制的byte数组内容:abcdef
写入一个字节后缓存区中有效字节大小:7
********将此输出流缓冲区内容写入到另一个输出流********
另一个输出流的缓冲区内容:abcdefa
********重置缓冲区有效字节数量********
重置后缓存区中有效字节大小:0
FileOutputStream
文件输出流是用于将数据写入到输出流File
或一个FileDescriptor
。 文件是否可用或可能被创建取决于底层平台。 特别是某些平台允许一次只能打开一个文件来写入一个FileOutputStream (或其他文件写入对象)。 在这种情况下,如果所涉及的文件已经打开,则此类中的构造函数将失败。
成员变量及构造函数:
//true则将输出流的数据追加在末尾,否则写入到开头
private final boolean append;
//该输出流连接的文件的路径
private final String path;
//创建文件输出流以指定的名称写入文件
public FileOutputStream(String name) throws FileNotFoundException;
//创建文件输出流以指定的名称写入文件,如果第二个参数是true ,则字节将写入文件的末尾而不是开头
public FileOutputStream(String name, boolean append)throws FileNotFoundException;
//创建文件输出流以写入由指定的File对象表示的文件
public FileOutputStream(File file) throws FileNotFoundException;
//创建文件输出流以写入由指定的File对象表示的文件,如果第二个参数是true ,则字节将被写入文件的末尾而不是开头
public FileOutputStream(File file, boolean append)throws FileNotFoundException;
特有方法:
//清理与文件的连接,并确保当没有更多的引用此流时,将调用此文件输出流的 close方法
protected void finalize() throws IOException;
测试程序及输出结果:
//测试程序
public static void main(String[] args) {
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream("D:\\FileInputStream\\hello.txt",false);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
byte[] byteArr = {'a','b','c','d','e','f'};
try {
fileOutputStream.write(byteArr);
System.out.println("写入完成!");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//输出结果
写入完成!
确实写入进去了,这里就不上传text的图片了。
ObjectOutputStream
将对象反序列化,这个类在这篇文章里与序列化一起介绍,在此不再赘述。
PipedOutputStream
PipedOutputStream因为要与PipedInputStream配套使用,所以在这篇文章中一起介绍。
FilterOutputStream
FilterOutputStream与FilterInputStream一样,作为装饰者角色,保存抽象构件角色即OutputStream的引用。
//被装饰输出流的引用
protected OutputStream out;
//传入被装饰输出流的引用
public FilterOutputStream(OutputStream out);
同样重点介绍FilterOutputStream的子类即具体装饰者类。
BufferedOutputStream
BufferedOutputStream提供了一个byte类型的数组作为缓冲区,使用BufferedOutputStream装饰OutputStream后,缓冲区一次写入n个(构造函数可传入byte数组长度,默认8192)字节到磁盘。使用缓冲区减少了磁盘IO次数,也就提高了写入效率。
成员变量及构造函数:
//用作缓冲区的byte数组
protected byte buf[];
//缓冲区中有效字节数量
protected int count;
//传入被装饰输出流引用,缓冲区大小默认为8192个字节
public BufferedOutputStream(OutputStream out);
//传入被装饰输出流引用和缓冲区大小
public BufferedOutputStream(OutputStream out, int size);
这里BufferedOutputStream与BufferedInputStream的用法类似,因此就不写测试程序了。
其他的装饰者类用到再补充。
难顶,本来想一晚上写完字节流和字符流,结果两个晚上堪堪写完字节流,继续加油吧。。。