沉淀再出发:java的文件读写
一、前言
对于java的文件读写是我们必须使用的一项基本技能,因此了解其中的原理,字节流和字符流的本质有着重要的意义。
二、java中的I/O操作
2.1、文件读写的本质
概念框架:
方式: 字节流 Byte 和 字符流 Char
方向: 输入 Input 和 输出 Output ; 读 Reader 和 写 Writer
源: 字符串 String, 数组 Array, 对象 Object, 文件 File, 通道 Channel, 管道 Pipe, 过滤器 Filter,控制台 Console, 网络 Network Link ;
一切可产生/接收数据的 Data
性能: 带缓冲和不带缓冲的
行为: Readable, Appendable, Closable, Flushable, Serializable/Externalizable
概念组合:
方式、方向、源、行为四个维度可以组合出各种特性的IO流。
JavaIO框架采用装饰器模式,可以在现有IO流的基础上添加新的IO特性,生成更强功能的IO流,体现了读写能力的可扩展性。
比如要构造一个带缓冲的文本文件读取流:
File -> FileInputStream -> InputStreamReader -> FileReader -> BufferedFileReader,
其中文本:字符流, 文件:源, 读:输入方向, 带缓冲的:性能。
读写耗时的字符输入输出流可以使用缓冲来提升性能,比如文件读写、网络流读写等。
这是由于每个待读写的字符都要经过读写字节、字节/字符转换两个操作。
缓冲实际上是将大量单个的读写操作转换为一个大的单个的批量读写操作。
字节流:
字节输入流:InputStream, BufferedInputStream, FileInputStream, ByteArrayInputStream, PipedInputStream, FilterInputStream, DataInputStream, ObjectInputStream;
字节输出流: OuputStream, BufferedOuputStream, FileOuputStream, ByteArrayOuputStream, PipedOuputStream, FilterOuputStream, DataOuputStream, ObjectOuputStream, PrintStream;
字符流:
字符输入流: Reader, BufferedReader, FileReader, CharArrayReader, PipedReader, FilterReader, StringReader, LineNumberReader;
字符输出流: Writer, BufferedWriter, FileWriter, CharArrayWriter, PipedWriter, FilterWriter, StringWriter, PrintWriter;
字节流与字符流之间的转化:
InputStreamReader:输入字节流转化为输入字符流
OutStreamWriter: 输出字符流转化为输出字节流
装饰器模式:
多个类实现同一个接口,并且这些实现类均持有一个该接口的引用,通过构造器传入,从而可以在这些实现类的基础上任意动态地组合叠加,构造出所需特性的实现来。装饰器模式可以实现数学公式/逻辑公式运算函数。
实现:
在IO流的实现类中,通常有一个 Char[],Byte[], CharBuffer, StringBuffer, ByteBuffer的成员数组或成员对象。将成员数组/对象里的数据写到方法里给定的参数数组或返回值,即为读实现;将方法里给定的参数数组写到成员数组或成员对象里,即为写实现。读写数据本质上就是数据在不同源之间的拷贝实现。
IO流通常是会实现读写单个字节/字符、读写字节数组/字符数组、跳过若干字节/字符的功能。成员 (next,pos,mark,count) 用于标识数据读写位置;mark 与 reset 方法组合可实现重复读写。
要实现一个自定义的 Reader, 可参考 StringReader 实现;StringReader 可以将字符串作为字符流来读取,内部成员为String;
而 StringWriter 内部成员为线程安全的 StringBuffer。两者均要考虑并发读写的问题,使用一个 Object 锁进行互斥访问。
FileReader 是文本文件读取流,通过继承 InputStreamReader, 转化 FileInputStream 而得。
因文件是对磁盘字节数据的抽象,因此要获得人类可读的文本,必然存在从字节流到字符流的转换。
InputStreamReader 使用 StreamDecoder 将字节流转换为指定字符编码的字符流,后续读操作直接委托 StreamDecoder 读取;
StreamDecoder 是一个字节解码器, 是 Reader 实现类。
OutputStreamWriter 使用 StreamEncoder 将字符流转换为指定字符编码的字节流,
后续写操作直接委托 StreamEncoder 写入底层 OutputStream;StreamEncoder 是一个 Writer 实现类。
FilterReader, FilterWriter, FilterInputStream, FilterOutputStream: IO流的抽象类,分别持有一个[Reader,Writer,InputStream, OutputStream],
自定义 IO 流可以通过继承 FilterXXX 来实现。CommonIO库里 ProxyReader, ProxyWriter 分别提供了 Reader, Writer 的一个示例实现,
java.io.DataInputStream 提供了 FilterInputStream 的一个示例实现,java.io.PrintStream 提供了 FilterOutputStream 的一个示例实现。
BufferedReader: 提供了缓冲读及读行的功能。
BufferedReader 主要成员为 [Reader in, char[defaultsize=8192] cb, int nextChar, int nChars, int markedChar];
nextChar 指示将要读取的字符位置, nChars 指示可以读取字符的终止位置,
markedChar 表示被标记的将要读取字符的位置;
BufferedReader 从字符流 in 中读取字符预存入缓冲字符数组 cb 中,然后在需要的时候从 cb 中读取字符。
BufferedReader 的实现由 fill 和 read, readLine 共同完成。
当缓冲字符数组的字符数不足于填充要读取的参数数组时,就会使用 fill 方法从输入流 in 中读取数据再进行填充; PipedReader: 管道输入流,必须从 PipedWriter 中读取。
底层实现有两个线程、两个位置索引以及一个字符数组。
两个线程分别是读线程 readSide 和 写线程 writeSide, 两个位置索引分别用于指示字符数组中将要写的位置和将要读的位置。
字符数组是一个循环队列,默认大小为 1024 chars 。初始化时必须将 PipedReader 的输入连接至 PipedWriter 。
读实现方法中,即是将底层字符数组拷贝到方法中的参数数组。PipedWriter 的实现相对简单,其输出连接至 PipedReader;
PipedWriter 的写就是 PipedReader 的读,因此 PipedWriter 的方法实现委托给 PipedReader 引用的方法。
文件:
文件相关的类: File, RandomAccessFile, FileDescriptor, FileChannel, FilePermission, FilePermissionCollection, FileFilter, FileSystem, UnixFileSystem ;
文件 File: 唯一性路径标识【目录路径/文件名[.文件扩展名]】、文件元信息【描述符、类型、大小、创建时间、最近访问时间、节点信息】、文件访问权限【读/写/执行组合】等;
File 的操作会先使用 SecurityManager 检查访问权限,然后通过 FileSystem 判断和操作。
构造可以实际读写的文件输入输出流需要使用 FileInputStream, FileOutputStream, FileReader, FileWriter, BufferedReader, BufferedWriter, BufferedInputStream, BufferedOutputStream 来包装 File ,
比如 new BufferedReader(new FileReader(new File(filename))) 。
FileChannel: 操作文件内容的通道;FileChannel 是线程安全的,使用 java.nio 引入的高性能机制来实现。
文件描述符 FileDescriptor : 用于操作打开的文件、网络socket、字节流的唯一标识句柄;
其成员是一个整型数 fd 和一个原子计数器 AtomicInteger useCount ;
标准输入流 System.in.fd = 0 , 标准输出流 System.out.fd = 1 , 标准错误流 System.err.fd = 2
文件访问权限 FilePermission: 提供了【文件可进行的操作 actions 与位掩码表示 mask 】之间的转化方法。
文件可进行的操作 actions 在类 SecurityConstants 的常量中定义;
mask 是一个整型数,表示文件的执行(0x01)/写(0x02)/读(0x04)/删(0x08)/读链接(0x10)/无(0x00) 等权限组合。
FileSystem:所使用的操作系统平台的文件系统。
指明该文件系统所使用的【文件分隔符、路径分隔符、路径标准化、路径解析、文件访问安全控制、文件元信息访问、文件操作等】。
文件分割符是指路径名中分割目录的符号,linux = '/', windows = '\' ;
路径分割符是环境变量中分割多个路径的符号,linux = ':' , windows = ';' ;
UnixFileSystem 提供了 FileSystem 的一个实现示例。
枚举:
存在 BA_EXISTS = 0x01;
普通文件 BA_REGULAR = 0x02;
目录 BA_DIRECTORY = 0x04;
隐藏 BA_HIDDEN = 0x08;
可读 ACCESS_READ = 0x04;
可写 ACCESS_WRITE = 0x02;
可执行 ACCESS_EXECUTE = 0x01;
判断文件属性及访问权限采用与或非位运算实现, 优点是可以灵活组合。
序列化:
序列化是实现内存数据持久化的机制。可以将 Java 对象数据存储到文件中,或者将文件中的数据恢复成Java对象。Java RPC, Dubbo,ORM 等都依赖于序列化机制。
序列化相关的类及方法: Serializable/Externalizable, ObjectInputStream, ObjectOutputStream, readObject, writeObject 。
使用 ObjectInputStream.readObject, ObjectOutputStream.writeObject 方法进行对象序列化:
JDK提供的标准对象序列化方式;对象必须实现 Serializable 接口;
适用于任何 Java 对象图的序列化,通用的序列化方案;输出为可读性差的二进制文件格式,很难为人所读懂,无法进行编辑;
对某些依赖于底层实现的组件,存在版本兼容和可移植性问题;只有基于 Java 的应用程序可以访问序列化数据;
使用 XMLDecoder.readObject, XMLEncoder.writeObject 方法进行对象序列化:
对象是普通的 javaBean,无需实现 Serializable 接口;输出为可读性良好的XML文件,容易编辑和二次处理;
其它平台亦可访问序列化数据;可以修改数据成员的内部结构的实现,只要保留原有bean属性及setter/getter签名,就不影响序列化和反序列化;
可以解决版本兼容的问题,提供长期持久化的能力;每个需要持久化的数据成员都必须有一个 Java bean 属性。
实现 Externalizable 接口来进行对象序列化。
需在对象中实现 readObject, writeObject 方法,提供更高的序列化灵活性,由开发者自行决定实例化哪些字段、何种顺序、输出格式等细节。
NIO:
java.nio 为 javaIO 体系引入了更好的性能改善,主要是 Buffer, Channel 和 file 相关的支撑类。
由于 java.io 类已经引用了 nio 里的类,因此直接使用 java.io 里的类就可以获得 nio 带来的好处了。
2.2、案例分析
首先我们看一个例子:
字节流读写测试,可以发现如果预先规定了Byte对象的大小,就会产生乱码:
package com.io.test; import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream; public class ByteTest { public static void main(String[] args) { // 第一种方式读文件,因为方法throws了异常,所以在这要捕获异常
try {
ByteTest.readFromFileByByte();
} catch (FileNotFoundException e) {
e.printStackTrace();
System.out.println("找不到文件啊");
} catch (IOException e) {
e.printStackTrace();
System.out.println("读不成功啊!");
} System.out.println("==========================="); // 第二种方式读文件
try {
ByteTest.readFromFileByteTwo();
} catch (IOException e) {
e.printStackTrace();
System.out.println("还是读不成功啊!");
}
System.out.println("===========================");
ByteTest.WriteToFile();
} /**
* 第一种方法读文件 通过字节流读取文件中的数据
*
* @throws IOException
*/
public static void readFromFileByByte() throws IOException {
File file = new File("abc.txt");
// 如果文件不存在则创建文件
if (!file.exists()) {
file.createNewFile();
}
InputStream inputStream = new FileInputStream(file);
// 这里定义了数组的长度是1024个字节,如果文件超出这字节,就会溢出,结果就是读不到1024字节以后的东西
byte[] bs = new byte[2048];
// 这里len获得的是文件中内容的长度
int len = inputStream.read(bs);
inputStream.close();
System.out.println(len+new String(bs));
} /**
* 第二种方法读文件 通过字节流读取文件中的数据
*
* @throws IOException
*/
public static void readFromFileByteTwo() throws IOException {
// 注意这里的不同,File.separator是分隔符,这里指明绝对路径,即D盘根目录下的abc.txt文件
File file = new File("d:" + File.separator + "abc.txt");
// 如果文件不存在则创建文件
if (!file.exists()) {
file.createNewFile();
}
InputStream inputStream = new FileInputStream(file);
// 这里也有不同,可以根据文件的大小来声明byte数组的大小,确保能把文件读完
byte[] bs = new byte[(int) file.length()];
// read()方法每次只能读一个byte的内容
inputStream.read(bs);
inputStream.close();
System.out.println(new String(bs));
} public static void WriteToFile() {
File file = new File("D:" + File.separator + "write.txt");
OutputStream outputStream = null;
if (!file.exists()) {
try {
// 如果文件找不到,就new一个
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
try {
// 定义输出流,写入文件的流
outputStream = new FileOutputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
// 定义将要写入文件的数据
String string = "Hell Java, Hello World, 你好,世界!";
// 把string转换成byte型的,并存放在数组中
byte[] bs = string.getBytes();
try {
// 写入bs中的数据到file中
outputStream.write(bs);
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
} // =================到此,文件的写入已经完成了!
// 如果想在文件后面追加内容的话,用下面的方法
OutputStream outToFileEnd = null;
try {
outToFileEnd = new FileOutputStream(file, true);
String string2 = "Here I come!!";
byte[] bs2 = string2.getBytes();
outToFileEnd.write(bs2);
outToFileEnd.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
字符流的读写,同样的对于Byte对象的大小设定非常重要,同时写入的时候并没有识别出换行符:
package com.io.test; import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer; public class CharTest { public static void main(String[] args) throws IOException { File file1 = new File("D:" + File.separator + "test1.txt");
File file2 = new File("D:" + File.separator + "test2.txt");
Writer writer = new FileWriter(file1);
Writer writer1 = new FileWriter(file2, true);
String string = "今天是教师节!";
writer.write(string);
String string2 = "祝愿所有的老师教师节快乐!";
writer1.write(string);
writer1.write(string2);
// 在这一定要记得关闭流
writer.close();
writer1.close(); ReadFromFile();
ReadFromFile2();
} public static void ReadFromFile() throws IOException {
File file = new File("d:" + File.separator + "test1.txt");
Reader reader = new FileReader(file);
char[] cs = new char[1024];
// 上面定义了一个大小为1024的char型数组,如果文件内容过大,程序就会报错,而不是只读到1024的大小
reader.read(cs, 0, (int) file.length());
System.out.println(cs);
reader.close();
} public static void ReadFromFile2() throws IOException {
try {
// 声明一个可变长的stringBuffer对象
StringBuffer sb = new StringBuffer(""); Reader reader = new FileReader("d:" + File.separator + "test1.txt");
// 这里我们用到了字符操作的BufferedReader类
BufferedReader bufferedReader = new BufferedReader(reader);
String string = null;
// 按行读取,结束的判断是是否为null,按字节或者字符读取时结束的标志是-1
while ((string = bufferedReader.readLine()) != null) {
// 这里我们用到了StringBuffer的append方法,这个比string的“+”要高效
sb.append(string + "/n");
System.out.println(string);
}
// 注意这两个关闭的顺序
bufferedReader.close();
reader.close(); /*
* 完整写入文件
*/
Writer writer = new FileWriter("d:" + File.separator + "test3.txt");
BufferedWriter bw = new BufferedWriter(writer);
// 注意这里调用了toString方法
bw.write(sb.toString());
// 注意这两个关闭的顺序
bw.close();
writer.close(); } catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
} }
2.3、File类
在整个io包中,唯一表示与文件本身有关的类就是File类。使用File类可以进行创建或删除文件以及文件夹等常用操作。
要想使用File类,则首先要观察File类的构造方法,此类的常用构造方法如下所示:
public File(String pathname):
实例化File类的时候,必须设置好路径。直接根据路径找到文件。
File类中的主要方法和常量:
1、public static final String pathSeparator 常量 表示路径的分隔符(windows是:";")
2、public static final String separator 常量 表示路径的分隔符(windows是:"\")
3、public File(String pathname) 构造方法 创建File类对象,传入完整路径
4、public boolean createNewFile() throws IOException 普通方法 创建新文件
5、public boolean delete() 普通方法 删除文件
6、public boolean exists() 普通方法 判断文件是否存在
7、public boolean isDirectory() 判断给定的路径是否是一个目录
8、public long length() 普通方法 返回文件的大小
9、public String[] list() 普通方法 列出指定目录的全部内容,只是列出了名称
10、public File[] listFiles() 普通方法 列出指定目录的全部内容,会列出路径
11、public boolean mkdir() 普通方法 创建一个目录
12、public boolean removeTo(File dest)为已有的文件重命名
2.4、RandomAccessFile类
该对象并不是流体系中的一员,其封装了字节流,同时还封装了一个缓冲区(字符数组),通过内部的指针来操作字符数组中的数据。该对象特点:
1、 该对象只能操作文件,所以构造函数接收两种类型的参数:a.字符串文件路径;b.File对象。
2、 该对象既可以对文件进行读操作,也能进行写操作,在进行对象实例化时可指定操作模式(r,rw)
注意:该对象在实例化时,如果要操作的文件不存在,会自动创建;如果文件存在,写数据未指定位置,会从头开始写,即覆盖原有的内容。可以用于多线程下载或多个线程同时写数据到文件。
此类的实例支持对随机访问文件的读取和写入。随机访问文件的行为类似存储在文件系统中的一个大型 byte 数组。存在指向该隐含数组的光标或索引,称为文件指针;输入操作从文件指针开始读取字节,并随着对字节的读取而前移此文件指针。如果随机访问文件以读取/写入模式创建,则输出操作也可用;输出操作从文件指针开始写入字节,并随着对字节的写入而前移此文件指针。写入隐含数组的当前末尾之后的输出操作导致该数组扩展。该文件指针(实现数组随机读写)可以通过 getFilePointer 方法读取,并通过 seek 方法设置。通常,如果此类中的所有读取例程在读取所需数量的字节之前已到达文件末尾,则抛出 EOFException(是一种 IOException)。如果由于某些原因无法读取任何字节,而不是在读取所需数量的字节之前已到达文件末尾,则抛出 IOException,而不是 EOFException。需要特别指出的是,如果流已被关闭,则可能抛出 IOException。
RandomAccessFile类包含了一个记录指针,用以标识当前读写处的位置,当程序新创建一个RandomAccessFile对象时,该对象的文件记录指针位于文件头(也就是0处),当读/写了n个字节后,文件记录指针将会向后移动n个字节。除此之外,RandomAccessFile可以*的移动记录指针,即可以向前移动,也可以向后移动。RandomAccessFile包含了以下两个方法来操作文件的记录指针.
long getFilePointer(); 返回文件记录指针的当前位置
void seek(long pos); 将文件记录指针定位到pos位置
RandomAccessFile即可以读文件,也可以写,所以它即包含了完全类似于InputStream的3个read()方法,其用法和InputStream的3个read()方法完全一样;也包含了完全类似于OutputStream的3个write()方法,其用法和OutputStream的3个Writer()方法完全一样。除此之外,RandomAccessFile还包含了一系类的readXXX()和writeXXX()方法来完成输入和输出。
RandomAccessFile有两个构造器,其实这两个构造器基本相同,只是指定文件的形式不同而已,一个使用String参数来指定文件名,一个使用File参数来指定文件本身。除此之外,创建RandomAccessFile对象还需要指定一个mode参数。该参数指定RandomAccessFile的访问模式,有以下4个值:
“r” 以只读方式来打开指定文件夹。如果试图对该RandomAccessFile执行写入方法,都将抛出IOException异常。
“rw” 以读,写方式打开指定文件。如果该文件尚不存在,则试图创建该文件。
“rws” 以读,写方式打开指定文件。相对于”rw” 模式,还要求对文件内容或元数据的每个更新都同步写入到底层设备。
“rwd” 以读,写方式打开指定文件。相对于”rw” 模式,还要求对文件内容每个更新都同步写入到底层设备。
三、java文件IO概览
3.1、数据流
Java中I/O操作主要是指使用Java进行输入,输出操作,Java所有的I/O机制都是基于数据流进行输入输出,这些数据流表示了字符或者字节数据的流动序列。
数据流是一串连续不断的数据的集合,就象水管里的水流,在水管的一端一点一点地供水,而在水管的另一端看到的是一股连续不断的水流。数据写入程序可以是一段、一段地向数据流管道中写入数据,这些数据段会按先后顺序形成一个长的数据流。对数据读取程序来说,看不到数据流在写入时的分段情况,每次可以读取其中的任意长度的数据,但只能先读取前面的数据后,再读取后面的数据(不能随机读取)。不管写入时是将数据分多次写入,还是作为一个整体一次写入,读取时的效果都是完全一样的。流序列中的数据既可以是未经加工的原始二进制数据,也可以是经一定编码处理后符合某种格式规定的特定数据。因此Java中的流分为两种: 1) 字节流:数据流中最小的数据单元是字节 2) 字符流:数据流中最小的数据单元是字符, Java中的字符是Unicode编码,一个字符占用两个字节。
简而言之:数据流是一组有序,有起点和终点的字节的数据序列。包括输入流和输出流。
当程序需要读取数据的时候,就会建立一个通向数据源的连接,这个数据源可以是文件,内存,或是网络连接。类似的,当程序需要写入数据的时候,就会建立一个通向目的地的连接。
3.2、java的IO框架
Java.io包中最重要的就是5个类和一个接口。5个类指的是File、OutputStream、InputStream、Writer、Reader;一个接口指的是Serializable。掌握了这些就掌握了Java I/O的精髓了。
Java I/O主要包括如下3层次:
流式部分——最主要的部分。如:OutputStream、InputStream、Writer、Reader等
非流式部分——如:File类、RandomAccessFile类和FileDescriptor等类
其他——文件读取部分的与安全相关的类,如:SerializablePermission类,以及与本地操作系统相关的文件系统的类,如:FileSystem类和Win32FileSystem类和WinNTFileSystem类。
I/O流:java.io包里有4个基本类:InputStream、OutputStream及Reader、Writer类,它们分别处理字节流和字符流。其他各种各样的流都是由这4个派生出来的。
按来源/去向分类:
File(文件): FileInputStream, FileOutputStream, FileReader, FileWriter
byte[]:ByteArrayInputStream, ByteArrayOutputStream
Char[]: CharArrayReader, CharArrayWriter
String: StringBufferInputStream, StringReader, StringWriter 网络数据流:InputStream, OutputStream, Reader, Writer
InputStream:InputStream 为字节输入流,它本身为一个抽象类,必须依靠其子类实现各种功能,此抽象类是表示字节输入流的所有类的超类。 继承自InputStream 的流都是向程序中输入数据的,且数据单位为字节(8bit);
InputStream是输入字节数据用的类,所以InputStream类提供了3种重载的read方法,Inputstream类中的常用方法:
public abstract int read( ):读取一个byte的数据,返回值是高位补0的int类型值。若返回值=-1说明没有读取到任何字节读取工作结束。
public int read(byte b[ ]):读取b.length个字节的数据放到b数组中。返回值是读取的字节数。该方法实际上是调用下一个方法实现的
public int read(byte b[ ], int off, int len):从输入流中最多读取len个字节的数据,存放到偏移量为off的b数组中。
public int available( ):返回输入流中可以读取的字节数。注意:若输入阻塞,当前线程将被挂起,如果InputStream对象调用这个方法的话,它只会返回0,这个方法必须由继承InputStream类的子类对象调用才有用。
public long skip(long n):忽略输入流中的n个字节,返回值是实际忽略的字节数, 跳过一些字节来读取。
public int close( ) :使用完后,必须对我们打开的流进行关闭。 来看看几种不同的InputStream:
FileInputStream把一个文件作为InputStream,实现对文件的读取操作
ByteArrayInputStream:把内存中的一个缓冲区作为InputStream使用
StringBufferInputStream:把一个String对象作为InputStream
PipedInputStream:实现了pipe的概念,主要在线程中使用
SequenceInputStream:把多个InputStream合并为一个InputStream OutputStream
OutputStream提供了3个write方法来做数据的输出,这个是和InputStream是相对应的。
public void write(byte b[ ]):将参数b中的字节写到输出流。
public void write(byte b[ ], int off, int len) :将参数b的从偏移量off开始的len个字节写到输出流。
public abstract void write(int b) :先将int转换为byte类型,把低字节写入到输出流中。
public void flush( ) : 将数据缓冲区中数据全部输出,并清空缓冲区。
public void close( ) : 关闭输出流并释放与流相关的系统资源。 几种不同的OutputStream:
ByteArrayOutputStream:把信息存入内存中的一个缓冲区中
FileOutputStream:把信息存入文件中
PipedOutputStream:实现了pipe的概念,主要在线程中使用
SequenceOutputStream:把多个OutStream合并为一个OutStream
Reader和InputStream类似;Writer和OutputStream类似。
有两个需要注意的:
InputStreamReader : 从输入流读取字节,在将它们转换成字符。
BufferedReader :接受Reader对象作为参数,并对其添加字符缓冲器,使用readline()方法可以读取一行。 如何选择I/O流
确定是输入还是输出
输入:输入流 InputStream,Reader
输出:输出流 OutputStream,Writer 明确操作的数据对象是否是纯文本
是:字符流 Reader,Writer
否:字节流 InputStream,OutputStream 明确具体的设备。
文件:
读:FileInputStream,, FileReader,
写:FileOutputStream,FileWriter
数组:
byte[ ]:ByteArrayInputStream, ByteArrayOutputStream
char[ ]:CharArrayReader, CharArrayWriter String:
StringBufferInputStream(已过时,因为其只能用于String的每个字符都是8位的字符串), StringReader, StringWriter
Socket流
键盘:用System.in(是一个InputStream对象)读取,用System.out(是一个OutputStream对象)打印
是否需要转换流
是,就使用转换流,从Stream转化为Reader、Writer:InputStreamReader,OutputStreamWriter
是否需要缓冲提高效率
是就加上Buffered:BufferedInputStream, BufferedOuputStream, BufferedReader, BufferedWriter
是否需要格式化输出
下面再看一个例子:
package com.io.test; import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Scanner; public class IOTest { public static void main(String[] args) throws Exception{
System.out.println("将标准输入(键盘输入)显示到标准输出(显示器),支持字符:");
inputAndOutput(); System.out.println("将IOTest.java的内容打印到显示器1:");
inputFileAndShow1(); System.out.println("将IOTest.java的内容打印到显示器2:");
inputFileAndShow2(); System.out.println("将文件A的内容拷贝到文件B:");
copyAToB(); System.out.println("将标准输入的内容写入文件:");
systemToFile();
} //将标准输入(键盘输入)显示到标准输出(显示器),支持字符。
public static void inputAndOutput(){
char ch;
BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); //将字节流转为字符流,带缓冲
try {
while ((ch = (char) in.read()) != -1 && ch != 'q'){
System.out.print(ch);
}
} catch (IOException e) {
e.printStackTrace();
}
} //将IOTest.java的内容打印到显示器
public static void inputFileAndShow1() throws IOException{
BufferedReader in = new BufferedReader(new FileReader("IOTest.java"));
String s;
try {
while ((s = in.readLine()) != null){
System.out.println(s);
}
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void inputFileAndShow2() throws FileNotFoundException{
FileReader in = new FileReader("IOTest.java");
int b;
try {
while ((b = in.read()) != -1){
System.out.print((char)b);
}
in.close();
} catch (IOException e) {
e.printStackTrace();
}
} //将文件A的内容拷贝到文件B
public static void copyAToB() throws IOException{
FileInputStream in = new FileInputStream("IOTest.java");
FileOutputStream out = new FileOutputStream("copy.txt");
int b;
while ((b = in.read()) != -1){
out.write(b);
}
out.flush();
in.close();
out.close();
} //将标准输入的内容写入文件
public static void systemToFile() throws IOException{
Scanner in = new Scanner(System.in);
FileWriter out = new FileWriter("systemIn.log");
String s;
while (!(s = in.nextLine()).equals("Q")){
out.write(s + "\n");
}
out.flush();
out.close();
in.close();
} }
四、总结
对于文件的操作,本质上就是对内存数据的读写并且解析和表示的问题,必不可少的会牵扯到管道,缓存等机制,在java的IO之中使用了装饰器模式非常的优美和简洁,值得深入思考。
参考文献: https://www.cnblogs.com/jmwm/p/6931503.html
http://www.cnblogs.com/lovesqcc/p/5201929.html
https://blog.csdn.net/Named_BaoYong/article/details/74626789