欢迎来到Introzo百科
Introzo百科
【第203期】彻底理解Java IO:字节流、字符流、缓冲流
IO流是Java的重要组成部分,也是我们经常打交道的。这篇关于 Java IO 的博文充满了有用的信息,在互联网上排名前三(请温柔!)
如果你能流利地回答下面的问题(问题会持续补充),那么恭喜你,你已经掌握了IO知识,可以立即关闭文章了。否则,您可以在下面的文章中找到答案。
Java IO流有什么特点?
Java IO 流有多少种类型?
字节流和字符流有什么关系和区别?
字符流是否使用缓冲?
缓冲流的效率一定高吗?为什么?
缓冲流体现了Java中的哪种设计模式思想?
为什么需要实现序列化?如何实现序列化?
序列化数据后,如果再次修改class文件,读取数据就会出现问题。怎么解决呢?
IO,即in和out,即输入和输出,是指应用程序与外部设备之间的数据传输。常见的外部设备包括文件、管道和网络连接。
Java通过流来处理IO,那么什么是流呢?
流是一个抽象概念,指一系列数据(字符或字节),是以先进先出方式发送信息的通道。
当程序需要读取数据时,它会打开一个流到数据源。该数据源可以是文件、内存或网络连接。同样,当程序需要写入数据时,它会打开一个流到目的地。这时,你可以想象数据似乎在里面“流动”。
一般来说,流的特点有以下几点:
先进先出:先写入输出流的数据先从输入流中读取。
顺序访问:可以将一串字节依次写入流中。读取时,也会按照写入的顺序读取一串字节。中间的数据不能随机访问。 (随机访问文件除外)
只读或只写:每个流只能是输入流或输出流,不能同时具有这两种功能。输入流只能读取,输出流只能写入。在一个数据传输通道中,如果要写入数据和读取数据,则需要分别提供两个流。
1.1 IO流分类
IO流的分类方法主要有以下三种:
按数据流向:输入流、输出流
按处理数据单元分:字节流、字符流
按功能:节点流程、处理流程
1. 输入流和输出流
输入和输出是与应用程序相关的,例如文件的读取和写入。读文件是输入流,写文件是输出流。这很容易逆转。
2、字节流和字符流字节流和字符流的用法几乎完全相同。区别在于字节流和字符流操作的数据单元不同。字节流操作的单位是数据单位是8位字节,而字符流操作的是数据。单位是16位字符。
为什么我们需要字符流?
Java中的字符采用Unicode标准。在Unicode编码中,1个英文字符为1个字节,1个汉字为2个字节。在UTF-8编码中,一个汉字占3个字节。例如下图中,“云神之子”5个汉字对应15个字节:-28-70-111-26-73-79-28-72-115-25-97-91-27 -92 -124
那么问题来了。如果使用字节流处理中文,每次读写一个字符对应的字节数是没有问题的。一旦一个字符对应的字节被分割,就会出现乱码。为了更方便地处理这些汉字,Java引入了字符流。
字节流和字符流的其他区别:
字节流一般用于处理图像、视频、音频、PPT、Word等类型的文件。字符流一般用于处理纯文本文件,例如TXT文件,但不能处理图像、视频等非文本文件。一句话简介:字节流可以处理所有文件,而字符流只能处理纯文本文件。
字节流本身没有缓冲区。与字节流相比,缓冲字节流有非常高的效率提升。字符流本身有缓冲区,有缓冲的字符流相对于字符流来说效率提升并不是那么大。详情请参见文末的效率对比。
以写文件为例,我们查看了字符流的源码,发现确实使用了缓冲区:
3. 节点流程及处理流程
节点流:直接操作数据读写的流类,如FileInputStream
处理流:链接封装现有的流,通过处理数据为程序提供强大灵活的读写功能,如BufferedInputStream(缓冲字节流)
处理流程和节点流程应用了Java的装饰器设计模式。
下图形象地描绘了节点流程和处理流程。处理流程封装了节点流,最终的数据处理由节点流完成。在众多的处理流中,有一个非常重要,那就是缓冲流。
我们知道,程序与磁盘的交互相比内存操作是非常慢的,很容易成为程序的性能瓶颈。减少程序与磁盘的交互是提高程序效率的有效手段。缓冲流应用了这种思想:普通流一次读取和写入一个字节,而缓冲流在内存中设置一个缓冲区。缓冲区首先存储足够的数据以供操作,然后再与内存或磁盘交互。这样在数据总量不变的情况下,通过增加每次交互的数据量来减少交互次数。想想生活中的例子。当我们搬运砖块时,将它们一一装载到车上肯定是非常低效的。我们可以用小推车先把砖块装到小推车上,然后把小推车推到车前,把砖块装到车上。在这个例子中,手推车可以被视为一个缓冲区。小车的存在减少了我们装车的次数,从而提高了效率。需要注意的是,缓冲流效率一定高吗?不必要。在某些情况下,缓冲流效率甚至更低。详情请参见IO流效率对比。
完整的IO分类图如下:
1.2 案例实践
接下来我们看看如何使用Java IO。
文本读写的一个例子,如文章开头提到的,就是写“松下问童子,老师去采药。唯此山中,云深不知处”。写入本地文本,然后从文件中读取内容并输出到控制台。往期面试题:001~180期总结
1.FileInputStream、FileOutputStream(字节流)
字节流方式效率较低,不推荐。
public class IOTest { public static void main(String[] args) throws IOException { File file = new File("D:/test.txt"); }
写(文件); System.out.println(读取(文件)); }
public static void write(File file) throws IOException { OutputStream os = new FileOutputStream(file, true); }
// 要写入的字符串 String string = "松下问童子,说老师去采药,唯此山中,云深不知处。"; // 写入文件 os.write(string.getBytes()); // 关闭流 os.close(); }
public static String read(File file) throws IOException { InputStream in = new FileInputStream(file); }//一次取多少个字节 byte[] bytes = new byte[1024]; //用于接收读取的字节数组 StringBuilder sb = new StringBuilder(); //读取的字节数组的长度,为-1时表示没有数据 int length = 0; // 循环获取数据 while ((length = www.introzo.com(bytes)) != -1) { // 将读取的内容转换为字符串 sb.append( new String(bytes, 0, length)); } // 关闭流 in.close();
返回 sb.toString(); }}
2.BufferedInputStream、BufferedOutputStream(缓冲字节流)
缓冲字节流旨在提高效率。真正的读写操作还是依赖于FileOutputStream和FileInputStream,所以它的构造方法的参数是这两个类的对象也就不足为奇了。
公共类 IOTest {
public static void write(File file) throws IOException { // 缓冲字节流,提高效率 BufferedOutputStream bis = new BufferedOutputStream(new FileOutputStream(file, true));
// 要写的字符串 String string = "松下问童子,说老师去采药,唯此山中,云深不知处。"; // 写入文件 bis.write(string.getBytes()); // 关闭流 bis.close(); }
public static String read(File file) throws IOException { BufferedInputStream fis = new BufferedInputStream(new FileInputStream(file));//一次取多少个字节 byte[] bytes = new byte[1024]; //用于接收读取的字节数组 StringBuilder sb = new StringBuilder(); //读取的字节数组的长度,为-1时表示没有数据 int length = 0; // 循环获取数据 while ((length = www.introzo.com(bytes)) != -1) { // 将读取的内容转换为字符串 sb.append( new String(bytes, 0, length)); } // 关闭流 fis.close();
返回 sb.toString(); }}
3.InputStreamReader、OutputStreamWriter(字符流)
字符流适合读写文本文件。 OutputStreamWriter类实际上是借助FileOutputStream类来实现的,所以它的构造方法就是FileOutputStream的对象。
public class IOTest { public static void write(File file) throws IOException { // OutputStreamWriter 可以显示指定字符集,否则使用默认字符集 OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(file, true), "UTF-8 ”);
// 要写入的字符串 String string = "松下问童子,请老师去采药,唯此山中,云深不知处。"; osw.write(字符串); osw.close(); }public static String read(File file) throws IOException { InputStreamReader isr = new InputStreamReader(new FileInputStream(file), "UTF-8"); } //字符数组:一次读取多少个字符 char[] chars = new char[1024 ]; // 每次读取的字符数组先追加到StringBuilder中 StringBuilder sb = new StringBuilder(); // 读取的字符数组的长度,为-1时表示没有数据 int length; // 循环获取数据 while ((length = www.introzo.com(chars)) != -1) { // 将读取的内容转换为字符串 sb.append(chars, 0, length); } // 关闭流 isr.close();
返回 sb.toString() }}
4.字符流便利类
Java提供了FileWriter和FileReader来简化字符流的读写。 new FileWriter 相当于 new OutputStreamWriter(new FileOutputStream(file, true))
公共类 IOTest { 公共静态 void write(File file) 抛出 IOException { FileWriter fw = new FileWriter(file, true);
// 要写入的字符串 String string = "松下问童子,说老师去采药,唯此山中,云深不知处。"; fw.write(字符串); fw.close(); }public static String read(File file) throws IOException { FileReader fr = new FileReader(file); //一次取多少个字节 char[] chars = new char[1024]; //用于接收读取的字节 Array StringBuilder sb = new StringBuilder(); // 读取的字节数组的长度,为-1时表示没有数据 int length; // 循环获取数据 while ((length = www.introzo.com(chars)) != - 1) { // 将读取的内容转换为字符串 sb.append(chars, 0, length); } // 关闭流 fr.close();
返回 sb.toString(); }}
5.BufferedReader、BufferedWriter(字符缓冲流)
public class IOTest { public static void write(File file) throws IOException { // BufferedWriter fw = new BufferedWriter(new OutputStreamWriter(new // FileOutputStream(file, true), "UTF-8")); // FileWriter 可以大大简化代码 BufferedWriter bw = new BufferedWriter(new FileWriter(file, true));
// 要写入的字符串 String string = "松下问童子,说老师去采药,唯此山中,云深不知处。"; bw.write(字符串); bw.close(); }public static String read(File file) throws IOException { BufferedReader br = new BufferedReader(new FileReader(file)); // 用于接收读取的字节数组 StringBuilder sb = new StringBuilder();
// 按行读取数据 String line; // 循环获取数据 while ((line = br.readLine()) != null) { // 将读取的内容转换为字符串 sb.append(line); } //关闭流 br.close();
返回 sb.toString(); }}
在第一节中,我们对IO有了一个大概的了解,并完成了几个案例,但对IO还缺乏更详细的了解。接下来我们将对Java IO进行详细分解,整理出一个完整的知识体系。
Java提供了40多个类。我们只需要详细了解比较重要的就可以满足日常应用了。
2.1 文件类
File类是用来操作文件的类,但是它不能操作文件中的数据。
公共类文件扩展对象实现可序列化,可比较
File类实现了Serialized和Comparable,表明它支持序列化和排序。
File类的构造方法
File类的常用方法
文件类使用示例
public class FileTest { public static void main(String[] args) throws IOException { File file = new File("C:/Mu/fileTest.txt"); }// 判断文件是否存在 if (!file.exists()) { // 如果不存在则创建 file.createNewFile(); } System.out.println("文件的绝对路径:" + file.getAbsolutePath()); System.out.println("文件大小:" + file.length());
//删除文件 file.delete(); }}
2.2 字节流
InputStream和OutputStream是两个抽象类,它们是字节流的基类。所有具体的字节流实现类都分别继承这两个类。搜索公众号java面试题精选,回复实际项目,送你项目源码+教程
以InputStream为例,它继承Object并实现Closeable
公共抽象类 InputStreamextends Objectimplements Closeable
InputStream 类有许多实现子类。下面列出了一些比较常用的类:我们来详细解释一下上图中的类:
InputStream:InputStream是所有字节输入流的抽象基类。正如前面提到的,抽象类不能被实例化。它们实际上作为模板存在,并为所有实现类定义处理输入流的方法。
FileInputSream:文件输入流,非常重要的字节输入流,用于读取文件。
PipedInputStream:管道式字节输入流,可以实现多线程之间的管道通信。
ByteArrayInputStream:字节数组输入流,从字节数组(byte[])中以字节为单位读取,即将资源文件以字节的形式存储到该类go中的字节数组中。
FilterInputStream:装饰器类。具体的装饰器继承这个类。这些类都是处理类。它们的作用是封装节点类,实现一些特殊的功能。
DataInputStream:数据输入流,用于装饰其他输入流。它的功能是“允许应用程序以与机器无关的方式从底层输入流读取基本的Java数据类型”。
BufferedInputStream:缓冲流,装饰节点流。里面会有一个缓冲区来存储字节。每次缓冲区填满再发送,而不是发送一个字节或两个字节,效率很高。更高。ObjectInputStream:对象输入流,用于提供基本数据或对象的持久存储。通俗地说,就是可以直接传递对象,通常用于反序列化。它也是一个处理流,构造函数的输入参数是InputStream的实例对象。
OutputStream类继承图:
OutputStream类的继承关系与InputStream类似。需要注意的是PrintStream。
往期面试题:001~180期总结
2.3 字符流
与字节流类似,字符流也有两个抽象基类,分别是Reader和Writer。其他字符流实现类都继承这两个类。
以Reader为例,其主要实现子类如下所示: 各类详细说明:
InputStreamReader:字节流到字符流的桥梁(InputStreamReader构造函数的输入参数是FileInputStream的实例对象)。它读取字节并使用指定的字符集将它们解码为字符。它使用的字符集可以通过名称指定,显式给出,或者可以接受平台的默认字符集。
BufferedReader:从字符输入流中读取文本并设置缓冲区以提高效率。 BufferedReader是InputStreamReader的封装。前者的构造函数的输入参数是后者的实例对象。
FileReader:读取字符文件的便利类,new FileReader(File file) 等价于 new InputStreamReader(new FileInputStream(file, true), "UTF-8"),但 FileReader 不能指定字符编码和默认字节缓冲区大小。
PipedReader:管道字符输入流。实现多线程之间的管道通信。
CharArrayReader:从 Char 数组读取数据的媒体流。
StringReader:从String读取数据的媒体流。
Writer和Reader结构类似,方向相反,不再赘述。唯一的区别是 PrintWriter 是 Writer 的子类。
2.4 序列化
待续…
3.1 字节流法
字节输入流InputStream主要方法:
read():从此输入流读取数据字节。
read(byte[] b):从此输入流中读取最多 b.length 个字节的数据到字节数组中。read(byte[] b, int off, int len):从此输入流中读取最多 len 个字节的数据到字节数组中。
close():关闭此输入流并释放与该流关联的所有系统资源。
字节输出流OutputStream主要方法:
write(byte[] b):将指定字节数组中的 b.length 个字节写入此文件输出流。
write(byte[] b, int off, int len):将指定字节数组中从offset开始的len个字节写入此文件输出流。
write(int b):将指定字节写入此文件输出流。
close() :关闭此输入流并释放与该流关联的所有系统资源。
3.2 字符流法
字符输入流Reader主要方法:
read():读取单个字符。
read(char[] cbuf):将字符读入数组。
read(char[] cbuf, int off, int len):将字符读入数组的某一部分。
read(CharBuffer target):尝试将字符读入指定的字符缓冲区。
lush():刷新该流的缓冲区。
close():关闭此流,但首先刷新它。
字符输出流Writer主要方法:
write(char[] cbuf):写入字符数组。
write(char[] cbuf, int off, int len):写入字符数组的某一部分。
write(int c):写入单个字符。
write(String str):写入字符串。
write(String str, int off, int len):写入字符串的某一部分。
lush():刷新该流的缓冲区。
close():关闭此流,但首先刷新它。
另外,字符缓冲流还有两个独特的方法:
BufferedWriter类newLine():写入行分隔符。此方法将自动适应您系统的行分隔符。
BufferedReader 类 readLine():读取文本行。
往期面试题:001~180期总结
4.1 位、字节、字符
字节是一种计量单位,表示数据量。它是计算机信息技术用来衡量存储容量的计量单位。通常,一个字节等于八位。字符是计算机中使用的字母、数字、单词和符号,例如“A”、“B”、“$”、“&”等。
一般来说,英文中一个字母或字符占用一个字节,而一个汉字则用两个字节表示。
字节和字符:
ASCII码中,1个英文字母(不区分大小写)为1个字节,1个汉字为2个字节。
UTF-8编码中,1个英文单词为1个字节,1个中文单词为3个字节。
在Unicode编码中,1个英文单词为1个字节,1个中文单词为2个字节。
符号:英文标点符号为一个字节,中文标点符号为两个字节。例如:英语句号。占用1字节大小,中文句号。占用2字节大小。
在UTF-16编码中,每个英文字母字符或一个汉字需要2个字节来存储(Unicode扩展区的一些汉字需要4个字节来存储)。
在UTF-32编码中,世界上任何字符的存储都需要4个字节。
4.2 IO流效率对比
首先对比一下普通字节流和缓冲字节流的效率:
public class MyTest { public static void main(String[] args) throws IOException { File file = new File("C:/Mu/test.txt"); } StringBuilder sb = new StringBuilder();
for (int i = 0; i < 3000000; i++) { sb.append("abcdefghigklmnopqrstuvwsyz"); } byte[] bytes = sb.toString().getBytes();
长开始 = System.currentTimeMillis();写入(文件,字节);长结束= System.currentTimeMillis();
长start2 = System.currentTimeMillis(); bufferedWrite(文件,字节);长end2 = System.currentTimeMillis();System.out.println("普通字节流耗时:" + (end - start) + " ms"); System.out.println("缓冲字节流需要时间:" + (end2 - start2) + " ms");
}
// 普通字节流 public static void write(File file, byte[] bytes) throws IOException { OutputStream os = new FileOutputStream(file); os.write(字节); os.close(); }
// 缓冲字节流 public static void bufferedWrite(File file, byte[] bytes) throws IOException { BufferedOutputStream bo = new BufferedOutputStream(new FileOutputStream(file)); bo.write(字节); bo.close(); }}
运行结果:
普通字节流需要250 ms。缓冲字节流需要 268 毫秒。
这个结果让我很惊讶。不是说缓冲流效率很高吗?要知道为什么,只能到源码中寻找答案。看一下字节缓冲流的write方法:public synchronized void write(byte b[], int off, int len) throws IOException { if (len >= buf.length) { /* 如果请求长度超过输出缓冲区的大小,则刷新输出缓冲区,然后写入直接数据。通过这种方式,缓冲流将无害地级联。 */ 刷新缓冲区(); out.write(b, 关闭, len);返回; } if (len > buf.length - count) {lushBuffer(); } System .arraycopy(b, off, buf, count, len);计数 += 长度;}
注释说得很清楚:如果请求长度超过输出缓冲区的大小,则刷新输出缓冲区并直接写入数据。这样缓冲的流将无害地级联。
不过,至于为什么要这样设计,我还没有想明白。有懂的人可以留言指点一下吗?
基于以上情况,如果要比较普通字节流和缓冲字节流的效率差距,就必须避免直接读写较长的字符串。因此,设计了以下比较案例: 使用字节流和缓冲字节流 流单独复制文件。
public class MyTest { public static void main(String[] args) throws IOException { File data = new File("C:/Mu/www.introzo.com"); }文件a = new File("C:/Mu/www.introzo.com ");文件b = new File("C:/Mu/www.introzo.com");
StringBuilder sb = new StringBuilder();长开始= System.currentTimeMillis();复制(数据,a);长结束= System.currentTimeMillis();
长start2 = System.currentTimeMillis(); bufferedCopy(数据, b); long end2 = System.currentTimeMillis();
System.out.println("普通字节流耗时:" + (end - start) + " ms"); System.out.println("缓冲字节流耗时:" + (end2 - start2) + " ms"); }
// 普通字节流 public static void copy(File in, File out) throws IOException { // 封装数据源 InputStream is = new FileInputStream(in); // 封装目的地 OutputStream os = new FileOutputStream(out); int by = 0; while ((by = www.introzo.com()) != -1) { os.write(by); } is.close(); os.close(); }
// 缓冲字节流 public static void bufferedCopy(File in, File out) throws IOException { // 封装数据源 BufferedInputStream bi = new BufferedInputStream(new FileInputStream(in)); // 封装目的地 BufferedOutputStream bo = new BufferedOutputStream(new FileOutputStream(out)); int by = 0; while ((by = www.introzo.com()) != -1) { bo.write(by); } bo.close(); bi.close(); }}
运行结果:
普通字节流耗时:184867 ms缓冲字节流耗时:752 ms
这次,普通字节流和缓冲字节流的效率差异就很明显了,达到了245倍。
再看看字符流和缓冲字符流的效率对比:
public class IOTest { public static void main(String[] args) throws IOException { // 数据准备 dataReady();
File data = new File("C:/Mu/data.txt"); File a = new File("C:/Mu/a.txt"); File b = new File("C:/Mu/b.txt"); File c = new File("C:/Mu/c.txt");
long start = System.currentTimeMillis(); copy(data, a); long end = System.currentTimeMillis();
long start2 = System.currentTimeMillis(); copyChars(data, b); long end2 = System.currentTimeMillis();
long start3 = System.currentTimeMillis(); bufferedCopy(data, c); long end3 = System.currentTimeMillis();
System.out.println("普通字节流1耗时:" + (end - start) + " ms,文件大小:" + a.length() / 1024 + " kb"); System.out.println("普通字节流2耗时:" + (end2 - start2) + " ms,文件大小:" + b.length() / 1024 + " kb"); System.out.println("缓冲字节流耗时:" + (end3 - start3) + " ms,文件大小:" + c.length() / 1024 + " kb"); }
// 普通字符流不使用数组 public static void copy(File in, File out) throws IOException { Reader reader = new FileReader(in); Writer writer = new FileWriter(out);
int ch = 0; while ((ch = www.introzo.com()) != -1) { writer.write((char) ch); } reader.close(); writer.close(); }
// 普通字符流使用字符流 public static void copyChars(File in, File out) throws IOException { Reader reader = new FileReader(in); Writer writer = new FileWriter(out);
char[] chs = new char[1024]; while ((www.introzo.com(chs)) != -1) { writer.write(chs); } reader.close(); writer.close(); }
// 缓冲字符流 public static void bufferedCopy(File in, File out) throws IOException { BufferedReader br = new BufferedReader(new FileReader(in)); BufferedWriter bw = new BufferedWriter(new FileWriter(out));
String line = null; while ((line = br.readLine()) != null) { bw.write(line); bw.newLine(); bw.flush(); }
// 释放资源 bw.close(); br.close(); }
// 数据准备 public static void dataReady() throws IOException { StringBuilder sb = new StringBuilder(); for (int i = 0; i < 600000; i++) { sb.append("abcdefghijklmnopqrstuvwxyz"); } OutputStream os = new FileOutputStream(new File("C:/Mu/data.txt")); os.write(sb.toString().getBytes());
os.close(); System.out.println("完毕"); }}
运行结果:
普通字符流1耗时:1337 ms,文件大小:15234 kb普通字符流2耗时:82 ms,文件大小:15235 kb缓冲字符流耗时:205 ms,文件大小:15234 kb
测试多次,结果差不多,可见字符缓冲流效率上并没有明显提高,我们更多的是要使用它的readLine()和newLine()方法。
相关文章
- 10-04 C语言经典16题例题,C语言经典例题100题
- 10-04 inodelinux简介下载
- 10-04 黑客学习ppt教案,黑客ppt模板
- 10-04 Linux下加载无线网卡的步骤(linux加载无线
- 10-04 Linux C语言管理文件目录(linuxcdir
- 10-04 安阳网络安全中心:打造安全互联网新生态(安阳网络安
- 10-04 荣耀x50有没有nfc功能
- 10-04 苹果怎么判断听筒坏了
- 10-04 苹果手机自动调节亮度怎么关闭
- 10-04 苹果手机如何扫码连接wifi
- 10-04 天玑720是最差的5g处理器吗
- 10-04 苹果id怎么取消和另一个的关联
- 10-04 科技昨晚今晨0808:小米员工回应手机地震预警为何
- 10-04 IDC:二季度中国平板电脑市场同比下滑6.6%,苹
- 10-04 贾跃亭下周真的要回国吗?法拉第未来宣布与黄冈市合作
- 10-04 苹果11打游戏怎么样
- 10-04 苹果12怎么关机啊
- 10-04 华为手机怎么把字体调大
- 10-04 小米Note3线刷刷机教程及线刷rom包救砖包下载
- 10-04 美图M6线刷刷机教程及线刷包救砖系统刷机包下载
- 最近发表