java.io
通过数据流、序列化和文件系统提供系统输入和输出。
我有一个习惯,在学习新的知识体系时,总是先将它的类层次结构搞清楚,这样可以对类有一个全局观。下面先看一下 java IO 系统的类关系图
字符流:
字节流:
一、File 类
File 类是文件和目录路径的抽象表示形式。File 类定义了一些与平台无关的方法来操纵文件,例如:创建、删除和重命名文件。
编程举例:判断某个文件是否存在,存在则删除,不存在则创建。
二、流
流是字节序列的抽象概念。文件是数据的静态存储形式,而流是指数据传出时的形态。
三、字节流
1、InputStream 类
程序可以冲中连续读取字节的对象叫输入流,在 Java 中,用 InputStream 类来描述所有输入流的抽象概念。
InputStream 类的方法:
- int read() :从输入流中读取一个字节的内容,并且把这个内容以整数的形式反回,如果遇到流的结束处,则返回 -1 。如果流没有结束,但是暂时没有数据可读,read() 方法将阻塞,直到有新的数据可读。因为 read() 只读取了一个字节的数据,read() 方法会将这一个字节的数据写在 int 4字节中的最低位,其它高字节位全部设为 0 ,这个过程与 byte 转为 int 的过程是不一样的。因此 read() 方法返回的整数应在 0~255 之间,这样就可以与表示流结束的 -1 数值进行区别。
- int read(byte[] b) :从输入流中读取若干个字节的内容到 byte 数组中,最多读取的数据长度为 byte 数组的长度。返回值为实际读取到的字节数。
- int read(byte[] b, int off, int len) :从输入流中读取 len 个字节的数据,然后将读取的数据从数组 b 的角标开始存放到数组中,和上面一样,返回值为实际读取到的字节数。
- long skip(long n) :跳过输入流中的 n 个字节,并返回实际跳过的字节数。主要用于包装流中。除了包装流类,其它流只能顺序读取数据。
- int avaliable() :返回流中可读的字节数。检查流中是否有数据可读,避免程序发生阻塞。
- void mark(int readlimit) :用与在输入流中,建立一个标记,从 readlimit 位置开始,最多还能读取多少数据。用于包装类。
- void reset() :于 mark() 方法结合使用,让指针跳到当前标记出。
- boolean markSupported() :返回当前流对象,是否支持 mark() 和 reset() 操作。
- void close() :将流对象关闭,以释放与流对象相关的资源。垃圾回收期只会清除没有变量引用的对象资源,而不能清除系统产生的系统资源,因此在流结束后要使用 close() 方法。
InputStream 是抽象类,实际使用到的是 InputStream 的子类,但并不是所有的子类对象都具有上面的方法 。
(1)FileInputStream 用来创建磁盘文件的输入流对象,通过 FileInputStream 的构造函数来指定文件路径和文件名。创建 FileInputStream 实例对象时,指定的文件应该是存在的和可读的。对于一个磁盘文件创建 FileInputStream 对象的两种方式:
1、FileInputStream in = new FileInputStream("fileName");
2、File f = new File("fileName");
FileInputStream in = new FileInputStream(f);
编程举例:用 FileOutputStream 类向文件中写入一个字符串,然后用 FileInputStream 读出写入的内容。
(2)PipedInputStream 和 PipedInputStream 用于在应用程序中创建管道通信。一个 PipedInputStream 对象必须与一个 PipedOutputStream 对象进行连接产生一个通信管道。PipedOutoutStream 对象负责向管道中写入数据, PipedInputStream 对象负责从管道中读取数据。主要完成线程之间的通信。一个线程的 PipedInputStream 能从另一个线程的 PipedOutputStream 对象中读取数据。
编程举例:实现两个线程之间的通信
使用管道流类,可以实现各个程序模块之间的松耦合通道。
(3)ByteArrayInputStream 用于以 IO 流的方式来完成对字节数组内容读写,来支持类似内存虚拟文件或者内存映像文件的功能。
构造函数:
1、ByteArrayInputStream(byte[] b)
2、ByteArrayInputStream(byte[] b, int offset, int length)
编程举例:编写一个函数,把输入六中所有英文字母编程大写字母后,将结果写入带一个输出流对象,用这个函数来将一个字符串中的所有字符转换成大写。
(4)System.in 和 System.out System.in 连接到键盘,是 InputStream 类型的实例对象。System.out 连接到显示器,是 PrintStream 类的实例对象。
编程举例:借助上一个杉树,将键盘上输入的内容转为大写字母后打印在屏幕上。
(5)DataInputStream 并没有对应到任何具体的流设备,一定要给它传递一个对应具体流设备的输入流对象。DataInputStream 为包装类。构造 DataInputStream 的方法:
DataInputStream(InputStream in)
DateInputStream 提供了一个读取字符串的方法:
1、public final String readUTF();
DateOutputStream 提供了三个写入字符串的方法:
1、public final void writeBytes(String s);
2、public final void writeChars(String s);
3、public fianl void writeUTF(String s);
方法:
boolean | readBoolean () 参见 DataInput 的 readBoolean 方法的常规协定。 |
byte | readByte () 参见 DataInput 的 readByte 方法的常规协定。 |
char | readChar () 参见 DataInput 的 readChar 方法的常规协定。 |
double | readDouble () 参见 DataInput 的 readDouble 方法的常规协定。 |
float | readFloat () 参见 DataInput 的 readFloat 方法的常规协定。 |
int | readInt () 参见 DataInput 的 readInt 方法的常规协定。 |
long | readLong () 参见 DataInput 的 readLong 方法的常规协定。 |
short | readShort () 参见 DataInput 的 readShort 方法的常规协定。 |
(6)BufferedInputStream Java 提供的两个缓冲区包装类,不管底层系统是否使用了缓冲区,这两个类在自己的实例对象中创建缓冲区。依然是用 InputStream 类的实例来构造自己。
流栈的包装过程:
编程实例:分别使用 DataOutputStream 类的 writeUTF、writeBytes、writeChars 方法,比较这几个方法的差异。
(7)ObjectInputStream ObjectInputSteam 包装类用于从底层输入流中读取对象类型的数据和将对象类型的数据写入到底层输出流。只要把一个对象中的所有成员变量存储起来就等于保存了这个对象。ObjectInputStream 所要写的对象必须实现了 Serializable 接口,Serializable 接口中没有方法,是一个空的接口,它的作用就是为编译器做标记。对象中的 transient(临时变量标记,例如线程对象可以被 transient 修饰,因为没有保存的必要)和 static 类型的成员变量不会被写入。
编程举例:创建一个可序列化的学生对i向,并用 ObjectOutputStream 类把它存储到一个文件中,然后再用 ObjectInputStream 类把存储的数据读取到一个学生对象中,即反序列化。
2、OutputStream 类
程序可以向其中连续写入字节的对象叫输出流,在 Java 中,用 OutputStream 类来描述所有输出流的抽象概念。
OutputStream 类的方法:
- void write(int b) :将一个整数中的最低的一个字节的内容写到输出流中,其它高字节省去。
- void write(byte[] b) :将一个字节数组 b 中的所有内容写入到输出流中。
- void write(byte[] b, int off, int len) :将字节数组中从 off 个字节开始,一直写到 第 len 个字节。
- void flush() :将内存缓冲区中的内容彻底的清空,并且输出到 I/O 设备中。
- void close() :关闭流对象,并释放资源。
(1)FileOutputStream 用来创建磁盘文件的输出流对象,通过 FileOutputStream 的构造函数来制定文件路径和文件名。 创建 FileOutputStream 实例对象时,指定的文件应该是存在的和可写的,并且没有被其它应用程序占用的。对于一个磁盘文件创建 FileOutputStream 对象有以下方式:
1、 FileOutputStream out = new FileOutputStream ("fileName");
2、 FileOutputStream out = new FileOutputStream ("fileName", true); //是否追加
3、 File f = new File("fileName");
FileOutputStream out = new FileOutputStream (f);
FileOutputStream out = new FileOutputStream (f ,true);//是否追加
内存缓冲区的作用: 在应用程序和 I/O 设备之间,通常会有内存缓冲区。这是由于计算机访问外部设备的速度,要比直接访问内存的速度慢得多,如果在应用程序中的每一次 write 方法中都直接将数据写入到外部设备中,cpu 就要花费更多的时间等待数据写完。如果在程序中开辟了一个内存缓冲区,程序的每一次 write 方法的调用都是先将数据写入到缓冲区中,只有缓冲区被填满后,cpu 踩会将缓冲区的内容一次性的写入到外部设备中。使用内存缓冲区有两个方面的好处:1、提高了 cpu 的使用率。2、 write 方法并没有真正写入到外部设备,程序还有机会撤销操作。对于输入流也可以使用内存缓冲区,可以将外部大量数据读取到内存缓冲区中,然后再从内存缓冲区中读 取到应用程序中来。
(2)ByteArrayOutStream 参照 ByteArrayInputStream。
(3)DataOutputStream 参照 DataInputStream。
(4)BufferedOutputStream 参照 BufferedInputStream。
(8)ObjectOutputStream 参照 ObjectInputStream。
四、字符流
1、Reader 类
用于读取字符流的抽象类,读取文本格式的内容。Java 中的字符是采用 Unicode 编码的,是双字节的。而字节流中要读取字符串时,我们需要将它转换为字节后再读取数据。Java 中提供了单独的字符流类。
二进制文件与文本文件的区别: 在不考虑正负数的情况下,每个字节中的数据在0~255之间 ,它们在内存中都是以 二进制的形式存在。文件就是内存中的数据复制到硬盘上的存储形式,文件中的每个字节的数据也都是二进制形式的,所以严格的说磁盘上的每个文件都是二进制文 件。各种文本字符都是由一个或多个字节组成的,但是组成这些字符的字节不能是0~255之间的任意数,而是特定的数,有些数是不能存在于任何字符中的。如 果一个文件中所有每相邻的字节数据都可以表示成一个字符,那我们就称这个文件为文本文件。文本文件只是二进制文件的一种特例,为了与文本文件区别,人们又 把除了文本文件以外的文件称为二进制文件。
(1)FileReader 用来读取字符文件的便捷类。
(2)InputStreamReader 用来读取磁盘数据的便捷类。是字节流通向字符流的桥梁
编程举例: 用 FileWriter 类向文件中写入一个字符串,然后用 FileReader 独处写入的内容。
(3)PipedReader 和 PipedWriter 对照 PipedInputStream 和 PipedOutputStream
(4)StringReader 以字符 IO 流的方式处理字符串
(5)BufferedReader 参照 BufferedInputStream。
2、Writer 类
用于写入字符流的抽象类,写入文本格式的内容。
(1)FileWriter 用来写入字符文件的便捷类。
(2)OutStreamWriter 用来写入磁盘数据的便捷类。是字节流通向字符流的桥梁
(3)StingWriter 参照 StringReader。
(4)BufferedWriter 参照 BufferedOutputtStream。
五、字节流与字符流之间的转换
- 能不能找到一种简单的方式来读取键盘上输入的一行字符?我们很容易的想到 BufferedReader 中有一个 readLine() 的方法,但是 readLine() 只能接收 Reader 类型的字符流,而键盘的 System.in 属于字节流,上面曾经说过,InputStreamReader 和 OutputStreamWriter 是字节流与字符流之间的桥梁,所以我们想到了用 InputStreamReader 来包装 InputStream 类型的流。代码:
InputStreamReader 和 OutputStreamWriter ,是用于将字节流转换成字符流的两个类, InpuStreamReader 可以将一个字节流中的字节解码成字符后读取,OutStreamWriter 将字符解码成字节后写入到一个字节流中。为了避免繁琐的在字节与字符之间转换,最好不要直接使用 InputStreamReader 和 OutputStreamWriter ,应该尽量使用 BufferedWriter 和 BufferedReader 来包装它们。
六、Java 程序与其它进程的数据通信
在 Java 中可以使用 Process 类的实例对象来表示子进程,子进程的标准输入和输出不再连接到键盘和显示器,而是以管道流的形式连接到父进程的一个输出流和输入流对象上。调用 Process 类的 getOutputStream 和 getInputStream 方法可以获得连接到子进程的输出流和输入流对象。
七、特殊的 RandomAccessFile 类
RandomAccessFile 类提供了众多的文件访问方法。 RandomAccessFile 类支持随机访问方式,也就是可以跳转到文件的任意位置开始读写数据。 RandomAccessFile 实例对象中有个指示器,它可以跳转到文件任意位置 。 RandomAccessFile 读写操作都是从指示器所指示的当前位置开始读写,当读写 N 个字节以后,文件指示器将指向 N 个字节后的下一个字节处。 RandomAccessFile 类在随机(相对于顺序读取而言)读写等长记录格式的文件时有恨大的优势。 RandomAccessFile 类仅限于操作文件,不能访问其它的 IO 设备。
RandomAccessFile 类两种构造方法:
1、new RandomAccessFile(file,"rw"); //读写方式
2、new RandomAccessFile(file,"r"); //只读方式
八、总结
字节流输入流(InputStream)
FileInputStream、 ObjectInputStream、 PipedInputStream、 ByteArrayInputStream、 DataInputStream、 BufferedInputStream、 FilterInputStream
字节输出流(OutputStream)
FileOutputStream、ObjectOutputStream、PipedOutputStream、ByteArrayOutputStream、DataOutputStream、BufferedOutputStream、FilterOutputStream、PrintStream
字符输入流(Reader)
FileReader、CharArrayReader、StringReader、PipedReader、FilterReader、BufferedReader、InputStreamReader
字符输出流(Writer)
FileWriter、 CharArrayWriter、 StringWriter、 PipedWriter、 FilterWriter、 BufferedWirter、 OutputStreamReader