本文包含以下知识点:
- 字节流OutputStream和InputStream类及其子类
- 字符流Writer和Reader类及其子类
- 转换流OutputStreamWriter和InputStreamReader
- 代码示例
- 字节流、字符流的比较
- 各类的使用场景
流
在程序中所有的数据都是以流的方式进行传输和保存的。程序需要数据的时候要使用输入流读取数据,而当程序需要将一些数据保存起来的时候,就要使用输出流完成。
在java.io包中操作文件内容的主要有两大类:字节流、字符流。两类都分为输入和输出操作。输入流基本方法是读,输出流基本方法是写。
输入流输出流是相对于内存而言的,程序运行在内存中。
从外存读取数据到内存以及将数据从内存写到外存中:
输入:读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中,输入流。
输出:将数据从程序 (内存) 中写到磁盘、光盘等存储设备中,输出流。
大致操作流程:(以File为例)
- 使用File类打开一个文件;
- 通过字节流或字符流的子类,指定输出的位置;
- 进行读/写操作;
- 关闭输入/输出。
java中的流是对字节序列的抽象,流中保存的实际上全都是字节文件。
字节流
字节流处理的基本单元为1个字节(byte),操作字节和字节数组,通常用来处理二进制数据。
面向字节流的OutputStream和InputStream是一切的基础。均为抽象类,实际使用的是它们的一系列子类。
字节输出流OutputStream
类与方法定义
抽象类,定义如下:
1 | public abstract class OutputStream |
方法有:
1 | 1.将一个字节数据写入数据流 |
1 | 2.将一个byte数组写入数据流 |
1 | 3.将指定byte数组中从偏移量off开始的len个字节写入数据流 |
1 | 4.刷新缓冲区 |
要想使用此类及以上方法,必须通过子类实例化对象。
继承OutputStream的子类
FileOutputStream
文件输出流,是向File或FileDescriptor输出数据的一个输出流。
FileOutputStream类的构造方法有:
1.创建一个文件输出流,向指定的File对象输出数据(写入)
1 | public FileOutputStream(File file) throws FileNotFoundException |
2.创建一个文件输出流,向指定名称的文件输出数据(写入到指定名称的文件中)
1 | public FileOutputStream(String name) throws FileNotFoundException |
3.创建一个文件输出流,向指定的文件描述器输出数据,该文件描述符表示文件系统中一个实际文件的现有连接。
1 | public FileOutputStream(FileDescriptor fdObj) |
OutputStream的其他子类
1.ObjectOutputStream(对象输出流)
对象输出流将Java对象的原始数据类型和对象图写入输出流。只有支持可序列化接口的对象才可以写入到输出流。
2.ByteArrayOutputStream(字节数组输出流)
该类实现了一个以字节数组形式写入数据的输出流,缓冲区会随着数据的写入而自动扩大。可以用toByteArray()和toString()检索数据。
构造方法有:
(1)创建一个新的字节数组输出流
1 | public ByteArrayOutputStream() { |
(2)创建一个新的字节数组输出流,并带有制定大小字节的缓冲区容量
1 | public ByteArrayOutputStream(int size) |
3.PipedOutputStream(管道输出流)
管道输出流可以连接到管道输入流来创建通信管道,它是这个管道的发送端。
一个线程通过管道输出流写入(发送)数据,另一个线程通过管道输入流读取数据,这样可实现两个线程之间的通讯。
不建议尝试从单个线程使用这两个管道流对象,因为它可能会造成线程死锁。
构造方法有:
(1)创建连接到指定管道输入流的管道输出流
1 | public PipedOutputStream(PipedInputStream snk) throws IOException |
(2)创建一个管道输出流,该输出流尚未连接到管道输入流
1 | public PipedOutputStream() |
4.FilterOutputStream
其子类有DataOutputStream、BufferedOutputStream、PrintStream。
代码示例:将序列化对象写入文件
创建文件,输出字节的代码示例:
1 | public class Test { |
将序列化对象写入文件,会用到字节输出流:
1 | /** |
如下图所示
字节输入流InputStream
类与方法定义
抽象类,定义如下:
1 | public abstract class InputStream implements Closeable |
方法有:
1 | 1.读取一个字节无符号填充到int的低8位,高8位补零,读取到-1结束 |
1 | 2.将当前输入流中b.length个字节读入到byte数组中,实际读取的字节数作为整数返回 |
1 | 3.将输入流中len个字节数据读入一个字节数组中,从数组的off位置开始存放len长度的数据 |
1 | 4.从该输入流中跳过或忽略n字节 |
要想使用此类及以上方法,必须通过子类实例化对象。
继承InputStream的子类
FileInputStream
文件输入流,从文件系统中的文件读取输入字节。
FileInputStream的构造方法有:
1.创建一个输入文件流,从指定的File对象读取数据
1 | public FileInputStream(File file) throws FileNotFoundException |
2.创建一个输入文件流,从指定名称的文件读取数据
1 | public FileInputStream(String name) throws FileNotFoundException |
3.创建一个输入文件流,从指定的文件描述器读取数据
1 | public FileInputStream(FileDescriptor fdObj) |
InputStream的其他子类
1.ObjectInputStream(对象输入流)
2.ByteArrayInputStream(字节数组输入流)
把内存的一个缓冲区作为InputStream使用。
构造方法有:
(1)创建一个字节数组输入流,使用buf作为它的缓冲区数组,从缓冲区数组中读取数据
1 | public ByteArrayInputStream(byte buf[]) |
(2)创建一个字节数组输入流,从指定字节数组中读取数据
1 | public ByteArrayInputStream(byte buf[], int offset, int length) |
3.PipedInputStream(管道输入流)
管道输入流是一个通讯管道的接收端。
构造方法有:
(1)创建连接到指定管道输出流的管道输入流
1 | public PipedInputStream(PipedOutputStream src) throws IOException |
(2)创建一个管道输入流,该输出流尚未连接到管道输出流
1 | public PipedInputStream() |
4.SequenceInputStream(序列输入流)
此类允许应用程序把几个输入连续地合并起来,它从一个有序的输入流集合开始,每个输入流依次被读取到文件的末尾,直到到达最后一个输入流的文件末尾。
构造方法有:
(1)参数中枚举生成运行时类型为输入流的对象。新创建一个序列输入流,通过读取由枚举产生的输入流来初始化它。
1 | public SequenceInputStream(Enumeration<? extends InputStream> e) |
(2)新创建一个序列输入流,按参数顺序读取输入流s1,s2来初始化它。
1 | public SequenceInputStream(InputStream s1, InputStream s2) |
5.StringBufferInputStream
不推荐使用,此类不能将字符正确地转换为字节。从一个串创建一个流的最佳方法是采用StringReader类。
6.FilterInputStream
其子类有LineNumberInputStream(java8中已不建议使用)、DataInputStream、BufferedInputStream、PushbackInputStream。
代码示例:解序列化
读取文件test1.txt内容的代码示例:
1 | public class Test { |
把序列化对象从文件中读取出来,会用到字节输入流(与上文把序列化对象写入文件关联):
1 | /** |
如下图所示:
字符流
字符流处理的基本单元为2个字节的Unicode字符,操作字符、字符数组或字符串,它通常用来处理文本数据。
java提供了Writer、Reader两个专门操作字符流的类。表示以Unicode字符为单位往stream中写入或读取信息。
字符输出流Writer
类与方法定义
抽象类,定义如下:
1 | public abstract class Writer implements Appendable, Closeable, Flushable |
常用方法有:
1 | 1.写一个字符 |
1 | 2.写一个字符数组 |
1 | 3.写入一个字符串 |
1 | 4.将指定的字符序列caq追加到该writer |
1 | 5.将指定字符追加到该writer |
1 | 6.强制性清空缓存 |
1 | 7.关闭输出流 |
要想使用此类,必须通过子类实例化对象,使用子类。
继承Writer的子类
FileWriter
如果是向文件中写入内容,应该使用FileWriter子类。与FileOutputStream对应。
++FileWriter继承自OutputStreamWriter,OutputStreamWriter继承自Writer++
构造方法:
1.构造给定File对象的FileWriter(文件写入器对象)
1 | public FileWriter(File file) throws IOException |
2.构造给定文件名的FileWriter
1 | public FileWriter(String fileName) throws IOException |
3.构造与文件描述器相关联的FileWriter
1 | public FileWriter(FileDescriptor fd) |
Writer的其他子类
1.BufferedWriter
2.CharArrayWriter
与ByteArrayOutputStream对应
3.PipedWriter
与PipedOutputStream对应
4.FilterWriter
5.StringWriter
代码示例:将字符串写入文本文件
1 | public class Test { |
字符输入流Reader
类与方法定义
抽象类,定义如下:
1 | public abstract class Reader implements Readable, Closeable |
常用方法有:
1 | 1.读取单个字符 |
1 | 2.将内容读入字符数组,返回读入的长度,如果到达输入流末端,返回-1 |
1 | 3.关闭输入流,并释放与之关联的任何系统资源 |
要想使用此类,必须通过子类实例化对象,使用子类。
继承Reader的子类
FileReader
如果要从文件中读取内容,可以直接使用FileReader子类。FileReader与FileInputStream对应。
++FileReader继承自InputStreamReader,InputStreamReader继承自Reader++。
构造方法:
1.创建一个新的FileReader,从指定的File对象读取数据
1 | public FileReader(File file) throws FileNotFoundException |
2.创建一个新的FileReader,从指定名称的文件中读取数据
1 | public FileReader(String fileName) throws FileNotFoundException |
3.创建一个新的FileReader,从指定的文件描述器读取数据
1 | public FileReader(FileDescriptor fd) |
Reader的其他子类
1.BufferedReader
2.CharArrayReader
与ByteArrayInputStream对应。此类实现一个可用作字符输入流的字符缓冲区。子类有LineNumberReader
3.PipedReader
与PipedInputStream对应
4.FilterReader
子类有PushbackReader。
5.StringReader
代码示例:
1 | public class Test { |
1 | //还可以以while循环判断是否读到底 |
1 | //还可以用readLine()方法 |
字节—字符转换
OutputStreamWriter和InputStreamReader是字节流与字符流的转换类。从源码中分析:
++OutputStreamWriter是Writer的子类,将输出的字符流转换成字节流。++
++InputStreamReader是Reader的子类,将输入的字节流转换成字符流。++
OutputStreamWriter
1 | public class OutputStreamWriter extends Writer |
OutputStreamWriter类是从字符流到字节流的桥梁。在OutputStreamWriter类中需要一个字节流的对象(OutputStream out)。
OutputStreamWriter将多个字符写入到一个输出流,根据指定的字符编码将写入的字符编码成字节。实际负责编码的是StreamEncoder类,过程中必须使用指定的编码集。
构造方法:
1.可以通过名称指定支持的字符编码集:
1 | public OutputStreamWriter(OutputStream out, String charsetName) |
2.构造器中指定Charset类型:
1 | out, Charset cs) OutputStreamWriter(OutputStream |
3.也可以接受平台的默认编码集:
1 | public OutputStreamWriter(OutputStream out) |
4.可以使用指定charset encoder:
1 | public OutputStreamWriter(OutputStream out, CharsetEncoder enc) |
write()方法的每次调用都会在给定字符(或字符串)上调用编码转换器。在写入底层输出流之前,将在缓冲区中积累产生的字节。
注意,传递给write()方法的字符没有缓冲。OutputStreamWriter的write()方法写入的是字符(或字符串),而非字节。写入字节的是输出字节流(OutputStream)。
为了提高效率,可以考虑将OutputStreamWriter包装在一个BufferedWriter中,以避免频繁的转换。例如:
1 | Writer out = new BufferedWriter(new OutputStreamWriter(System.out)); |
InputStreamReader
1 | public class InputStreamReader extends Reader |
InputStreamReader类是从字节流到字符流的桥梁。在InputStreamReader类中需要一个字节流的对象(InputStream in)。
它使用的字符集可以通过名称指定,也可以明确Charset类型,也可接受平台的默认字符集。与OutputStreamWriter类似。
构造函数:
1 | public InputStreamReader(InputStream in, String charsetName) |
每次调用InputStreamReader的read()方法,都可能导致从底层字节输入流中读取一个或多个字节。为了能有效地将字节转化为字符,可从底层输入流中读取更多字节,而不只是满足当前读取操作所需的字节。
注意,InputStreamReader是Reader的子类,其read()方法读取的是一个或多个字符。读取字节的是字节输入流(InputStream)。
为了提高效率,可以考虑将InputStreamReader包装在一个BufferedReader中,比如:
1 | BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); |
代码示例:用Buffered从Socket上读取数据
1 | /** |
如下图所示:
字节流与字符流的比较
字节流操作的基本单元是字节;字符流操作的基本单元为Unicode码元。
字节流在操作的时候本身不会用到缓冲区的,是与文件本身直接操作的;而字符流在操作的时候使用到缓冲区的。
所有文件的存储都是字节(byte)的存储,在磁盘上保留的是字节。
- 在使用字节流操作中,即使没有关闭资源(close方法),也能输出;而字符流不使用close方法的话,不会输出任何内容。
说明字符流用到了缓冲区,如果执行关闭输出流的话会刷新缓冲区,所以可以把内容输出。如果没有关闭,可以调用flush()方法强制刷新缓冲区,这样可以在不使用close()的情况下输出内容。
输入流、输出流相关类的使用判断
根据使用场景决定使用哪个类。参考[字符流和字节流的区别,使用场景,相关类]。如下(不考虑特殊需要):
1.考虑最原始的数据格式是什么:
(1)二进制格式(只要不能确定是纯文本的):InputStream,OutputStream及其子类(字节流)。
(2)纯文本格式(含纯英文与汉字或其他编码方式):Reader,Writer及其子类(字符流)。
2.是输入还是输出:
(1)输入:Reader,InputStream类型的子类。
(2)输出:Writer,OutputStream类型的子类。
3.是否需要转换流:
字节到字符:InputStreamReader
字符到字节:OutputStreamWriter
4.数据来源(去向)是什么:
(1)是文件:FileInputStream,FileOutputStream ; FileReader,FileWriter
(2)是byte[]: ByteArrayInputStream, ByteArrayOutputStream
(3)是Char[]:CharArrayReader,CharArrayWriter
(4)是String:StringBufferInputStream,StringBufferOutputStream;StringReader,StringWriter
(5)是网络数据流:InputStream,OutputStream;Reader,Writer
5.是否要缓冲:(要注意readLine()是否有定义,有什么比read(),writer()更特殊的输入或输出方法)
要缓冲:BufferedInputStream, BufferedOutputStream; BufferedReader, BufferedWriter
6.是否要格式化输出:
要格式化输出:PrintStream, PrintWriter
PrintStream是FilterOutputStream的子类。
PrintWriter是Writer的子类。可用PrintWriter写数据到Socket上。
还有如下特殊需要的情景:
(1)对象输入输出:ObjectInputStream, ObjectOutputStream
(2)进程间通信:PipedInputStream, PipedOutputStream, PipedReader, PipedWriter
(3)合并输入: SequenceInputStream
(4)更特殊的需要:
PushbackInputStream(FilterInputStream的子类),LineNumberInputStream(FilterInputStream的子类,java8中已不建议使用);
LineNumberReader(BufferedReader的子类),PushbackReader(FilterReader的子类)