精华内容
下载资源
问答
  • 缓冲流
    千次阅读
    2019-06-14 10:06:37

    缓冲流的原理

    在这里插入图片描述
    java.io.BufferedOutputStream extends OutputStream

    BufferedOutputStream ————字节缓冲输出流

    继承自父类的共性方法

    • public void close()——关闭此输出流并释放与此流相关联的任何系统资源。
    • public void flush()——刷新此输出流并强制任何缓冲的输出字节被写出。
    • public void write(byte[ ] b)——将b.length字节从指定的字节数组写入到此输出流。
    • public void write(byte[ ] b,int off,int len)——从指定的字节数组写入len字节,从偏移量off开始输出到此输出流。
    • public abstract void write(int b)——将指定的字节输出流。
      构造方法
    • BufferedOutputStream (OutputStream out)——创建一个新的缓冲输出流,以将数据写入指定的底层输出流。
    • BufferedOutputStream (OutputStream out,int
      size)——创建一个新的缓冲输出流,以将具有指定缓冲区大小的数据写入指定的底层输出流。
      参数
    • OutputStream out————字节输出流
      我们可以传递FileOutPutStream,缓冲流会给FileOutPutStream增加一个缓冲区,提高FileOutPutStream的写入效率。
    • int size—————————指定缓冲流内部缓冲区的大小,不指定默认。
      使用步骤(重点)
      1.创建FileOutPutStream对象,构造方法中绑定要输出的目的地。
      2.创建BufferedOutputStream对象,构造方法中传递FileOutPutStream对象,提高FileOutPutStream对象效率。
      3.使用BufferedOutputStream对象中的方法write,把数
      据写入到内部缓冲区中。
      4.使用BufferedOutputStream对象中的方法flush,把内部缓冲区中的数据,刷新到文件中。
      5.释放资源(会先调用flush方法刷新数据,第四步可以省略)。

    java.io.BufferedIutputStream extends IutputStream

    BufferedIntputStream ————字节缓冲输入流

    继承自父类的共性方法

    • int read()——从输入流中读取数据的下一个字节。
    • int read(char[ ] cbuf)——从输入流中读取一定数量的字节,并将其存储在缓冲区数组b中。
    • void close()——关闭此输入流并释放与此流相关联的任何系统资源。
    • 构造方法
    • BufferedIntputStream (IutputStream in)——创建一个BufferedIntputStream 并保存其参数,即输入流in,以便将来使用。
    • BufferedIntputStream (IntputStream in,int
      size)——创建具有指定缓冲区大小的BufferedIntputStream 保存其参数,即输入流in。
      参数
    • IntputStream in————字节输入流
      我们可以传递FileIntPutStream,缓冲流会给FileIntPutStream增加一个缓冲区,提高FileIntPutStream的读取效率。
    • int size—————————指定缓冲流内部缓冲区的大小,不指定默认。
      使用步骤(重点)
      1.创建FileIntPutStream对象,构造方法中绑定要读取的数据。
      2.创建BufferedIntputStream 对象,构造方法中传递FileIntPutStream对象,提高FileIntPutStream对象读取效率。
      3.使用BufferedIntputStream 对象中的方法read,读取文件
      4.释放资源。

    java.io.BufferedWriter extends Writer

    字符缓冲输出流——BufferedWriter类

    继承自父类的共性成员方法

    • public void write(int c)—————写入单个字符。

    • public void write(char[ ] cbuf)——写入字符数组。

    • abstract void write(char[ ] cbuf,int off,int len)——写入字符数组的某一部分,off数组的开始索引,len写的字符个数。

    • void write(String str)——写入字符串。

    • void write(char[ ] cbuf,int off,int len)——写入字符数组的某一部分,off字符串的开始索引,len写的字符个数。

    • void flush()——刷新该流的缓冲。

    • public void close()————————关闭此流,但要先刷新它。
      构造方法

    • BufferedWriter (Writer out)————创建一个使用默认大小输出缓冲区的缓冲字符输出流。

    • BufferedWriter (Writer out,int sz)——创建一个使用给定大小输出缓冲区的缓冲字符输出流。
      参数
      Writer out————字符输出流
      我们可以传递FileWtite,缓冲流会给FileWtite增加一个缓冲区,提高FileWtite的写入效率。
      int sz—————指定缓冲区的大小,不写默认大小。
      特有的成员方法

    • void newLine()————写入一个行分隔符。会根据不同的操作系统,获取不同的行分隔符。

    使用步骤
    1.创建字符缓冲输出流对象,构造方法中传递字符输出流。
    2.调用字符缓冲输出流对象的方法write,把数据写入到缓冲区中。
    3.调用字符缓冲输出流对象的方法flush,把内存缓冲区中的数据,刷新到文件中。
    4.释放资源。
    java.io.BufferedReader extends Reader

    字符输入流——BufferedReader 类

    继承自父类的共性成员方法

    • int read()——读取单个字符并返回。
    • int read(char[ ] cbuf)——一次读取多个字符,将字符读入数组。
    • void close()——关闭该流并释放与之关联的所有资源。
      构造方法
    • BufferedReader (Reader in)————创建一个使用默认大小的输入缓冲区的缓冲字符输入流。
    • BufferedReader (Reader in)————创建一个使用是定大小输入缓冲区的缓冲字符输入流。
      参数——————读取文件的数据源
      Reader in——字符输入流
      我们可以传递FileReader,缓冲流会给FileReader增加一个缓冲区,提高FileReader的读取效率。
      特有的成员方法
    • String readLine( )——————读取一个文本行。读取一行数据。
      行的终止符号—————————通过下列字符即可认为某行已终止————换行(‘\n’)、回车(‘\r’)或者回车后直接跟着换行(\n\r)。
      返回值
      包含改行内容的字符串,不包含任何终止符,如果已达流末尾,则放回null。
      使用步骤
      1.创建一个字符缓冲输入流对象,构造方法中传递符缓冲输入流。
      2.使用符缓冲输入流对象中的方法read/readLine读取文本。
      3.释放资源。

    练习——对文本的内容进行排序

    package cn.lrf.IODemo;
    
    import java.io.*;
    import java.util.HashMap;
    
    /*练习
     * 对文本的内容进行排序
     * 按照(1,2,3...顺序进行排序)
     * 分析
     * 1.创建一个HashMap集合对象,可以存储每行文本的序号1,2,3...);value:存储每行的文本
     * 2.创建字符缓冲输入流对象,构造方法中绑定字符输入流。
     * 3.创建字符缓冲输出流对象,构造方法中绑定字符输入流。
     * 4.使用字符缓冲输入流中的方法readLine,逐行读取文本。
     * 5.对读取到的文本进行切割,获取行中的序号和文本内容。
     * 6.把切割好的序号和文本的内容存储到HashMap集合中(key序号是有序的,会自动排序1,2,3,4...)
     * 7.遍历HashMap集合,获取每一个键值对。
     * 8.把每一个键值对,拼接为一个文本行
     * 9.把拼接好的文本,使用字符缓冲输出流的方法write,写入到文件中。
     * 10.释放资源。
     * */
    public class Demo05Test {
        public static void main(String[] args) throws IOException {
            // 1.创建一个HashMap集合对象,可以存储每行文本的序号1,2,3...);value:存储每行的文本
            HashMap<String, String> map = new HashMap<>();
            // 2.创建字符缓冲输入流对象,构造方法中绑定字符输入流。
            BufferedReader br = new BufferedReader(new FileReader("day08-code\\src\\cn\\lrf\\IODemo\\c.txt"));
            //3.创建字符缓冲输出流对象,构造方法中绑定字符输入流。
            BufferedWriter bw = new BufferedWriter(new FileWriter("day08-code\\src\\cn\\lrf\\IODemo\\d.txt"));
            // 4.使用字符缓冲输入流中的方法readLine,逐行读取文本。
            String line;
            while ((line = br.readLine()) != null) {
                // 5.对读取到的文本进行切割,获取行中的序号和文本内容。
                String[] arr = line.split("\\.");
                //6.把切割好的序号和文本的内容存储到HashMap集合中(key序号是有序的,会自动排序1,2,3,4...)
                map.put(arr[0], arr[1]);
            }
            // 7.遍历HashMap集合,获取每一个键值对。
            for (String key : map.keySet()) {
                String value = map.get(key);
                // 8.把每一个键值对,拼接为一个文本行
                line = key + "." + value;
                // 9.把拼接好的文本,使用字符缓冲输出流的方法write,写入到文件中。
                bw.write(line);
                bw.newLine();//写换行
            }
            // 10.释放资源。
    
            bw.close();
    
            br.close();
        }
    }
    
    
    更多相关内容
  • 【Java基础-3】吃透Java IO:字节、字符缓冲流

    万次阅读 多人点赞 2020-09-23 20:12:33
    什么是Java-IO?字符和字节的区别与适用场景是什么?缓冲流到底实现了什么?如何高效地读写文件? 本文用大量的示例图和实例,带你吃透Java IO。

    前言

    有人曾问fastjson的作者(阿里技术专家高铁):“你开发fastjson,没得到什么好处,反而挨了骂背了锅,这种事情你为什么要做呢?”

    高铁答道:“因为热爱本身,就是奖励啊!”

    这个回答顿时触动了我。想想自己,又何尝不是如此。写作是个痛苦的过程,用心写作就更加煎熬,需字字斟酌,反复删改才有所成。然而,当一篇篇精良文章出自己手而呈现眼前时,那些痛苦煎熬就都那么值得。如果这些博文能有幸得大家阅读和认可,就更加是莫大的鼓舞了。技术人的快乐就是可以这么纯粹和简单。

    点波关注不迷路,一键三连好运连连!

    IO流是Java中的一个重要构成部分,也是我们经常打交道的。这篇关于Java IO的博文干货满满,堪称全网前三(请轻喷!)

    下面几个问题(问题还会继续补充),如果你能对答如流,那么恭喜你,IO知识掌握得很好,可以立即关闭文章。反之,你可以在后面得文章中寻找答案。

    1. Java IO流有什么特点?
    2. Java IO流分为几种类型?
    3. 字节流和字符流的关系与区别?
    4. 字符流是否使用了缓冲?
    5. 缓冲流的效率一定高吗?为什么?
    6. 缓冲流体现了Java中的哪种设计模式思想?
    7. 为什么要实现序列化?如何实现序列化?
    8. 序列化数据后,再次修改类文件,读取数据会出问题,如何解决呢?

    1 初识Java IO

    IO,即inout,也就是输入和输出,指应用程序和外部设备之间的数据传递,常见的外部设备包括文件、管道、网络连接。

    Java 中是通过流处理IO 的,那么什么是流

    流(Stream),是一个抽象的概念,是指一连串的数据(字符或字节),是以先进先出的方式发送信息的通道。

    当程序需要读取数据的时候,就会开启一个通向数据源的流,这个数据源可以是文件,内存,或是网络连接。类似的,当程序需要写入数据的时候,就会开启一个通向目的地的流。这时候你就可以想象数据好像在这其中“流”动一样。

    一般来说关于流的特性有下面几点:

    1. 先进先出:最先写入输出流的数据最先被输入流读取到。
    2. 顺序存取:可以一个接一个地往流中写入一串字节,读出时也将按写入顺序读取一串字节,不能随机访问中间的数据。(RandomAccessFile除外)
    3. 只读或只写:每个流只能是输入流或输出流的一种,不能同时具备两个功能,输入流只能进行读操作,对输出流只能进行写操作。在一个数据传输通道中,如果既要写入数据,又要读取数据,则要分别提供两个流。

    1.1 IO流分类

    IO流主要的分类方式有以下3种:

    1. 按数据流的方向:输入流、输出流
    2. 按处理数据单位:字节流、字符流
    3. 按功能:节点流、处理流

    在这里插入图片描述

    1、输入流与输出流

    输入与输出是相对于应用程序而言的,比如文件读写,读取文件是输入流,写文件是输出流,这点很容易搞反。

    在这里插入图片描述
    2、字节流与字符流

    字节流和字符流的用法几乎完成全一样,区别在于字节流和字符流所操作的数据单元不同,字节流操作的单元是数据单元是8位的字节,字符流操作的是数据单元为16位的字符。

    为什么要有字符流?

    Java中字符是采用Unicode标准,Unicode 编码中,一个英文字母或一个中文汉字为两个字节。
    在这里插入图片描述
    而在UTF-8编码中,一个中文字符是3个字节。例如下面图中,“云深不知处”5个中文对应的是15个字节:-28-70-111-26-73-79-28-72-115-25-97-91-27-92-124
    在这里插入图片描述

    那么问题来了,如果使用字节流处理中文,如果一次读写一个字符对应的字节数就不会有问题,一旦将一个字符对应的字节分裂开来,就会出现乱码了。为了更方便地处理中文这些字符,Java就推出了字符流。

    字节流和字符流的其他区别:

    1. 字节流一般用来处理图像、视频、音频、PPT、Word等类型的文件。字符流一般用于处理纯文本类型的文件,如TXT文件等,但不能处理图像视频等非文本文件。用一句话说就是:字节流可以处理一切文件,而字符流只能处理纯文本文件。
    2. 字节流本身没有缓冲区,缓冲字节流相对于字节流,效率提升非常高。而字符流本身就带有缓冲区,缓冲字符流相对于字符流效率提升就不是那么大了。详见文末效率对比。

    以写文件为例,我们查看字符流的源码,发现确实有利用到缓冲区:
    在这里插入图片描述
    在这里插入图片描述

    3、节点流和处理流

    节点流:直接操作数据读写的流类,比如FileInputStream

    处理流:对一个已存在的流的链接和封装,通过对数据进行处理为程序提供功能强大、灵活的读写功能,例如BufferedInputStream(缓冲字节流)

    处理流和节点流应用了Java的装饰者设计模式。

    下图就很形象地描绘了节点流和处理流,处理流是对节点流的封装,最终的数据处理还是由节点流完成的。
    在这里插入图片描述
    在诸多处理流中,有一个非常重要,那就是缓冲流

    我们知道,程序与磁盘的交互相对于内存运算是很慢的,容易成为程序的性能瓶颈。减少程序与磁盘的交互,是提升程序效率一种有效手段。缓冲流,就应用这种思路:普通流每次读写一个字节,而缓冲流在内存中设置一个缓存区,缓冲区先存储足够的待操作数据后,再与内存或磁盘进行交互。这样,在总数据量不变的情况下,通过提高每次交互的数据量,减少了交互次数。
    在这里插入图片描述

    联想一下生活中的例子,我们搬砖的时候,一块一块地往车上装肯定是很低效的。我们可以使用一个小推车,先把砖装到小推车上,再把这小推车推到车前,把砖装到车上。这个例子中,小推车可以视为缓冲区,小推车的存在,减少了我们装车次数,从而提高了效率。
    在这里插入图片描述
    需要注意的是,缓冲流效率一定高吗?不一定,某些情形下,缓冲流效率反而更低,具体请见IO流效率对比。

    完整的IO分类图如下:
    在这里插入图片描述

    1.2 案例实操

    接下来,我们看看如何使用Java IO。

    文本读写的例子,也就是文章开头所说的,将“松下问童子,言师采药去。只在此山中,云深不知处。”写入本地文本,然后再从文件读取内容并输出到控制台。

    1、FileInputStream、FileOutputStream(字节流)

    字节流的方式效率较低,不建议使用

    public class IOTest {
    	public static void main(String[] args) throws IOException {
    		File file = new File("D:/test.txt");
    
    		write(file);
    		System.out.println(read(file));
    	}
    
    	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 = in.read(bytes)) != -1) {
    			// 将读取的内容转换成字符串
    			sb.append(new String(bytes, 0, length));
    		}
    		// 关闭流
    		in.close();
    
    		return sb.toString();
    	}
    }
    

    2、BufferedInputStream、BufferedOutputStream(缓冲字节流)

    缓冲字节流是为高效率而设计的,真正的读写操作还是靠FileOutputStreamFileInputStream,所以其构造方法入参是这两个类的对象也就不奇怪了。

    public class 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 = fis.read(bytes)) != -1) {
    			// 将读取的内容转换成字符串
    			sb.append(new String(bytes, 0, length));
    		}
    		// 关闭流
    		fis.close();
    
    		return 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(string);
    		osw.close();
    	}
    
    	public static String read(File file) throws IOException {
    		InputStreamReader isr = new InputStreamReader(new FileInputStream(file), "UTF-8");
    		// 字符数组:一次读取多少个字符
    		char[] chars = new char[1024];
    		// 每次读取的字符数组先append到StringBuilder中
    		StringBuilder sb = new StringBuilder();
    		// 读取到的字符数组长度,为-1时表示没有数据
    		int length;
    		// 循环取数据
    		while ((length = isr.read(chars)) != -1) {
    			// 将读取的内容转换成字符串
    			sb.append(chars, 0, length);
    		}
    		// 关闭流
    		isr.close();
    
    		return sb.toString()
    	}
    }
    

    4、字符流便捷类

    Java提供了FileWriterFileReader简化字符流的读写,new FileWriter等同于new OutputStreamWriter(new FileOutputStream(file, true))

    public class IOTest {
    	
    	public static void write(File file) throws IOException {
    		FileWriter fw = new FileWriter(file, true);
    
    		// 要写入的字符串
    		String string = "松下问童子,言师采药去。只在此山中,云深不知处。";
    		fw.write(string);
    		fw.close();
    	}
    
    	public static String read(File file) throws IOException {
    		FileReader fr = new FileReader(file);
    		// 一次性取多少个字节
    		char[] chars = new char[1024];
    		// 用来接收读取的字节数组
    		StringBuilder sb = new StringBuilder();
    		// 读取到的字节数组长度,为-1时表示没有数据
    		int length;
    		// 循环取数据
    		while ((length = fr.read(chars)) != -1) {
    			// 将读取的内容转换成字符串
    			sb.append(chars, 0, length);
    		}
    		// 关闭流
    		fr.close();
    
    		return 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(string);
    		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();
    
    		return sb.toString();
    	}
    }
    

    2 IO流对象

    第一节中,我们大致了解了IO,并完成了几个案例,但对IO还缺乏更详细的认知,那么接下来我们就对Java IO细细分解,梳理出完整的知识体系来。

    Java种提供了40多个类,我们只需要详细了解一下其中比较重要的就可以满足日常应用了。

    2.1 File类

    File类是用来操作文件的类,但它不能操作文件中的数据。

    public class File extends Object implements Serializable, Comparable<File>
    

    File类实现了SerializableComparable<File>,说明它是支持序列化和排序的。

    File类的构造方法

    方法名说明
    File(File parent, String child)根据 parent 抽象路径名和 child 路径名字符串创建一个新 File 实例。
    File(String pathname)通过将给定路径名字符串转换为抽象路径名来创建一个新 File 实例。
    File(String parent, String child)根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例。
    File(URI uri)通过将给定的 file: URI 转换为一个抽象路径名来创建一个新的 File 实例。

    File类的常用方法

    方法说明
    createNewFile()当且仅当不存在具有此抽象路径名指定名称的文件时,不可分地创建一个新的空文件。
    delete()删除此抽象路径名表示的文件或目录。
    exists()测试此抽象路径名表示的文件或目录是否存在。
    getAbsoluteFile()返回此抽象路径名的绝对路径名形式。
    getAbsolutePath()返回此抽象路径名的绝对路径名字符串。
    length()返回由此抽象路径名表示的文件的长度。
    mkdir()创建此抽象路径名指定的目录。

    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 字节流

    InputStreamOutputStream是两个抽象类,是字节流的基类,所有具体的字节流实现类都是分别继承了这两个类。

    InputStream为例,它继承了Object,实现了Closeable

    public abstract class InputStream
    extends Object
    implements Closeable
    

    InputStream类有很多的实现子类,下面列举了一些比较常用的:
    在这里插入图片描述
    详细说明一下上图中的类:

    1. InputStreamInputStream是所有字节输入流的抽象基类,前面说过抽象类不能被实例化,实际上是作为模板而存在的,为所有实现类定义了处理输入流的方法。
    2. FileInputSream:文件输入流,一个非常重要的字节输入流,用于对文件进行读取操作。
    3. PipedInputStream:管道字节输入流,能实现多线程间的管道通信。
    4. ByteArrayInputStream:字节数组输入流,从字节数组(byte[])中进行以字节为单位的读取,也就是将资源文件都以字节的形式存入到该类中的字节数组中去。
    5. FilterInputStream:装饰者类,具体的装饰者继承该类,这些类都是处理类,作用是对节点类进行封装,实现一些特殊功能。
    6. DataInputStream:数据输入流,它是用来装饰其它输入流,作用是“允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型”。
    7. BufferedInputStream:缓冲流,对节点流进行装饰,内部会有一个缓存区,用来存放字节,每次都是将缓存区存满然后发送,而不是一个字节或两个字节这样发送,效率更高。
    8. ObjectInputStream:对象输入流,用来提供对基本数据或对象的持久存储。通俗点说,也就是能直接传输对象,通常应用在反序列化中。它也是一种处理流,构造器的入参是一个InputStream的实例对象。

    OutputStream类继承关系图:
    在这里插入图片描述

    OutputStream类继承关系与InputStream类似,需要注意的是PrintStream.

    2.3 字符流

    与字节流类似,字符流也有两个抽象基类,分别是ReaderWriter。其他的字符流实现类都是继承了这两个类。

    Reader为例,它的主要实现子类如下图:
    在这里插入图片描述
    各个类的详细说明:

    1. InputStreamReader:从字节流到字符流的桥梁(InputStreamReader构造器入参是FileInputStream的实例对象),它读取字节并使用指定的字符集将其解码为字符。它使用的字符集可以通过名称指定,也可以显式给定,或者可以接受平台的默认字符集。
    2. BufferedReader:从字符输入流中读取文本,设置一个缓冲区来提高效率。BufferedReader是对InputStreamReader的封装,前者构造器的入参就是后者的一个实例对象。
    3. FileReader:用于读取字符文件的便利类,new FileReader(File file)等同于new InputStreamReader(new FileInputStream(file, true),"UTF-8"),但FileReader不能指定字符编码和默认字节缓冲区大小。
    4. PipedReader :管道字符输入流。实现多线程间的管道通信。
    5. CharArrayReader:从Char数组中读取数据的介质流。
    6. StringReader :从String中读取数据的介质流。

    WriterReader结构类似,方向相反,不再赘述。唯一有区别的是,Writer的子类PrintWriter

    2.4 序列化

    待续…

    3 IO流方法

    3.1 字节流方法

    字节输入流InputStream主要方法:

    • read() :从此输入流中读取一个数据字节。
    • read(byte[] b) :从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。
    • read(byte[] b, int off, int len) :从此输入流中将最多 len 个字节的数据读入一个 byte 数组中。
    • close():关闭此输入流并释放与该流关联的所有系统资源。

    字节输出流OutputStream主要方法:

    • write(byte[] b) :将 b.length 个字节从指定 byte 数组写入此文件输出流中。
    • write(byte[] b, int off, int len) :将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此文件输出流。
    • write(int b) :将指定字节写入此文件输出流。
    • close() :关闭此输入流并释放与该流关联的所有系统资源。

    3.2 字符流方法

    字符输入流Reader主要方法:

    • read():读取单个字符。
    • read(char[] cbuf) :将字符读入数组。
    • read(char[] cbuf, int off, int len) : 将字符读入数组的某一部分。
    • read(CharBuffer target) :试图将字符读入指定的字符缓冲区。
    • flush() :刷新该流的缓冲。
    • 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) :写入字符串的某一部分。
    • flush() :刷新该流的缓冲。
    • close() :关闭此流,但要先刷新它。

    另外,字符缓冲流还有两个独特的方法:

    • BufferedWriternewLine()写入一个行分隔符。这个方法会自动适配所在系统的行分隔符。
    • BufferedReaderreadLine() :读取一个文本行。

    4 附加内容

    4.1 位、字节、字符

    字节(Byte)是计量单位,表示数据量多少,是计算机信息技术用于计量存储容量的一种计量单位,通常情况下一字节等于八位。

    字符(Character)计算机中使用的字母、数字、字和符号,比如’A’、‘B’、’$’、’&'等。

    一般在英文状态下一个字母或字符占用一个字节,一个汉字用两个字节表示。

    字节与字符:

    • ASCII 码中,一个英文字母(不分大小写)为一个字节,一个中文汉字为两个字节。
    • UTF-8 编码中,一个英文字为一个字节,一个中文为三个字节。
    • Unicode 编码中,一个英文为一个字节,一个中文为两个字节。
    • 符号:英文标点为一个字节,中文标点为两个字节。例如:英文句号 . 占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();
    
    		long start = System.currentTimeMillis();
    		write(file, bytes);
    		long end = System.currentTimeMillis();
    
    		long start2 = System.currentTimeMillis();
    		bufferedWrite(file, bytes);
    		long 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(bytes);
    		os.close();
    	}
    
    	// 缓冲字节流
    	public static void bufferedWrite(File file, byte[] bytes) throws IOException {
    		BufferedOutputStream bo = new BufferedOutputStream(new FileOutputStream(file));
    		bo.write(bytes);
    		bo.close();
    	}
    }
    

    运行结果:

    普通字节流耗时:250 ms
    缓冲字节流耗时:268 ms
    

    这个结果让我大跌眼镜,不是说好缓冲流效率很高么?要知道为什么,只能去源码里找答案了。翻看字节缓冲流的write方法:

    public synchronized void write(byte b[], int off, int len) throws IOException {
        if (len >= buf.length) {
            /* If the request length exceeds the size of the output buffer,
               flush the output buffer and then write the data directly.
               In this way buffered streams will cascade harmlessly. */
            flushBuffer();
            out.write(b, off, len);
            return;
        }
        if (len > buf.length - count) {
            flushBuffer();
        }
        System.arraycopy(b, off, buf, count, len);
        count += len;
    }
    

    注释里说得很明白:如果请求长度超过输出缓冲区的大小,刷新输出缓冲区,然后直接写入数据。这样,缓冲流将无害地级联。

    但是,至于为什么这么设计,我没有想明白,有哪位明白的大佬可以留言指点一下。

    基于上面的情形,要想对比普通字节流和缓冲字节流的效率差距,就要避免直接读写较长的字符串,于是,设计了下面这个对比案例:用字节流和缓冲字节流分别复制文件。

    public class MyTest {
    	public static void main(String[] args) throws IOException {
    		File data = new File("C:/Mu/data.zip");
    		File a = new File("C:/Mu/a.zip");
    		File b = new File("C:/Mu/b.zip");
    
    		StringBuilder sb = new StringBuilder();
    
    		long start = System.currentTimeMillis();
    		copy(data, a);
    		long end = System.currentTimeMillis();
    
    		long start2 = System.currentTimeMillis();
    		bufferedCopy(data, 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 = is.read()) != -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 = bi.read()) != -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 = reader.read()) != -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 ((reader.read(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()方法。

    4.3 NIO

    待续…

    展开全文
  • 谐振1 概述在各种形式的开关变器中,为了减小功率管的电流、电压及热应力,降低损耗,提高变器效率,减小电磁干扰,提高开关频率和增加变器功率密度,广泛采用了软开关技术。作为软开关技术的一种,无源无损...
  • 缓冲技术某种程度上而言也是符合银弹理论,增加了一层抽象层–缓冲区,用于解决上下游机器介质速度不匹配导致的程序速度缓慢的问题。缓冲器是一个存储器,它可以是硬件级的,即独立于内存外设置的专门硬件缓冲器...

    缓冲技术某种程度上而言也是符合银弹理论,增加了一层抽象层–缓冲区,用于解决上下游机器介质速度不匹配导致的程序速度缓慢的问题。

    缓冲器是一个存储器,它可以是硬件级的,即独立于内存外设置的专门硬件缓冲器(内存页表起始地址寄存器),也可以是软件级的,即由软件在内存中开辟一块缓冲区域(buffer,cache)。硬件要花钱,所以一般用在关键的地方。

    1. 引入缓冲技术的原因
    1.Cache缓存,减少读块设备的次数。当进程需要在相应的块存储设备中读取数据时,可以通过操作系统批量地从块设备中预读取一段数据存放在缓冲器(一般是在内存中专门划分出来的区域),当再次对此块设备进行文件读取时,可以先从缓冲器内优先搜索,若没有命中,则转向块设备中进行读取。数据复用,当命中率(所读数据在缓冲器内的次数与读操作的总次数的比值)较高时,会明显提高读操作的速度;(fread文件时,操作系统会为该文件配置专属缓冲

    2.减少对CPU的中断次数:这是硬件缓冲器的作用。举个例子,比如在通信系统中,发送端发送来的数据,接收端只提供了一个Byte寄存器来缓冲接受,这意味着如果发送端发来一个Byte,接受端必须响应中断立即处理,不然缓冲寄存器中的数据会被下一次接受覆盖,而如果接受端提供8Byte的寄存器来缓冲接受,则显然可以将中断的频率降低至1/8。注:软缓冲并不能减少中断次数。

    3.作为无法直接通信的设备间的中转:比如将光盘上的数据存到磁盘上,可以现在内存中开辟一块缓冲区,先将光盘上的数据移动到缓冲区中,然后再将缓冲区中的内容批量传输到磁盘上。

    4.解决进程要求的数据尺寸和块设备提供的物理数据流单位不匹配的情况。如fread只需要读取下一个字符,而磁盘一般是以扇区为单位进行单次数据交换的(1个扇区512 B)。这种供需尺寸不匹配的情况,显然会极大地降低效率,fread代码作为case1的特例的同时,也可以很好地说明这种情况。

    5.让CPU和IO设备可以并行工作,加快进程的推荐速度。假设某进程的功能是每次读取一张卡片,然后对此卡片上的信息进行处理,循环进行。如果在卡片阅读器和进程存储区之间直接传输数据,则在从新卡片进行数据读取时,进程的处理端只能idle。所以如果在卡片阅读器和进程存储区之间设置一个缓冲区,则输入时先将卡片信息输入到缓冲区,然后将其复制到进程存储区,这时进程处理端可以进行工作,而缓冲区也可以转而读取下一张卡片,两者协同进行,如果两者速度匹配,则可以进程加速。

    2. 软缓冲技术汇总
    硬件缓冲一般是用来配备在关键场景下,可以暂且不考虑,主要将目标转向软件缓冲的技术实现上。软缓冲则又可以根据使用场合不同分为:单缓冲、双缓冲、循环缓冲和缓冲池

    1、单缓冲
    在单缓冲方式中,当用户进程发出I/O请求时,操作系统在内存中为其分配一个缓冲区。数据输入的过程是,当一个用户进程要求输入数据时,操作系统控制输入设备将数据送往缓冲区存放,再送往用户进程的数据存储区。单缓冲由于只有一个缓冲区,所以任何时刻只能支持单向单工操作,要么执行的是读取数据要么执行的是往设备中写数据,而当进程从缓冲区中读数据时,输入设备是不能从块设备搬运数据进入缓冲区的,否则会造成数据紊乱。即任意时刻,下图的四个箭头只能点亮一个。
    这里写图片描述
    2、双缓冲
    提供两个缓冲区,所以可以同时点亮两个箭头,联合操作的可能有多种
    这里写图片描述
    3.循环缓冲
    链表。循环缓冲技术是在内存中分配大小相等的存储区作为缓冲区,并将这些缓冲区连接起来,每个缓冲区都有一个指向下一个缓冲区的指针,链表形成一个环。并为这个循环链表配备两个全局参数IN和OUT,分别用来指向第一个空缓冲区和第一个满缓冲区。

    4.缓冲池:Linux系统采用的方式
    1到3都是进程专属缓冲,考虑到当前操作系统的分时并行特性,每个时刻只有一个进程的缓冲体系是在工作,而其他进程的缓冲体系此时没有工作,但是因为专属分配的情况,所以空间依旧被占据着,这便会导致内存占用过大,缓冲体系利用率不高。所以应该考虑采用公用缓冲池结构,可为多个并发进程提供缓冲服务。

    a.缓冲池的组成
    缓冲池由3种类型的缓冲区构成:空闲缓冲区、装满输入数据的缓冲区和装满输出数据的缓冲区。为了方便管理,将相同类型的缓冲区组成一个队列,于是可分别构成3个队列:空缓冲区队列emq;装满输入数据的缓冲区队列inq;装满输出数据的缓冲区队列outq。由于这些缓冲区是公用的,因此要由操作系统进行统一管理。

    b.缓冲池的工作方式
    公用缓冲池的缓冲区工作方式4种:收容输入、提取输入、收容输出、提取输出
    收容输入:当进程需要输入数据时,从空闲缓冲区队列emq中获取一个空缓冲区,把它作为收容工作缓冲区,然后将数据输入其中,输入完毕,将它挂在inq队列的末尾。

    提取输入:当计算进程需要输入数据进行计算时,系统从inq队列的队首取得一个缓冲区,作为提取输入工作缓冲区,计算进程从中提取数据,当进程提取完该缓冲区中的数据后,再将它挂在空缓冲区队列emq的末尾。

    收容输出:当计算进程需要输出数据时,系统从空缓冲队列emq的队首取得一个空闲缓冲区,将它作为收容输出工作缓冲区,装满数据后,将它挂在outq队列的末尾。

    提取输出:当要进行输出操作时,从输出缓冲区队列outq的队首取得一个缓冲区,作为提取输出工作缓冲区,当数据输出完毕,将该缓冲区挂在空缓冲区队列emq的末尾。

    3. Linux系统的磁盘高速缓存
    Linux系统在文件系统和磁盘驱动程序之间引入一层抽象层—高速缓存管理层,用于解决内存和磁盘速度不匹配的情况。磁盘缓冲管理试图尽可能多的把有用数据保存在缓冲区中,当进程程序需要读数据时,先从高速缓存中匹配,若命中,则直接从缓存中提取数据而不必启动磁盘IO,如果没有命中,则再启动磁盘驱动先将数据送往高速缓存中,进程再从高速缓存中读数据。当进程往磁盘写数据时,则采用“延迟写”策略,进程现网高速缓冲中写,如果缓冲区满了或延迟写时间到了,则缓冲管理程序会启动磁盘驱动批量写入。

    Linux系统提供的缓冲池,是由多进程共享,由操作系统进行统一管理。为了提高使用效率,必须采取适当的管理策略。缓冲区size = 磁盘块size。当进程要从一个磁盘读取数据时,先检查要读取的磁盘块是否包含在某缓冲区中,如果不在,则从空闲缓冲区队列中分配给它一个空闲缓冲区,当进程要将数据写入磁盘时,先检查要写入的磁盘块是否在该设备对应的缓冲区队列中,如果不在则为这个磁盘块分配一个空闲缓冲区。

    小小的思维亮点
    当缓冲区的信息被读入进行的内存空间后,或者进程已经将数据写进入了缓冲区(延迟写),按理说,该缓冲区已经可以释放了,并进入空闲缓冲队列中等待新任务分配,所以可将缓冲区的flag中的BUSY位置0,送入空闲缓冲区队尾,但这并不意味着立即让此缓冲区退出设备缓冲区队列,而是仍保留在队列中,这样做的原因是此处使用完毕送往空闲缓冲队列的缓冲区保存的信息可能被再次访问,若是直接释放成空缓冲区则显然比较浪费,完全可以等到空闲队列中该缓冲区被新分配了任务时,再根据保留的缓冲区头信息找到该缓冲区在设备缓冲区中位置,将其从队列中删除,并装载新的数据。即存在伪删除的操作fakeDelete()。而当空闲队列pop出的缓冲区的flag位为1的缓冲区,则应该先调度磁盘驱动程序将该延迟写缓冲区内容写到目标磁盘块中,然后再装载新内容。

    展开全文
  • java IO [缓冲技术] [装饰设计模式]

    千次阅读 2013-01-06 20:48:38
    IO流流的分类字符的由来乱码的出现读写流流的读写操作WriterReader缓冲技术IO异常装饰设计模式InputStreamReader IO  IO---input 和 output 的简写,用来处理设备之间的数据传输 如内存 硬盘 网络等

     

     

    1. IO流
    2. 流的分类
    3. 字符流的由来
    4. 乱码的出现
    5. 读写流
    6. 流的读写操作
    7. Writer
    8. Reader
    9. 缓冲技术
    10. IO异常
    11. 装饰设计模式
    12. InputStreamReader

    • IO流

               IO流---input 和 output 的简写,用来处理设备之间的数据传输 如内存 硬盘 网络等之间的数据传输
               IO包---用于操作流的对象封装在IO包中

     

    • 流的分类



                流按操作数据分 : 输入流 输出流

                流按流向分 : 字节流 字符流


     早期IO包出现的都是基于字节流,因为所有数据都是字节数据
     而为了方便操作字符,出现了字符流

    • 字符流的由来


    ASCII 码表 美国信息交换标准码(American Standard Code for Information Interchange)

    GBK 全名为汉字内码扩展规范,英文名Chinese Internal Code Specification亦采用双字节表示,总体编码
            范围为8140-FEFE,首字节在81-FE 之间,尾字节在40-FE 之间,剔除 xx7F一条线。
             总计23940 个码位,共收入21886个汉字和图形符号,其中汉字(包括部首和构件)21003 个,图形符号883 个。

    Unicode码表:国际通用的编码表都用两个字节表示 Java的内核和class文件是基于unicode的,这使Java程序具有良好的跨平台性
            对可以用ASCII表示的字符使用UNICODE并不高效,因为UNICODE比ASCII占用大一倍的空间,而对ASCII来说高字节的0对他毫无用处。

    UTF-8编码是一种兼容所有语言的编码方式,是UNICODE的一种变长字符编码又称万国码
            UTF-8用1到6个字节编码UNICODE字符。用在网页上可以同一页面显示中文简体繁体及其它语言(如日文,韩文)

    • 乱码的出现


    开发和编译代码时指定字符集与运行操作系统的默认编码表不一致
    先Java(包括JSP)源文件中很可能包含有中文,而Java和JSP源文件的保存方式是基于字节流的,如果Java和JSP编译成class文件过程
    中,使用的编码方式与源文件的编码不一致,就会出现乱码。

    • 读写流

    Java读写文件最常用的类是
    FileInputStream/FileOutputStream和FileReader/FileWriter。其中FileInputStream
    和FileOutputStream是基于字节流的,常用于读写二进制文件。

    读写字符文件建议使用基于字符的FileReader和FileWriter,省去了字节与字符之间的转换。
    但这两个类的构造函数默认使用系统的编码方式,如果文件内容与系统编码方式不一
    致,可能会出现乱码。

    • 流的读写操作


    Reader 和 Writer 是字符流的两个基类 两个专门操作字符流的类  适用于处理字符数据

    InputStream 和 OutputStream 是字节流的两个基类  是专门用来操作字节的 可以操作一切数据,通用性强

     

    所有由以上四个类都是抽象类 , 由它们派生的子类名都是以基类为后缀,以功能名作为前缀

     

    • Writer


    Writer 操作字符输入流的抽象类 , 子类必须实现的方法仅有 write(char[], int, int)、flush() 和 close()。
     |        多数子类将重写此处定义的一些方法,以提供更高的效率和/或其他功能。 Writer  构造方法被protected 修饰表明只有子类可以访问
     |----BufferedWriter  将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。特有方法 newLine()
     |----CharArrayWriter
     |----FilterWriter
     |----PipedWriter
     |----PrintWriter  此类实现在 PrintStream 中的所有 print 方法 可以接收 字节流也可以接收字符流  可以设置自动刷新   
     |----StringWriter ---特有方法 StringBuffer getBuffer()    返回该字符串缓冲区本身,当前缓冲区值的 StringBuffer。       
     |----OutputStreamWriter 字符流通向字节流的桥梁 ,将要写入流中的字符编码成字节,字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。

            |                                每次调用 write() 方法都会导致在给定字符(或字符集)上调用编码转换器。为了获得最高效率,可考虑将 OutputStreamWriter

            |                                 包装到 BufferedWriter 中,以避免频繁调用转换器。例如: 

            |                                Writer out = new BufferedWriter(new OutputStreamWriter(System.out));                          
            |----FileWriter  写文件的便捷类 ,用于写入字符流。

     

    他们都实现了 Appendable, Closeable, Flushable 接口

      • Writer 的基本方法


      Writer append(字符或字符序列) 将指定字符或字符序列添加到此流
      abstract  void close()   关闭此流,但要先刷新它。            ------抽象方法,子类需要实现
      abstract  void flush()   刷新该流的缓冲。               ------抽象方法,子类需要实现
      abstract  void write(char[] cbuf, int off, int len)   写入字符数组的某一部分。------抽象方法,子类需要实现
      void write(int c)  写入单个字符。                    
      void write(String str)   写入字符串。             
      void write(char[] cbuf)  写入字符数组。 
      void write(String str, int off, int len)   写入字符串的某一部分。     
        

      • FileWriter

        • FileWriter的构造方法

    public FileWriter(String fileName) throws IOException
        ┗----- 根据给定的文件名构造一个 FileWriter 对象时将在指定的目录下创建指定文件,若该目录已有同名文件,则将其覆盖。
        fileName - 一个字符串,表示与系统有关的文件名。
        必须抛出:
        IOException - 如果指定文件存在,但它是一个目录,而不是一个常规文件;或者该文件不存在,但无法创建它;
                               或因为其他某些原因而无法打开它 则抛出该异常
        
        
    public FileWriter(String fileName, boolean append) throws IOException
         ┗-----根据给定的文件名以及指示若存在同名文件是否会覆盖 append为 true表示续写 

      注意:

        1)创建流对象要声明抛出 IOException 异常 或try{} catch{} 处理
        2)创建的 FileWriter 对象调用write方法时 是将数据写入流当中,而不是文件中
        3)调用flush方法将流中的缓冲数据刷入目的文件中,并且流中的数据还存在于流中,可以继续使用
        4)调用close方法也可以将流中的数据刷到目的地,但会关闭流,所以流中的缓冲数据将被被清除
        5)FileWriter 专门负责字符文件写入 该类没有无参构造函数说明对象一初始化就要指定写入文件

        • flush与close的区别:
           close --数据写入结束必须关闭此写入流,但要先刷新一次内部缓冲中的数据将数据刷到目的地中。
               在关闭该流之后,再调用 write() 或 flush() 将导致抛出 IOException。关闭以前关闭的流无效。
           flush -- 刷新后流对象还可以继续使用,流中的数据还存在

     

    • Reader

     

    FileReader
     |  
     |----BufferedReader  从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。 可以指定缓冲区的大小,或者可使用默认的大小。
     |                 |----LineNumberReader
     |----CharArrayReader
     |----FilterReader
     |                 |---PushbackReader
     |----StringReader
     |----PipedReader
     |----InputStreamReader 字节流通向字符流的桥梁 
                          |----FileReader  用来读取字符文件的便捷类 ,用于读取字符流
                      

      • FileReader 

      • FileReader 的构造方法


                 FileReader(File file) throws FileNotFoundException
                     ┗在给定从中读取数据的 File 的情况下创建一个新 FileReader。
                 FileReader(FileDescriptor fd) throws FileNotFoundException
                     ┗在给定从中读取数据的 FileDescriptor 的情况下创建一个新 FileReader。
                 FileReader(String fileName) throws FileNotFoundException
                     ┗在给定从中读取数据的文件名的情况下创建一个新 FileReader。
               

            FileReader 的构造方法
            1)都是有参数构造函数 ,说明要将数据写入操作必须指定操作对象
            2)都要声明抛出 FileNotFoundException - 如果指定文件不存在,或者它是一个目录,而不是一个常规文件,抑或因为其他某些原因而无法打开进行读取。
            3)都假定默认字符编码和默认字节缓冲区大小都是适当的,若要自己指定 需要构造  InputStreamReader

        •  FileReader基本方法:


     abstract  void close()   关闭该流并释放与之关联的所有资源。 这是必须进行的操作  ----子类必须实现
                                         在关闭该流后,再调用 read()、 mark()、reset() 或 skip() 将抛出 IOException。关闭以前关闭的流无效。 
     abstract  int read(char[] cbuf, int off, int len)  将字符读入数组的某一部分。     ----子类必须实现

      void mark(int readAheadLimit)   标记流中的当前位置。
      void reset()  重置该流。
      long skip(long n)    跳过字符。
      boolean markSupported()  判断此流是否支持 mark() 操作。
      boolean ready()   判断是否准备读取此流。    

      int read()  每次读取一个字符,且下次自动读取下一个字符,当读取到文件末尾时返回 -1 (操作系统下文件结束标志)。
                     返回的是字符编码的整形表示 ,需要强转为字符
                     在字符可用、发生 I/O 错误或者已到达流的末尾前,此方法一直阻塞。
      int read(char[] cbuf)  将每次将指定长度(数组的长度)个字符读取存入数组中,且下次自动读取下一个指定长度的字符,
                      返回的是读取到的字符个数 ,若读取到文件末尾返回 -1
                      此数组称为写入缓冲区,
                      在某个输入可用、发生 I/O 错误或者已到达流的末尾前,此方法一直阻塞。

      int read(CharBuffer target)   试图将字符读入指定的字符缓冲区。


       
    总结 :

    Reader 的 read 方法是个阻塞式方法
    copy 文件过程:
      1)创建目的地(目标路径及文件)
       通过构造函数创建 写入流对象 创建目的文件


      2)与已有文件关联
       通过构造函数创建 读取流对象 关联已有文件


      3)读取文件中数据
       为了提高读写效率,不要读一个写一个数据,而是先将读取到的数据存入缓冲区(数组)中,等缓冲区会数组满后再进行写入操作

       避免了每次调用read() 方法都会导致从底层输入流读取一个或多个字节 ,频繁启动转换器,效率低


      4)将读取到的数据写入目的文件
       将读取到并存储在缓冲区数组中的数据写入流中


      5)读写完毕  关闭两个流并释放与之关联的所有资源
      

     

    • 缓冲技术

       流 Stream --数据像流水一样从一处流向另一处 流表示是数据从源到目的端的流动


       输入操作时,字节流从输入设备流向内存;
       输出操作时,字节流从内存流向输出设备;
       流中的数据可以是字符 图片 音视频 等


       因为如果没有缓冲区,应用程序每次输入(读取)输出(写入)都要和设备进行通信,效率很低,

       因此缓冲区为了提高效率 ,数据读写时在内存中为每个流对象提供一个临时缓冲区,每次读取
       一个缓冲区大小的数据后再将这些数据一次性写入(输出)到目标设备

       字符流底层使用字节流的缓冲区,先将字节读取到缓冲区,然后经过查字符编码表 查到字符才进一步的对字符进行操作

     

       数据缓冲这个东西,就是因为数据被输入后在处理的时候需要一定的时间,为了输入接着输出,零时差,就需要缓冲了,
       先预读并处理一部分信息,然后开始输出,在输出的同时可以进行输入和处理,然后等缓冲的部分输出完后,
       另一部分的数据也处理完毕了,就可以接着输出了。

      根据处理速度的不同,需要的缓冲区大小也是不同的。
      倘若没有这个缓冲,那么就会很卡了,断断续续的数据流,因为处理不完。
      CPU 的处理速度 内存存取速度 硬盘(外存储设备)擦写速度 输入输出设备读取输出速度 差异很大

     因此 , 缓冲区起到缓解速度不匹配的问题
     如果从设备读取10M的文件,每次读取一个字节,完成操作将需要做10M次I/O操作,I/O操作又是一件相当耗时的事情,无疑在很大程度上降低了系统的性能。
    提高读写效率,将要读写的数据存入指定大小缓冲区中,减少频繁的硬盘擦写操作

     

     

      • BufferedReader 

      •    ┗━从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。

                                Reader 所作的每个读取请求都会导致对底层字符或字节流进行相应的读取请求。因此,建议用 BufferedReader 包装所有其 read() 操作可能开销很高的

                                 Reader(如 FileReader 和 InputStreamReader)。例如, BufferedReader in = new BufferedReader(new FileReader("foo.in"));
                                 

                                 如果没有缓冲,则每次调用 read() 或 readLine() 都会导致从文件中读取字节,并将其转换为字符后返回,而这是极其低效的。

       构造方法:
       BufferedReader(Reader in)
         ┗━  创建一个使用默认大小输入缓冲区的缓冲字符输入流。 所接收的参数为 Reader 子类字符写入流对象
       BufferedReader(Reader in, int sz)
          ┗━创建一个使用指定大小输入缓冲区的缓冲字符输入流。  所接收的参数为 Reader 子类字符写入流对象

     

     

       特有方法: readLine() 读取一个文本行。通过下列字符之一即可认为某行已终止:
                       换行 ('\n')、回车 ('\r') 或回车后直接跟着换行。 
                       返回:包含该行内容的字符串,不包含任何行终止符,如果已到达流末尾,则返回 null ,所以在写入操作时需要另外添加换号符,,

      • BufferedWriter

          ┗━将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。

       构造函数:
       BufferedWriter(Writer out)
          ┗━创建一个使用默认大小输出缓冲区的缓冲字符输出流。 所接收的参数为 Writer 子类字符写入流对象
       BufferedWriter(Writer out, int sz)
          ┗━创建一个使用给定大小输出缓冲区的新缓冲字符输出流。 所接收的参数为 Writer 子类字符写入流对象

     

       特有方法:newLine() 写入一个行分隔符。行分隔符字符串由系统属性 line.separator 定义,

        利用缓冲技术拷贝文件 


       

     import java.io.*;
    
        class CopyText
        {
         public static void  main(String[] args) throws IOException
         {
    
          //为了提高字符写入流效率 加入了缓冲流技术将需要被提高写入流效率的对象作为参数传入缓冲区的构造函数中
          BufferedWriter bufw = null ;
          BufferedReader bufr = null ;
          try
          {
             bufw = new BufferedWriter(new FileWriter("tobuf.txt"));//字符写入流对象被封装进  写入缓冲区中
                bufr= new BufferedReader(new FileReader("frombuf.txt"));//字符读取流对象被封装进  读取缓冲区中
             String line = null ;
           while ((line=bufr.readLine())!=null)
           {
            bufw.newLine();//newLine()方法是 BufferedReader 的特有方法, 该方法具有跨平台性的行分隔作用
            bufw.flush();//使用 缓冲技术 写入字符到字符输出流中时 为了避免写入意外中断而导致数据丢失必须 刷新一次
           }
          }
          catch (IOException e)
          {
    
          }
          finally
          { 
           try
           { if(bufw!=null)
            bufw.close(); //关闭缓冲区 就是关闭缓冲区中的流对象
           }
           catch (IOException e)
           {
    
           }
           
            try
           { if(bufr!=null)
            bufr.close(); //关闭缓冲区 就是关闭缓冲区中的流对象
           }
           catch (IOException e)
           {
    
           }
          }
       }
    


     

     


             readLine() 的内部是通过read来实现的 , 通过一个一个字符的读取,并将字符存入内存中的字符数组中.直到读取到分隔符位置
                              时将读取并存在数组中的字符数组转换成字符串并返回,数组中的内容不包括换行符

     

       模拟 readLine() 方法

      class MyBufferedReader
      {
       private FileReader r;
    
       MyBufferedReader(FileReader r)
       {
        this.r=r;
       }
    
       public String myReadLine() Throws IOException
       {
        //定义临时容器. 用 BufferedReader 封装的是字符数组
        //定义一个 StringBuilder 容器 因为最终将数据转成字符串
        StringBuilder sb = new StringBuilder();
        int ch = 0 ;
        while((ch=r.read()!=-1)
        {
         if(ch=='\r')
          continue;
         if(ch=='\n')
          return sb.toString();
         else
          sb.append((char)ch);
        }
    
        if(sb.length()!=0)
         return sb.toString();
        return null;
       }
    
       public void myClose() throws IOException 
       {
        r.close();
       }
      }
    


     

     

     

     

     

     

    • IO异常

    所有涉及到IO数据的操作都要声明抛出异常 IOException 或者对其进行异常处理

    • IO异常的处理方式

      将所有操作流的语句放入try块中
       try
       {
        操作流的语句
       }
       catch (IOException e)
       {
        异常处理
        System.out.println(e.toString());//提示
       }
       finally
       {// 创建了多少个流对象就必须分别的关闭
        try//涉及流操作都有处理异常 这里关闭资源要单独try
        { if(流对象引用不为空)//只有创建了流对象才能调用资源关闭操作否则导致空指针异常
         关闭资源操作  //关闭资源是必须进行的操作
        }
        catch (IOException e)
        {
         异常处理
        System.out.println(e.toString());

                               }
                                }

     



       
                 

    • 装饰设计模式


    定义类在不必改变原类文件和使用继承的情况下,动态的扩展一个对象的功能。
    将已有对象作为参数传入,基于已有功能并提供功能加强的设计模式

    装饰类,通过构造函数将已有对象的传入并进行功能增强的类
     

    BufferedReader 就是一种装饰类

    可以将缓冲技术单独抽取进行封装。
    谁要缓冲区就将谁和缓冲相关联即可。
    这样的设计的相对继承体系会变的简单。

      • 为什么要用使用装饰类 与使用子类继承有什么不同?


    使用继承也可以对父类中的功能进行增强只需要调用super就可以调用父类的方法,那使用装饰和继承有什么区别

    已有对象被传入装饰类后 装饰类内持有传入对象的引用,从而可以直接调用传入对象的所有功能,因此在装饰类内部

    可以对存入对象的功能进行直接调用并加以功能增强,在原来对象的功能基础上添加更强的功能,当创建装饰类对象

    时必须先将已有对象传入,然后就可以调用装饰类中的增强功能了

     装饰是对对象动态添加新状态或行为方法,只需对某个或某些行为功能进行增强, 而子类继承父类是静态继承
     装饰模式提供了“即插即用”的方式,在运行期间决定何时增加何种功能。且功能不影响以前的功能 就增加功能来说,
     装饰模式比生成子类更加灵活。

     装饰类和被装饰类通常是都属于一个体系中的。被传入对象的类具备同一个父类或接口,装饰类利用多态,
     可以接收该体系下的所有基本装饰某些基本 功能的对象


    对于同一个父类的多个子类,他们都具备继承自父类的功能,但也都有自己独特的功能,在后期想要对子类的某个基本功能进行增强时,
    只需要创建装饰类,他可以接收具备该基本功能的所有子类对象,对该基本功能进行增强,当被装饰对象传入该装饰类之后,装饰类就具备
    被装饰对象的所有功能,同时又增强某个或几个功能,而采用继承的办法进行功能增强,则定义的子类只能对指定类进行增强,不同的类想要
    对同一个功能进行增强时都要定义自己的子类,因此会显得很臃肿,而且使用不灵活

    简单说就是装饰可以对同一体系下(利用多态特性,父类或者借口引用指向子类对象)的不同子类 的同一个基本功能进行功能增强,比较灵活
    利用继承进行功能增强需要分别为每个需要增强的类定义子类,显得臃肿

    如 FileReader 体系下都用到缓冲技术,如果每个子类都分别定义增加缓冲功能的子类,那么可以分别实现缓冲技术
     FileReader
       |----CharArrayReader
       |                |---BufferedCharArrayReader
       |----FilterReader
       |               |---BufferedFilterReader
       |----StringReader
       |               |---BufferedStringReader
       |----PipedReader
       |               |---BufferedPipedReader
       |----InputStreamReader
                       |---BufferedInputStreamReader
     如果定义装饰类 将需要增强的功能进行抽取 则体系变得简约
     FileReader
        |----CharArrayReader
        |----FilterReader
        |----StringReader
        |----PipedReader
        |----InputStreamReader
        |---BufferedReader   ---装饰类, 可以接收以上几个类的对象

    BufferedReader 构造函数接收的类型为 Reader 利用多态特性 实现对同一体系下的不同对象进行装饰

     

    • InputStreamReader


         InputStreamReader是字节流通向字符流的桥梁, 读取字节并将其解码为字符。 每次调用 InputStreamReader 中的一个 read() 方法都会导致从底层输入流读取一个或多个字节。

                为了达到最高效率,可要考虑在 BufferedReader 内包装 InputStreamReader。例如:
               BufferedReader in  = new BufferedReader(new InputStreamReader(System.in));


    InputStreamReader 转换流通过构造函数接收 InputStream 引用的字节输入流对象 将字节流转换成使用默认或者指定字符集的字符流

    OutputStreamWriter 转换流通过构造函数接收 OutputStream 引用的字节输出流对象 将字符流转换成字节流

     

    System.in 和 System.out分别是标准输入输出流
    可以通过 System.setIn(InputStream in)  和 System.setOut(PrintStream out) 来分别改变他们的输入输出流

    对键盘录入的数据进行一定的操作后通过控制台输出

    //从标准输入流中获取字节流并传入转换流通过指定字符集转成字符,然后用缓冲类包装装饰
    
    BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in)); 
    
    //通过装饰类装饰字符流进行字符进行一定的操作,然后转换成字节流 并通过标准输出流写入控制台上
    BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));
    
       String line = null ;
       while ((line=bufr.readLine())!=null)
       {
        if("over".equals(line))
         break;
        bufw.write(line.toUpperCase());//对输入流的字符数据进行转换操作,并写到字符写入流中
        bufw.newLine(); //给流添加行分隔符
        bufw.flush(); //将一行字符刷入显示器
       }
       bufr.close();
    


     

    涉及三个过程 输入 处理 输出
    1)首先是从标准输入流(键盘)中获取字节流并传入转换流通过指定字符集转成字符,然后用缓冲类包装装饰进行缓冲作用,一次读取一行
      字符流底层使用字节流的缓冲区,先将字节读取到缓冲区,然后经过查字符编码表 查到字符才进一步的对字符进行操作
    2)然后对一行的字符串进行一定的操作(这里是小写转大写操作)
    3)最后对操作完的存放于内存中的字符串(字符流)再转成字节流通过标准输出流输出到(屏幕)上

    在转换过程涉及到查编码表编码解码操作
    在字符串的小写转大写的时候涉及到CPU的处理速度
    在读和写的时候都涉及到缓冲过程 该过程就是起到了缓解CPU处理速度与 内存与输入输出设备之间的传输字节流或字符流的速度
    不匹配问题
    缓冲区的大小可以根据CPU处理速度和输入输出设备读写速度不同来定

     

     

     

    展开全文
  • java 输入,输出缓冲区的个人见解
  • 的概念 在.NET中Stream 是所有的抽象基类。是字节序列的抽象概念,或者说是计算机在处理文件或数据时产生的二进制序列。例如文件、输入/输出设备、内部进程通信管道或者 TCP/IP 套接字。Stream 类及其...
  • 媒体技术介绍

    千次阅读 多人点赞 2021-03-08 17:11:36
    媒体技术使得数据包可以像流水一样发送,如果不使用技术,用户就必须先下载整个媒体文件,而后才能使用多媒体数据。通过媒体技术,可将现场或预存于服务器上的影音传送至观看者端,当影音数据传送至观看者的...
  • TCP协议是当前网络的主流传输协议,因此利用TCP来传输媒体是比较容易实现和很常见的。...文章分析了在媒体传输过程中缓冲区对播放平稳度和实时性的作用及影响,在不同网络状况下计算出缓冲区大小和
  • Java IO学习总结三:缓冲流 转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/54292148 本文出自【赵彦军的博客】 InputStream |__FilterInputStream |__BufferedInputStream首先抛出一个...
  • I/O(5) 利用缓冲流实现对文件的复制操作

    万次阅读 热门讨论 2019-11-24 14:47:28
    前面的文章已经介绍过关于文件的读写操作,这篇文章介绍利用缓冲流实现文件的读写操作。
  • io流使用缓冲区提高读写效率

    千次阅读 2013-06-26 15:17:26
    缓冲区有:BufferedWriter写入缓冲区和BufferedReader读取缓冲区,因为Writer对象和Reader对象操作数据时要读一份写一份,而缓冲区能够把每次读入的数据存着,写的时候一次写出去。所以他们能够提高效率...
  • 【Java】缓冲流如何提高性能

    千次阅读 2015-05-18 12:32:14
    缓冲流如何提高性能(随笔,写的比较粗糙,详情还是请自行观赏源码)
  • Java I/O 操作(二)字节缓冲流 Java I/O 操作(三)File 文件操作、PrintWriter、SequenceInputStream Java I/O 操作(四)对象的序列化 本文涉及到的 I/O 类 System.in /Syste...
  • 学习标准输入输出,我们都会遇到一个概念,缓冲区,但到底什么是,什么是缓冲区呢?  书《C Primer Plus》上说,C程序处理一个而不是直接处理文件。后面的解释十分抽象:『(stream)是一个理想化的数据...
  • 一 介绍JavaIO技术是很重要的技术, 现在来解释一下BufferInputStream、BufferOutputStream的具体实现。.../** 使用缓冲技术读取数据 * * */ public static void bufferRead(String path) { Buffered...
  • 媒体技术概述

    千次阅读 2019-05-08 11:06:42
    媒体又称流式媒体,是将普通多媒体,如音频、视频、动画等,经过特殊编码,使其成为在网络中使用流式传输的连续时基媒体,以适应在网络上边下载边播放的方式。其具有连续性、实时性、时序性三个特点。在这个过程中...
  • 这份代码实现打开PCM裸文件并用双缓存机制进行播放的,可在VS2013下直接编译运行,其中包含一个PCM裸文件。
  • 此漏洞是由于数据存储(如缓冲区)和控件存储(如返回地址)的混合造成的:数据部分的溢出会影响程序的控制,因为溢出会改变返回地址。 本实验将提供四台不同的服务器,每台服务器运行一个带有缓冲区溢出漏洞的...
  • 缓冲区的出现提供了数据的都写效率 对应类:BufferedReader、BufferedWriter 缓冲区的出现是为了的操作效率而出现的, ...2.为了提高字符写入的效率,加入了缓冲技术, 只要将需要被提高效率的
  • JAVA之旅(二十五)——文件复制,字符缓冲区,BufferedWriter,BufferedReader,通过缓冲区复制文件,readLine工作原理,自定义readLine 我们继续IO上个篇幅讲 一.文本复制 读写都说了,我们来看下其他的操作...
  • 本文中的示例应用使用开源的技术来构建这样的系统,这些技术包括 OpenCV、Kafka 和 Spark。另外,还可以使用 Amazon S3 或 HDFS 进行存储; 该系统包含了三个主要的组件:视频收集器(Video Stream...
  • Java 缓冲流简介及简单用法

    千次阅读 2018-03-29 17:05:18
    在java编程中, 我们有时会听到缓冲流和原始等字眼. 其实在之前的博文中, 提到过可以分为原始和处理. http://blog.csdn.net/nvd11/article/details/30126233 也就是说处理是包裹在原始...
  • 在对响应的实体进行操作的时候,使用到...为了提高性能,volley定义了一个byte[]缓冲池,即ByteArrayPool 。 在ByteArrayPool 内,定义了 两个集合,分别是存储按大小顺序排列byte[]的list 和 按使用先后顺序排列byte
  • 摘要 在开发数据设备的驱动程序时,采用中断驱动的I/O方式结合缓冲区的使用,可以将数据的接收和系统调用read隔离开来,提高设备在系统中的运行效率。本文在讨论uClinux下中断处理程序和底半部分的开发的基础上,...
  • /*字符读取流缓冲区-读一行的方法readLine() 字符读取流缓冲区: 该缓冲区提供了一个一次读一行的方法readLine(),方便与对文本数据的获取。 当返回null时,表示读到文件末尾。 */ import java.io.*; class ...
  • 缓冲流,BufferedReader,BufferedWriter 1)BufferedReader,BufferedWriter是更高级的,二者的源和目的地必须是字符输入和字符输出.如果把字符输入做为BufferedReader的源,把字符输出作为...
  • 将文本写入字符输出缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。 可以指定缓冲区的大小,或者接受默认的大小。在大多数情况下,默认值就足够大了。 该类提供了 newLine() 方法,它使用平台自己的...
  • 通过读文件和写文件都使用缓存 BufferedReader 和 BufferedWriter ,使得读写...只是原来我们自己要写个字符数组来实现缓冲的目的,现在可以直接使用jdk给的缓冲来实现复制文件的效率提升。 package com.lxk.FileTe
  • } } 注释如下: BufferedReaderpublic class BufferedReaderextends Reader 从字符输入中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。 可以指定缓冲区的大小,或者可使用默认的大小。大多数情况...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 138,959
精华内容 55,583
关键字:

哪个流使用了缓冲技术