Java IO学习总结笔记-总结

时间:2023-02-24 12:29:24

Java IO 总结笔记
Java的 IO 大致可以分成
对字节流的输入输出,
对字符流的输入输出。
InputStream和OutputStream主要用于字节流byte的输入输出,二进制文件。
Reader 和 Writer 主要用于字符流的输入输出,文本文件。
流与Reader和Writer在结束使用的时候,需要正确地关闭它们。通过调用close()方法可以达到这一点。InputStream的关闭代码放到了finally块中,无论在try-catch块中发生了什么,finally内的代码始终会被执行,所以这个InputStream总是会被关闭。
Java IO中也就基本流,文件流和缓冲流,其他的都不常用。重点关注 基本流,文件流和缓冲流。

========================================
Java 流
InputStream
java.io.InputStream类是所有Java IO输入流的基类。InputStream子类包括FileInputStream,BufferedInputStream,PushbackInputStream等等。
通常使用输入流中的read()方法从InputStream流内读取数据。read()方法返回一个整数,代表了读取到的字节的内容(0 ~ 255)。
当达到流末尾没有更多数据可以读取的时候,read()方法返回-1。
read()
read()方法返回从InputStream流内读取到的一个字节内容(0~255)
int data = inputstream.read();

你可以把返回的int类型转化成char类型:
流末尾
如果read()方法返回-1,意味着程序已经读到了流的末尾,此时流内已经没有多余的数据可供读取了。-1是一个int类型,不是byte或者char类型,这是不一样的。当达到流末尾时,你就可以关闭流了。

read(byte[])

InputStream包含了2个从InputStream中读取数据并将数据存储到缓冲数组中的read()方法,他们分别是:

int read(byte[])
int read(byte, int offset, int length)

一次性读取一个字节数组的方式,比一次性读取一个字节的方式快的多,所以,尽可能使用这两个方法代替read()方法。
read(byte[])方法会尝试读取与给定字节数组容量一样大的字节数,返回值说明了已经读取过的字节数。如果InputStream内可读的数据不足以填满字节数组,那么数组剩余的部分将包含本次读取之前的数据。记得检查有多少数据实际被写入到了字节数组中。
read(byte, int offset, int length)方法同样将数据读取到字节数组中,不同的是,该方法从数组的offset位置开始,并且最多将length个字节写入到数组中。同样地,read(byte, int offset, int length)方法返回一个int变量,告诉你已经有多少字节已经被写入到字节数组中,所以请记得在读取数据前检查上一次调用read(byte, int offset, int length)的返回值。
这两个方法都会在读取到达到流末尾时返回-1。

FileInputStream
FileInputStream可以以字节流的形式读取文件内容。FileInputStream是InputStream的子类。这意味着你可以把FileInputStream当做InputStream使用(FileInputStream与InputStream的行为类似)。

InputStream 和 FileInputStream 组合使用
例子

InputStream inputstream = new FileInputStream("c:\\data\\input-text.txt");
int data = inputstream.read();
while(data != -1) { 
    //do something with data... 
    doSomethingWithData(data);   
    data = inputstream.read();
}
inputstream.close();

===========================
OutputStream
java.io.OutputStream是Java IO中所有输出流的基类。子类包括BufferedOutputStream,FileOutputStream等等。
你可以将流整合起来以便实现更高级的输入和输出操作。比如,一次读取一个字节是很慢的,所以可以从磁盘中一次读取一大块数据,然后从读到的数据块中获取字节。为了实现缓冲,可以把InputStream包装到BufferedInputStream中。代码示例:

InputStream input = new BufferedInputStream(new FileInputStream("c:\\data\\input-file.txt"));

缓冲同样可以应用到OutputStream中。你可以实现将大块数据批量地写入到磁盘(或者相应的流)中,这个功能由BufferedOutputStream实现。

write(byte[])
OutputStream同样包含了将字节数据中全部或者部分数据写入到输出流中的方法,分别是write(byte[])和write(byte[], int offset, int length)。
write(byte[])把字节数组中所有数据写入到输出流中。

write(byte[], int offset, int length)

把字节数据中从offset位置开始,length个字节的数据写入到输出流。
flush()
OutputStream的flush()方法将所有写入到OutputStream的数据冲刷到相应的目标媒介中。比如,如果输出流是FileOutputStream,那么写入到其中的数据可能并没有真正写入到磁盘中。即使所有数据都写入到了FileOutputStream,这些数据还是有可能保留在内存的缓冲区中。通过调用flush()方法,可以把缓冲区内的数据刷新到磁盘(或者网络,以及其他任何形式的目标媒介)中。
close()
当你结束数据写入时,需要关闭OutputStream。通过调用close()可以达到这一点。因为OutputStream的各种write()方法可能会抛出IO异常,所以你需要把调用close()的关闭操作方在finally块中执行。
FileOutputStream可以往文件里写入字节流,它是OutputStream的子类,所以你可以像使用OutputStream那样使用FileOutputStream。
OutputStream 和 FileOutputStream 组合使用
例子

OutputStream output = new FileOutputStream("c:\\data\\output-text.txt");
while(hasMoreData()) {
    int data = getMoreData();
    output.write(data);
}
output.close();

文件内容的覆盖Override VS追加Appending
当你创建了一个指向已存在文件的FileOutputStream,你可以选择覆盖整个文件,或者在文件末尾追加内容。通过使用不同的构造函数可以实现不同的目的。
其中一个构造函数取文件名作为参数,会覆盖任何此文件名指向的文件。
OutputStream output = new FileOutputStream(“c:\data\output-text.txt”);
另外一个构造函数取2个参数:文件名和一个布尔值,布尔值表明你是否需要覆盖文件。这是构造函数的例子:

OutputStream output = new FileOutputStream("c:\\data\\output-text.txt", true); //appends to file
OutputStream output = new FileOutputStream("c:\\data\\output-text.txt", false); //overwrites file

flush()
当你往FileOutputStream里写数据的时候,这些数据有可能会缓存在内存中。在之后的某个时间,比如,
每次都只有X份数据可写,或者FileOutputStream关闭的时候,才会真正地写入磁盘。
当FileOutputStream没被关闭,而你又想确保写入到FileOutputStream中的数据写入到磁盘中,可以调用flush()方法,
该方法可以保证所有写入到FileOutputStream的数据全部写入到磁盘中。

========================

Java IO: 字节流Buffered

本小节会简要概括Java IO中Buffered的输入输出流,主要涉及以下2个类型的流:BufferedInputStream,BufferedOutputStream
功能:提供缓存,提升速度
BufferedInputStream
BufferedInputStream能为输入流提供缓冲区,能提高很多IO的速度。你可以一次读取一大块的数据,而不需要每次从网络或者磁盘中一次读取一个字节。特别是在访问大量磁盘数据时,缓冲通常会让IO快上许多。
为了给你的输入流加上缓冲,你需要把输入流包装到BufferedInputStream中,代码如下:
InputStream input = new BufferedInputStream(new FileInputStream(“c:\data\input-file.txt”));
同时可以给BufferedInputStream的构造函数传递一个值,设置内部使用的缓冲区设置大小(译者注:默认缓冲区大小8 * 1024B),就像这样:

InputStream input = new BufferedInputStream(new FileInputStream("c:\\data\\input-file.txt"), 8 * 1024);

这个例子设置了8KB的缓冲区。最好把缓冲区大小设置成1024字节的整数倍,这样能更高效地利用内置缓冲区的磁盘。
除了能够为输入流提供缓冲区以外,其余方面BufferedInputStream基本与InputStream类似。
BufferedOutputStream
与BufferedInputStream类似,BufferedOutputStream可以为输出流提供缓冲区。可以构造一个使用默认大小缓冲区的BufferedOutputStream(译者注:默认缓冲区大小8 * 1024B),代码如下:

OutputStream output = new BufferedOutputStream(new FileOutputStream("c:\\data\\output-file.txt"));

也可以手动设置缓冲区大小,代码如下:

OutputStream output = new BufferedOutputStream(new FileOutputStream("c:\\data\\output-file.txt"), 8 * 1024);

为了更好地使用内置缓冲区的磁盘,同样建议把缓冲区大小设置成1024的整数倍。
除了能够为输出流提供缓冲区以外,其余方面BufferedOutputStream基本与OutputStream类似。唯一不同的时,你需要手动flush()方法确保写入到此输出流的数据真正写入到磁盘或者网络中。

========================

Java IO: 字节流
通过Java IO创建管道
可以通过Java IO中的PipedOutputStream和PipedInputStream创建管道。一个PipedInputStream流应该和一个PipedOutputStream流相关联。
一个线程通过PipedOutputStream写入的数据可以被另一个线程通过相关联的PipedInputStream读取出来。
管道和线程
请记得,当使用两个相关联的管道流时,务必将它们分配给不同的线程。read()方法和write()方法调用时会导致流阻塞,这意味着如果你尝试在一个线程中同时进行读和写,可能会导致线程死锁。
管道的替代
除了管道之外,一个JVM中不同线程之间还有许多通信的方式。实际上,线程在大多数情况下会传递完整的对象信息而非原始的字节数据。但是,如果你需要在线程之间传递字节数据,Java IO的管道是一个不错的选择。

========================

Java IO: Reader和Writer

Reader
Reader是Java IO中所有Reader的基类。Reader与InputStream类似,不同点在于,Reader基于字符而非基于字节。换句话说,Reader用于读取文本,而InputStream用于读取原始字节。
请记住,Java内部使用UTF8编码表示字符串。输入流中一个字节可能并不等同于一个UTF8字符。如果你从输入流中以字节为单位读取UTF8编码的文本,并且尝试将读取到的字节转换成字符,你可能会得不到预期的结果。
read()方法返回一个包含了读取到的字符内容的int类型变量(0~65535)。如果方法返回-1,表明Reader中已经没有剩余可读取字符,此时可以关闭Reader。-1是一个int类型,不是byte或者char类型,这是不一样的。
你通常会使用Reader的子类,而不会直接使用Reader。Reader的子类包括InputStreamReader,CharArrayReader,FileReader等等。
Writer
Writer是Java IO中所有Writer的基类。与Reader和InputStream的关系类似,Writer基于字符而非基于字节,Writer用于写入文本,OutputStream用于写入字节。
常用子类包括OutputStreamWriter,CharArrayWriter,FileWriter等。

Java IO: InputStreamReader和OutputStreamWriter

对于InputStreamReader和OutputStreamWriter。IO中的类要么以Stream结尾,要么以Reader或者Writer结尾,那这两个同时以字节流和字符流的类名后缀结尾的类是什么用途呢?
简单来说,这两个类把字节流转换成字符流,中间做了数据的转换,类似适配器模式的思想。

InputStreamReader

InputStreamReader会包含一个InputStream,从而可以将该输入字节流转换成字符流,代码例子:

InputStream inputStream = new FileInputStream("c:\\data\\input.txt");
Reader reader = new InputStreamReader(inputStream);
int data = reader.read();
while(data != -1){
    char theChar = (char) data;
    data = reader.read();
}
reader.close();

int data = reader.read();
你可以把返回的int值转换成char变量,就像这样:
InputStreamReader同样拥有其他可选的构造函数,能够让你指定将底层字节流解释成何种编码的字符流。例子如下:

InputStream inputStream = new FileInputStream("c:\\data\\input.txt");
Reader reader = new InputStreamReader(inputStream, "UTF-8");

注意构造函数的第二个参数,此时该InputStreamReader会将输入的字节流转换成UTF8字符流。

OutputStreamWriter
OutputStreamWriter会包含一个OutputStream,从而可以将该输出字节流转换成字符流,代码如下:

OutputStream outputStream = new FileOutputStream("c:\\data\\output.txt");
Writer writer = new OutputStreamWriter(outputStream);
writer.write("Hello World");
writer.close();

OutputStreamWriter同样拥有将输出字节流转换成指定编码的字符流的构造函数。

Java IO: FileReader和FileWriter
介绍FileReader和FileWriter。与FileInputStream和FileOutputStream类似,FileReader与FileWriter用于处理文件内容。
FileReader
FileReader能够以字符流的形式读取文件内容。除了读取的单位不同之外(FileReader读取字符,FileInputStream读取字节),FileReader与FileInputStream并无太大差异,
也就是说,FileReader用于读取文本。根据不同的编码方案,一个字符可能会相当于一个或者多个字节。代码如下:

Reader reader = new FileReader("c:\\data\\input-text.txt");
int data = reader.read();
while(data != -1) {
    //do something with data...
    doSomethingWithData(data);
    data = reader.read();
}
reader.close();

read()方法返回一个包含了读取到的字符内容的int类型变量(0~65535)。如果方法返回-1,表明FileReader中已经没有剩余可读取字符,此时可以关闭FileReader。
-1是一个int类型,不是byte或者char类型,这是不一样的。
FileWriter
FileWriter能够把数据以字符流的形式写入文件。同样是处理文件,FileWriter处理字符,FileOutputStream处理字节。根据不同的编码方案,一个字符可能会相当于一个或者多个字节。代码如下:

Writer writer = new FileWriter("c:\\data\\output.txt");
while(moreData) {
    String data = getMoreData();
    write.write(data);
}
writer.close();

处理文件都会碰到的一个问题是,当前写入的数据是覆盖原文件内容还是追加到文件末尾。当你创建一个FileWriter之后,你可以通过使用不同构造函数实现你的不同目的。

以下的构造函数取文件名作为参数,将会新写入的内容将会覆盖该文件:
Writer writer = new FileWriter(“c:\data\output.txt”);
以下的构造函数取文件名和一个布尔变量作为参数,布尔值表明你是想追加还是覆盖该文件。例子如下:

Writer writer = new FileWriter("c:\\data\\output.txt", true); //appends to file
Writer writer = new FileWriter("c:\\data\\output.txt", false); //overwrites file

Java IO: 字符流的Buffered和Filter
介绍缓冲与过滤相关的reader和writer,主要涉及BufferedReader、BufferedWriter、FilterReader、FilterWriter。
BufferedReader
BufferedReader能为字符输入流提供缓冲区,可以提高许多IO处理的速度。你可以一次读取一大块的数据,而不需要每次从网络或者磁盘中一次读取一个或多个字符。特别是在访问大量磁盘数据时,缓冲通常会让IO快上许多。
BufferedReader和BufferedInputStream的主要区别在于,BufferedReader操作字符,而BufferedInputStream操作原始字节。只需要把Reader包装到BufferedReader中,就可以为Reader添加缓冲区(译者注:默认缓冲区大小为8192字节,即8KB)。代码如下:
Reader input = new BufferedReader(new FileReader(“c:\data\input-file.txt”));
你也可以通过传递构造函数的第二个参数,指定缓冲区大小,代码如下:
Reader input = new BufferedReader(new FileReader(“c:\data\input-file.txt”), 8 * 1024);
这个例子设置了8KB的缓冲区。最好把缓冲区大小设置成1024字节的整数倍,这样能更高效地利用内置缓冲区的磁盘。
除了能够为输入流提供缓冲区以外,其余方面BufferedReader基本与Reader类似。BufferedReader还有一个额外readLine()方法,可以方便地一次性读取一整行字符。
BufferedWriter
与BufferedReader类似,BufferedWriter可以为输出流提供缓冲区。可以构造一个使用默认大小缓冲区的BufferedWriter(译者注:默认缓冲区大小8 * 1024B),代码如下:

Writer writer = new BufferedWriter(new FileWriter("c:\\data\\output-file.txt"));

也可以手动设置缓冲区大小,代码如下:

Writer writer = new BufferedWriter(new FileWriter("c:\\data\\output-file.txt"), 8 * 1024);

为了更好地使用内置缓冲区的磁盘,同样建议把缓冲区大小设置成1024的整数倍。除了能够为输出流提供缓冲区以外,其余方面BufferedWriter基本与Writer类似。类似地,BufferedWriter也提供了writeLine()方法,能够把一行字符写入到底层的字符输出流中。值得注意是,你需要手动flush()方法确保写入到此输出流的数据真正写入到磁盘或者网络中。
FilterReader
与FilterInputStream类似,FilterReader是实现自定义过滤输入字符流的基类,基本上它仅仅只是简单覆盖了Reader中的所有方法。
就我自己而言,我没发现这个类明显的用途。除了构造函数取一个Reader变量作为参数之外,我没看到FilterReader任何对Reader新增或者修改的地方。如果你选择继承FilterReader实现自定义的类,同样也可以直接继承自Reader从而避免额外的类层级结构。
FilterWriter
内容同FilterReader,不再赘述。

Java IO: 字符流的Piped和CharArray
介绍管道与字符数组相关的reader和writer,主要涉及PipedReader、PipedWriter
PipedReader
PipedReader能够从管道中读取字符流。与PipedInputStream类似,不同的是PipedReader读取的是字符而非字节。换句话说,PipedReader用于读取管道中的文本。代码如下:

Reader reader = new PipedReader(pipedWriter);
int data = reader.read();
while(data != -1) {
    //do something with data...
    doSomethingWithData(data);
    data = reader.read();
}
reader.close();

read()方法返回一个包含了读取到的字符内容的int类型变量(0~65535)。如果方法返回-1,表明PipedReader中已经没有剩余可读取字符,此时可以关闭PipedReader。-1是一个int类型,不是byte或者char类型,这是不一样的。
正如你所看到的例子那样,一个PipedReader需要与一个PipedWriter相关联,当这两种流联系起来时,就形成了一条管道。要想更多地了解Java IO中的管道,请参考Java IO管道。
PipedWriter
PipedWriter能够往管道中写入字符流。与PipedOutputStream类似,不同的是PipedWriter处理的是字符而非字节,PipedWriter用于写入文本数据。代码如下:

PipedWriter writer = new PipedWriter(pipedReader);
while(moreData()) {
    int data = getMoreData();
    writer.write(data);

}
writer.close();

PipedWriter的write()方法取一个包含了待写入字节的int类型变量作为参数进行写入,同时也有采用字符串、字符数组作为参数的write()方法。

===========================

Java IO: File
Java IO API中的FIle类可以让你访问底层文件系统,通过File类,你可以做到以下几点:
检测文件是否存在

File file = new File("c:\\data\\input-file.txt");
boolean fileExists = file.exists();

通过调用exists()方法,可以检测文件是否存在
读取文件长度

File file = new File("c:\\data\\input-file.txt");
long length = file.length();
  通过调用length()可以获得文件的字节长度

重命名或移动文件

File file = new File("c:\\data\\input-file.txt");
boolean success = file.renameTo(new File("c:\\data\\new-file.txt"));

删除文件

File file = new File("c:\\data");   
boolean success = file.delete();

通过调用delete()方法可以删除文件
检测某个路径是文件还是目录

File file = new File("c:\\data"); 
boolean isDirectory = file.isDirectory();

读取目录中的文件列表

File file = new File("c:\\data");
String[] fileNames = file.list();
File[] files = file.listFiles();

请注意:File只能访问文件以及文件系统的元数据。如果你想读写文件内容,
需要使用FileInputStream、FileOutputStream或者RandomAccessFile。
如果你正在使用Java NIO,并且想使用完整的NIO解决方案,你会使用到java.nio.FileChannel(否则你也可以使用File)。