Java 中的流

Posted wukj_litai

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 中的流相关的知识,希望对你有一定的参考价值。

Java 中对输入输出的操作是通过流的方式来进行的,通过把输入和输出封装在流对象中,提供丰富的API,我们可以轻松的完成输入和输出的操作。我们都知道数据在内存和硬盘上都是以字节的形式存放的,一个字节是8个二进制的位,这是操作数据的最少单元。流对象操作数据的最小单元也是字节,Java 为我们提供的操作字节的二个类OutputStream和InputStream。字节操作类可以处理任何的数据,图片,文字,声音,视频等等。我们知道世界上的文字类型比较多,不可能使用8位的二进制数表示完成这些所有的文字,因此我们必须给文字进行编码,有二个字节或者二个以上的字节来表示我们的文字,Java因此提供了专门用来处理文字的类Writer和Reader,这两个类是在字节流处理类的基础上在里面封装了编码表,可以用来直接处理文字。这些是我对这流对象的一点自己的想法,下面我们开始分别介绍这些了的使用,我的想法是从字符流到字节流,再说点其他的流对象。

字节流的二个类是 OutputStream和InputStream,分别用来处理输出和输入。OutputStream 是一个抽象类,这里我们就看看FileOutputStream,来学习这个类的API,其他的子类中的API和这个相似,我们可以根据操作的对象的不同而选择适合的子类去使用,直接在代码中演示吧。

// 创建一个FileOutputStream 对象,这里我们传入的是文件的路径,  传入文件名之后会把文件保存在
// 工作空间目录下面,true 表示这是添加不会覆盖掉原来文件中的内容
       FileOutputStream fileOutputStream = new FileOutputStream("test.txt",true);
       // 这个方法是 write(byte []) 这个要求写入的是byte类型的数组
       fileOutputStream.write("abc".getBytes());
       //这个方法是 write(byte [],int offset,int length) 这个要求写入的是byte类型的数组
       // 从偏移量offset开始,数据的长度是 length
       fileOutputStream.write("abc".getBytes(), 0, 2);
       // 这个方法是 write(int b) 指定的字节写入到流中,我们知道int 是四个字节,这里只写入
       // 其中的低8位
       fileOutputStream.write(98);
       // 操作完成之后关闭资源
       fileOutputStream.close();

关于这个输出流的演示我们就到这里啊,下面看下输入流InputStream,同样的这也是一个抽象的类,我们使用它的子类FileInputStream 类举个例子啊,直接看代码 。


       /**
        * 这里我们把上面程序中写入到test.txt 中的 abcabb 读出来并打印到控制台上
        * 在FileInputStream 中read 方法有下面的三种
        * read() 从流中读取一个字节 如果遇到结束则返回的是 -1
        * read(byte [] b)从流中最多读取 b的长度个字节
        * read(byte [] b ,int off,int length)
        * 从此输入流中将最多 len 个字节的数据读入一个 byte 数组中。
        * 上面的读取操作 读取结束的标志都是 -1
        * 下面的操作是比较主流的操作,我们可以定义一个数据,去循环的读取操作数据
        * 当没有读取结束的时候返回的是读取的数据的个数
        */
       FileInputStream fileInputStream  = new FileInputStream("test.txt");

       byte [] my_byte = new byte[2];
       int number = 0;
       while((number = fileInputStream.read(my_byte))!=-1)
           System.out.println(new String(my_byte, 0, number));
       
       fileInputStream.close();// 读取完成之后关闭资源   

字节流的学习我们就到这里啊,下面我们看看字符流。字符流的二个类是Reader和Writer 分别用来进行数据的输入和输出。我们首先来看下Reader,我把对这类的描述写在代码的注释中了,开始直接上代码:

/**
         * 继续操作我们在上面写好的文件 test.txt  abcabb
         * Reader 中读取的方法我们看下
         * read()读取的是单个的字符
         * read(char [] c) 将字符读入数组
         */
    FileReader fileReader = new FileReader("test.txt");

    char [] my_char = new char[2];
    int number = 0;
    while((number=fileReader.read(my_char))!=-1)
        System.out.println(my_char);
    

下面是Writer的例子:

/**
         * Wrtier 中我们还是使用操作文件的类FileWriter 来演示
         *
         */
        FileWriter fileWriter = new FileWriter("test.txt",true);
        fileWriter.write("juijijji"); // 写入一个字符串
        fileWriter.flush();
        fileWriter.close();

大家看到上面的例子中同样是输出数据OutputStream和Writer 在写入数据之后会进行不同的操作,Writer需要进行flush() 才可以把数据写入到目的地址中去,而OutputStream 不需要进行这样的操作,这是为什么那?我们从中可以看出来对字节流的处理是在获取之后,立刻把这个拿到的字节给写入到目的地中去,但是对于字符操作的时候,我们知道在拿到一个字节之后并不能马上进行操作,因为在操作文字的时候,一个文字的字符会有多于一个字节的情况,我们并不能马上进行处理,只能将拿到的字节先缓存起来,等到缓存区满了,或者调用了close 或者flush 之后对照编码表刷新流,把数据写入到目的地中去。

在上面的文章中我们分别介绍了操作流的四个类,OutputStream ,InputStream ,Reader和 Writer。 还记的我们在文章的刚开始的时候说过的一个问题吗,对字符流的操作我们是在字节流中指定字符集而得到的,现在回到这个问题上,我们重新再看看这个问题。通过查看jdk 的API我们可以看到不管是Writer的子类 还是 Reader 的子类的构造方法,在创建对象的时候都没有给我们提供可以指定字符集的构造方法,那我们上面说的字节流中指定字符集得到字符流是怎么来的,查看API 我们找到了这样的二个类,我们看看这两个类的信息。

InputStreamReader 类,我们可以看看官方文档对这个类的描述(这段描述真的很好,请允许我直接把API上的这段都复制过来):

InputStreamReader 是字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。

每次调用 InputStreamReader 中的一个 read() 方法都会导致从底层输入流读取一个或多个字节。要启用从字节到字符的有效转换,可以提前从底层流读取更多的字节,使其超过满足当前读取操作所需的字节。

为了达到最高效率,可要考虑在 BufferedReader 内包装 InputStreamReader。例如:

BufferedReader in
= new BufferedReader(new InputStreamReader(System.in));

我们看看这个类的构造函数,会更加明白的:

InputStreamReader(InputStream in)
InputStreamReader(InputStream in, Charset cs)

我们知道InputStreamReader 是 Reader 的子类,给传入一个InputStream,会得到一个Reader子类的对象,其中第一个方法我们没有指定字符集,这个时候操作字符使用的是系统默认的字符集,我们也可以通过第二个方法 传入一个字符集,那么得到的Reader 对象就是按照我们指定的字符集去处理流中的数据。

同样的我们看OutputStreamWriter,同样官方文档的API描述真的很好,我把描述的信息都给搬过来:

OutputStreamWriter 是字符流通向字节流的桥梁:可使用指定的 charset 将要写入流中的字符编码成字节。它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。

我们看下它的构造函数:

OutputStreamWriter(OutputStream out)
OutputStreamWriter(OutputStream out, Charset cs)

和Reader类似,我们同样指定字符集,将写入流中的字符编码成字节输出。

到这里我相信大家对这四个流对象已经有了比较深入的了解,已经搞清楚了他们之间的关系。我在这里再做一下叙述,帮大家理理思路,理清楚了我们好继续下的学习。我们处理图片声音等非文字信息的时候,只能使用字节流对象,字节流对象处理数据是按照一个字节一个字节去处理的,到了文字处理的这里,由于文字编码的原因我们读取一个字节之后,并不能马上进行处理,需要等读够一个字符后才能进行处理,为了处理字符的方便,我们在这里使用 专门处理文字的类字符流对象,字符和字节流之间也是用关联的,通过OutputStreamWriter 和InputStreamReader 我们实现他们之间的转化,从而使用对方更好的方法处理我们的问题。

把上面的东西做了一点小的总结后,我们继续学习在工作中会使用的比较多的东西啊。我们都知道对硬盘的读取和写入都是很耗时的操作,会耗费系统大量的资源,我们会有这样的想法,如果我们把要写入或者读取的数据做一点缓存,等数据量多一点,一次性的写入或者读取这样会更加高效,确实这样操作会更好一点,Java 其实也给我们提供了这样的处理方法啊,我们就能进行操作,看看提供给我们的类你就知道了。

BufferedInputStream :
为另一个输入流添加一些功能,即缓冲输入以及支持 mark 和 reset 方法的能力。在创建 BufferedInputStream 时,会创建一个内部缓冲区数组。在读取或跳过流中的字节时,可根据需要从包含的输入流再次填充该内部缓冲区,一次填充多个字节。mark 操作记录输入流中的某个点,reset 操作使得在从包含的输入流中获取新字节之前,再次读取自最后一次 mark 操作后读取的所有字节。

BufferedOutputStream:

该类实现缓冲的输出流。通过设置这种输出流,应用程序就可以将各个字节写入底层输出流中,而不必针对每次字节写入调用底层系统。
使用了BufferedOutputStrem 之后我们会把数据先写到缓存区中,并不直接把数据写入到目的地,这个时候和使用OutputStrem 不同的是我们需要使用 flush() 去刷新流才会把数据写到目的地。

BufferedReader: 这个是实现对字符的缓冲的,看官方文档的描述,
从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。
可以指定缓冲区的大小,或者可使用默认的大小。大多数情况下,默认值就足够大了。
通常,Reader 所作的每个读取请求都会导致对底层字符或字节流进行相应的读取请求。因此,建议用 BufferedReader 包装所有其 read() 操作可能开销很高的 Reader(如 FileReader 和 InputStreamReader)。例如,
BufferedReader in= new BufferedReader(new FileReader(“foo.in”));
将缓冲指定文件的输入。如果没有缓冲,则每次调用 read() 或 readLine() 都会导致从文件中读取字节,并将其转换为字符后返回,而这是极其低效的。
通过用合适的 BufferedReader 替代每个 DataInputStream,可以对将 DataInputStream 用于文字输入的程序进行本地化。

我们关注下,缓冲的类给们提供的新的API:

我们可以看到一个新的方法是 readLine(),返回值是String,我们调用这个方法能够在没一次直接读取一行数据,缓冲为我们提供了更高效的读取方法。

BufferedWriter:

将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。
可以指定缓冲区的大小,或者接受默认的大小。在大多数情况下,默认值就足够大了。
该类提供了 newLine() 方法,它使用平台自己的行分隔符概念,此概念由系统属性 line.separator 定义。并非所有平台都使用新行符 (‘\\n’) 来终止各行。因此调用此方法来终止每个输出行要优于直接写入新行符。

看到这里我想大家会有这样的疑问,这也是我的疑问,直到昨天我才对这个问题进行了思考,在网上找了相关的资料,希望对这个问题能进行完美的解决,到底是什么问题那,我想大家都使用过BufferedReader ,Reader,以及BufferWriter 和 Writer ,难道这些之间的区别仅仅在于多了个readLine() 和newLine() 方法吗,如果我们在读或者写的时候不使用这两个方法,使用缓存区是不是没有作用,这个问题我思考了好久,在晚上找了资料,看了看,最后看官方API的时候才恍然大悟,其实Reader和Writer 也使用了缓存,即使我们不去调用flush() 或者close(),对Writer 也会在缓存区满了之把数据刷新到目的地去,既然有缓冲的机制,为什么还提供缓冲区的类,我们有理由相信,Writer和Reader 自带的缓冲效果不是很好,我们需要使用BufferedReader和BufferedWriter 来提供更好的功能,并且我们可以自己指定缓冲区的大小,是不是感觉很强大啊。缓冲区都有一个机制是,如果你不直接调用flush() 或者close() 进行强制的刷新,缓存区的数据满了之后才会进行的刷新缓冲区中的数据去目的地的操作。

上面我们了解了基础的流操作类,这些只是比较基础的操作类,Java针对不同的操作对象还给我们提供了一些常见的其他流操作类会更加方便我们的工作,下面我对这些常见的操作类做点介绍,这些类都可以看成是对基本流的功能增强的流对象。

PrintWriter:这是一个打印流类,这个类是Writer 的子类,提供的方法可以实现格式化的打印,比如API中的方法,println() 就可以实现打印并且换行的操作,不用我们自己去进行换行的操作。

ObjectInputStream和ObjectOutputStream 这两个是可以用来对数据的持久化和持久化数据的读取,使用ObjectInputStream 写入的数据只能使用ObjectOutputStream 读取出来。

PipedInputStream和PipedOutputStream这分别是管道输入和输出流。使用管道流的时候我们需要管道的输入和输出分别在不同的线程中进行操作。我们在输入流中写入的数据可以在输出流中直接读取出来。

以上就是Java 中的流对象,我的一点想法和思考,最重要的是OutputSream ,InputStream ,Writer和Reader,我们需要好好的进行理解,我写这篇博客的主要意图就是让大家能好好理解下这个,后面的算是升级的类,都比较简单,理解了前面的东西,在使用的时候查下API大家肯定会很快会用的,我对这个流的理解目前也仅限在这个水平上,里面肯定有描述欠恰当的地方,希望大家发现后及时给我指出来,我好进行修改,本想高屋建瓴的给写篇博客,可是写着发现自己的水平还是有限很多东西不能很好的描述给大家,望谅解,不甚感激。

以上是关于Java 中的流的主要内容,如果未能解决你的问题,请参考以下文章

“等待”Java中的流[重复]

Java输入/输出(学习总结)

15_IO流

如何使用 for 遍历 Java 中的流? [复制]

Java基础学习——I/O

JAVA基础Java IO详解