Java IO流

Java IO流,主要有四大类:File文件、InputStream/OutputStream字节流、Reader/Writer字符流、RandomAccessFile随机存取文件类

IO 流

IO 流类图结构:
IO 流类图结构

Stream 流的概念
流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象;
即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作;

IO 流的分类
1) 根据处理数据类型的不同分为:字符流字节流
2) 根据数据流向不同分为:输入流输出流

字符流和字节流
字符流的由来:因为数据编码的不同,从而有了对字符进行高效操作的流对象;本质就是基于字节流读取时,去查了指定的码表。

字节流和字符流的区别:
1) 读写单位不同:字节流字节(8bit)为单位,字符流字符为单位,根据码表映射字符,一次可能读多个字节(2字节或者4字节);
2) 处理对象不同:字节流能处理所有类型的数据(如图片、视频、文字等),而字符流只能处理字符类型的数据;

结论:只要是处理纯文本数据,就优先考虑使用字符流,除此之外都使用字节流;

输入流和输出流
对输入流只能进行读操作,对输出流只能进行写操作,程序中需要根据待传输数据的不同特性而使用不同的流。

字节流类图
字节流类图

图中蓝色的为主要的对应部分,红色的部分就是不对应部分;紫色的虚线部分代表这些流一般要搭配使用。

抽象类:
InputStream:所有输入字节流的父类,它是一个抽象类;
OutputStream:所有输出字节流的父类,它是一个抽象类。

基本的类:
1) ByteArrayInputStream/ByteArrayOutputStream:从字节数组中获取数据/往字节数组中写入数据(在内存中操作)
2) FileInputStream/FileOutputStream:从磁盘文件中获取数据/往磁盘文件中写入数据(在外存中操作)
3) PipedInputStream/PipedOutputStream:从管道中获取数据/往管道中写入数据(线程间通信)

缓冲 IO 流:
BufferedInputStream/BufferedOutputStream:自带缓冲区的包装流,默认建立一个 8192 字节的缓冲区,可以显著提高 IO 效率

序列化相关:
1) DataInputStream/DataOutputStream:用于基本类型的序列化、反序列化操作,包括 8 大基本类型、String字符串;
2) ObjectInputStream/ObjectOutputStream:用于引用类型的序列化、反序列化操作,需实现 java.io.Serializable 接口(标记接口);

“存在即合理”,简单分析一下”不对称”的流类:
1) LineNumberInputStream:可以获取/设置数据的行号信息,已被标记为Deprecated,不建议使用;
2) PushbackInputStream:可以用来“窥探”输入流的内容而不对它们进行破坏;比较少用;
3) StringBufferInputStream:已被标记为Deprecated,本身就不应该出现在 java.io 包中;
4) SequenceInputStream:一个工具类,将两个或者多个输入流当成一个输入流依次读取;完全可以从 IO 包中去除;
5) PrintStream:用于格式化输出,提供 print、println、printf、format 系列方法;System.outSystem.err就是PrintStream的实例;

字符流类图
字符流类图

图中蓝色的为主要的对应部分,红色的部分就是不对应部分;紫色的虚线部分代表这些流一般要搭配使用。

抽象类:
Reader:所有输入字符流的父类,它是一个抽象类;
Writer:所有输出字符流的父类,它是一个抽象类。

基本的类:
1) CharArrayReader/CharArrayWriter:从字符数组中获取数据/往字符数组中写入数据(在内存中操作)
2) StringReader/StringWriter:从字符串中获取数据/往字符串中写入数据(在内存中操作)
3) InputStreamReader/OutputStreamWriter:从字节流中获取数据(解码decoding)/往字节流中写入数据(编码encoding)(字节流和字符流之间的桥梁)
4) FileReader/FileWriter:针对磁盘文件的InputStreamReader/OutputStreamWriter特化版本
5) PipedReader/PipedWriter:从管道中获取数据/往管道中写入数据(线程间通信)

缓冲 IO 流:
BufferedReader/BufferedWriter:自带缓冲区的包装流,默认建立一个 8192 字符(即 16 KB)的缓冲区,可以显著提高 IO 效率;

装饰 IO 流:
1) LineNumberReader:可以获取/设置数据的行号信息;
2) PrintWriter:支持格式化输出,提供 print、println、printf、format 方法,在 jdk1.4 之后,PrintStreamPrintWriter基本无区别,除了 autoFlush 的不同;

File 文件类
File 类是对文件系统中文件以及文件夹进行封装的对象,可以通过对象的思想来操作文件和文件夹;

File 类保存文件或目录的各种元数据信息,包括文件名、文件长度、最后修改时间、是否可读、获取当前文件的路径名,判断指定文件是否存在、获得当前目录中的文件列表,创建、删除文件和目录等方法;

注意,实例化一个 File 对象并不会检查对应的文件(夹)的真实性,只有在进行真正的 IO 操作时才会检查(如创建文件、删除文件等);

RandomAccessFile
RandomAccessFile 并不是上述流体系中的一员,其封装了字节流,同时还封装了一个缓冲区,通过内部的文件指针来操作文件;
RandomAccessFile 的特点:既可以对文件进行读操作,也能进行写操作,在进行对象实例化时可指定操作模式(rrwrwsrwd);
RandomAccessFile 多用于操作大文件、多线程下载、多个线程同时操作文件;

STDIN、STDOUT、STDERR
Java 和 C/C++ 一样,默认为每个进程打开了 3 个文件,即标准输入文件标准输出文件标准错误文件

这 3 个标准文件定义在 java.lang.System 类中,均为静态成员:
1) System.inInputStream类的实例,文件描述符 FileDescriptor 为 FileDescriptor.in,fd = 0;
2) System.outPrintStream类的实例,文件描述符 FileDescriptor 为 FileDescriptor.out,fd = 1;
3) System.errPrintStream类的实例,文件描述符 FileDescriptor 为 FileDescriptor.err,fd = 2;

File 和 FileDescriptor 的区别
一个 File 对象代表一个抽象的”磁盘文件”,仅仅是一个记录而已,和真正的文件之间没有关联;
一个 FileDescriptor 表示一个已打开的文件,在 Linux 中就是一个”文件描述符 fd”,就像建立的网络连接一样;

File 类

构造函数
public File(String pathname);:文件名(包含路径),可以为相对路径、绝对路径;
public File(String parent, String child);:父路径 + 子路径;
public File(File parent, String child);:父路径(File 对象) + 子路径;
public File(URI uri);:java.net.URI 形式,如file:///etc/sysctl.d/default.conf

常用方法

File.separator
在 Windows 下的路径分隔符和 Linux 下的路径分隔符是不一样的,当直接使用绝对路径时,跨平台会暴出“No such file or diretory”错误;

比如要在 tmp 目录下建立一个临时文件 test.tmp:
Windows 下应写为:File f = new File("C:\\tmp\\test.tmp");,也可以为File f = new File("C:/tmp/test.tmp");
Linux 下应写为:File f = new File("/tmp/test.tmp");
如果考虑跨平台,最好这么写:File f = new File("C:" + File.separator + "tmp" + File.separator + "test.tmp");

File 类的几个 static 分隔符:
1) public static final char separatorChar:char 形式,文件路径分隔符,Windows下为\,Linux下为/
2) public static final String separator:String 形式,同上;
3) public static final char pathSeparatorChar:char 形式,路径列表分隔符,Windows下为;,Linux下为:
4) public static final String pathSeparator:String 形式,同上;

例子:

InputStream、OutputStream

InputStream 抽象类

OutputStream 抽象类

ByteArray 字节数组

ByteArrayInputStream
构造函数
public ByteArrayInputStream(byte buf[]);
public ByteArrayInputStream(byte buf[], int offset, int length);

常用方法

ByteArrayOutputStream
构造函数
public ByteArrayOutputStream();:default 大小为 32(数组长度)
public ByteArrayOutputStream(int size);

常用方法

File 文件

FileInputStream
构造函数
public FileInputStream(String name) throws FileNotFoundException;
public FileInputStream(File file) throws FileNotFoundException;
public FileInputStream(FileDescriptor fdObj);

常用方法

FileOutputStream
构造函数
public FileOutputStream(String name) throws FileNotFoundException;
public FileOutputStream(String name, boolean append) throws FileNotFoundException;
public FileOutputStream(File file) throws FileNotFoundException;
public FileOutputStream(File file, boolean append) throws FileNotFoundException;
public FileOutputStream(FileDescriptor fdObj);

常用方法

Buffered 缓冲流

BufferedInputStream
构造函数
public BufferedInputStream(InputStream in);:default 大小 8192 字节
public BufferedInputStream(InputStream in, int size);

常用方法

BufferedOutputStream
构造函数
public BufferedOutputStream(OutputStream out);:default 大小 8192 字节
public BufferedOutputStream(OutputStream out, int size);

常用方法

Data 基本类型

DataInputStream
构造函数
public DataInputStream(InputStream in);

常用方法

DataOutputStream
构造函数
public DataOutputStream(OutputStream out);

常用方法

Object 引用类型

ObjectInputStream
构造函数
public ObjectInputStream(InputStream in) throws IOException;

常用方法

ObjectOutputStream
构造函数
public ObjectOutputStream(OutputStream out) throws IOException;

常用方法

自定义序列化
readObject()、writeObject() 方法先检查序列化对象的类是否已定义 readObject()、writeObject() 方法,如果有那么就执行自定义的序列化操作,否则执行 defaultReadObject()、defaultWriteObject() 默认操作;

例子:

对象序列化
序列化是一种对象持久化的手段,普遍应用在网络传输、RMI(远程方法调用)等场景中;

Java 平台允许我们在内存中创建可复用的 Java 对象,但一般情况下,只有当 JVM 处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比 JVM 的生命周期更长;但在现实应用中,就可能要求在 JVM 停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象;Java 对象序列化就能够帮助我们实现该功能;

使用 Java 对象序列化,在保存对象时,会把其状态保存为一组字节,在未来,再将这些字节组装成对象;必须注意地是,对象序列化保存的是对象的”状态”,即它的成员变量;由此可知,对象序列化不会关注类中的静态变量;

除了在持久化对象时会用到对象序列化之外,当使用 RMI(远程方法调用),或在网络中传递对象时,都会用到对象序列化;Java 序列化 API 为处理对象序列化提供了一个标准机制,该 API 简单易用;

在 Java 中,只要一个类实现了java.io.Serializable接口,那么它就可以被序列化;

序列化及反序列化相关知识
1、序列化只关心实例变量,并不会将静态变量进行序列化(对静态变量进行序列化也没有实际意义);
2、使用 ObjectOutputStream 和 ObjectInputStream 的 writeObject()、readObject() 方法进行序列化及反序列化操作;
3、一个对象能否反序列化成功,非常重要的一点是序列化 ID 是否一致(serialVersionUID);
4、serialVersionUID可以由 IDE 生成,也可以通过 jdk 自带的serialver工具生成;
5、transient(短暂的)关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化,在反序列化后,transient 变量的值被设为初始值,如 int 整型的是 0,引用类型的是 null;
6、Java 允许我们自己定义对象的序列化及反序列化操作,只需要在类中定义 writeObject()、readObject() 方法;
private void writeObject(ObjectOutputStream out) throws IOException
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException

如果一个类想被序列化,就必须实现 java.io.Serializable 接口。否则将抛出 NotSerializableException 运行时异常;
如果你仔细观察 ObjectOutputStream.writeObject0() 方法,可以发现,如果被序列化的对象不属于String字符串Array内置数组Enum枚举Serializable可序列化对象,那么将抛出 NotSerializableException 异常。

特别的,如果序列化对象的成员变量为引用类型,并且它不属于 String、Array、Enum,Serializable 类型,那么在执行序列化的过程中,将会抛出运行时异常 NotSerializableException;

因此对于上述情况的引用类型成员变量,有两种选择:
1) 使用 transient 关键字声明该成员,在序列化时跳过该成员变量,在反序列化之后自动赋值 null;
2) 想办法让其实现 Serializable 接口,如果是自定义的类还好办,稍作修改就可以,否则只能选择第一种方式来处理了。

其实还有一种刁钻的方式,创建一个序列化类的派生类,这个派生类没有任何声明的成员,仅仅实现了 Serializable 接口;
然后将这个派生类用于序列化反序列化操作,这样就能完美的避免 ObjectOutputStream.writeObject0() 的类型检查了。
不过有一个地方需要注意,序列化类不能实现 Serializable 接口,否则在运行时照样抛出 NotSerializableException 异常!

父类与子类的序列化问题
思考两个问题:
1) 父类实现了 Serializable 接口,而子类没有实现该接口,在序列化子类对象时有问题吗?
2) 子类实现了 Serializable 接口,而父类没有实现该接口,在序列化子类对象时有问题吗?

第一个问题很好解决,因为如果父类实现了 Serializable 接口,那么继承自父类的子类自然实现了 Serializable 接口,即使子类没有显式实现 Serializable 接口,在序列化子类对象时也不会有任何问题;

第二个问题暂时不能下定论,我们还是实践得真知,先测试一下:

我们在父类 A 中定义了一个整型 n、一个整型数组 arr,它们都是可序列化对象;
父类 A 没有实现 Serializable 接口,而子类 B 实现了 Serializable 接口;
在进行序列化和反序列化之后,发现父类的两个成员变量并未丢失,它们被序列化了。

那么会不会是因为这两个成员都是可序列化的才导致了这么一个现象呢?
我们再来测试一下:

到现在总算是可以得出答案了,如果父类没有实现 Serializable 接口,而子类实现了 Serializable 接口,那么序列化子类对象的同时也会序列其所有父类的非静态成员,不管其是否为可序列化对象。

其实从虚拟机的角度来说,只要知道了引用(对象、对象的成员),那么直接可以将引用地址上的内存 dump 出来,完全不需要考虑 Serializable 的相关问题,因为你要知道 Serializable 只是一个标记接口,仅仅当做一个类型来使用,它并没有定义任何关于序列化有用的方法;

构造函数

到现在为止,我们可以创建自定义的 stdin、stdout、stderr 标准输入输出文件了:
1) InputStream stdin = new FileInputStream(FileDescriptor.in);
2) PrintStream stdout = new PrintStream(new FileOutputStream(FileDescriptor.out));
3) PrintStream stderr = new PrintStream(new FileOutputStream(FileDescriptor.err));

常用方法

Reader、Writer

Reader 抽象类

Writer 抽象类

字符编码、内码、外码等知识

CharArray 字符数组

CharArrayReader
构造函数
public CharArrayReader(char buf[]);
public CharArrayReader(char buf[], int offset, int length);

常用方法

CharArrayWriter
构造函数
public CharArrayWriter();:default 值为 32(字符数组长度)
public CharArrayWriter(int initialSize);

常用方法

String 字符串

StringReader
构造函数
public StringReader(String s);

常用方法

StringWriter
构造函数
public StringWriter();:内部调用new StringBuffer();,默认大小 16 字符,即 32 字节;
public StringWriter(int initialSize);

常用方法

InputStream/OutputStream 桥梁

InputStreamReader
构造函数
public InputStreamReader(InputStream in);:自动识别编码
public InputStreamReader(InputStream in, String charsetName) throws UnsupportedEncodingException;

常用方法

OutputStreamWriter
构造函数
public OutputStreamWriter(OutputStream out);:使用平台默认编码,Linux 下为UTF-8
public OutputStreamWriter(OutputStream out, String charsetName) throws UnsupportedEncodingException;

常用方法

File 文件

FileReader
构造函数
public FileReader(String fileName) throws FileNotFoundException;
public FileReader(File file) throws FileNotFoundException;
public FileReader(FileDescriptor fd);

FileWriter
构造函数
public FileWriter(String fileName) throws IOException;
public FileWriter(String fileName, boolean append) throws IOException;
public FileWriter(File file) throws IOException;
public FileWriter(File file, boolean append) throws IOException;
public FileWriter(FileDescriptor fd);

Buffered 缓冲流

BufferedReader
构造函数
public BufferedReader(Reader in);:default 值为 8192 字符,即 16 KB;
public BufferedReader(Reader in, int sz);

常用方法

BufferedWriter
构造函数
public BufferedWriter(Writer out);:default 值为 8192 字符,即 16 KB;
public BufferedWriter(Writer out, int sz);

常用方法

LineNumber 行号

构造函数
public LineNumberReader(Reader in);:default 值为 8192 字符,即 16 KB;
public LineNumberReader(Reader in, int sz);

常用方法

构造函数

常用方法

RandomAccessFile

RanndomAccessFile实现了接口DataOutput/DataInput
实际上与 InputStream/OutputStream、Reader/Writer 无关
最大的特点是支持随机存储,内部依靠一个”文件指针”实现

构造函数

常用方法