Java IO-常用流总结

在java中,IO流是一个很重要的知识点。每一个学习java的人必然需要了解java IO流。流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。

Java IO流,根据名字其实可以看出就是输入流和输出流。I(input)O(output)

Java IO流的体系十分庞大,如下是体系图:
Java IO流体系图

初看到这个图,初学者相信你看了整个人都不好了,因为我看到这张图也是一脸懵逼的,想想java的设计者也真是煞费苦心。

其实,在这么庞大的流体系中,常用的也就那么几个,我们把它们抽取出来,可以总结为如下图:
常用io流

接下来,按照上图的分类,详细介绍这些常用io流。

字节流
1.字节输入流
字节输入流,基类是InputStream(抽象类),FileInputStream和BufferedInputStream是其子类,继承于抽象类InputStream。
(1)FileInputStream:文件字节输入流,计算机中文件在系统中都是以字节的形式保存的,无论是文档文件、视频文件、图片文件等,需要读取这些文件都可以使用FileInputStream去读取器保存在存储介质上的字节序列。
FileInputStream在创建时通过把File对象作为构造参数连接到需要读取的文件的字节内容,建立起字节流传输通道。
然后通过 read()、read(byte[])、read(byte[],int off,int len) 三种方法从字节流中读取 一个字节、一组字节。
(2)BufferedInputStream:带缓冲的字节输入流,我们知道文件字节输入流的读取时,是直接同字节流中读取的。由于字节流是与硬件(存储介质)进行的读取,所以速度较慢。而CPU需要使用数据时通过read()、read(byte[])读取数据时就要受到硬件IO的慢速度限制。我们又知道,CPU与内存发生的读写速度比硬件IO快10倍不止,所以优化读写的思路就有了:在内存中建立缓存区,先把存储介质中的字节读取到缓存区中。CPU需要数据时直接从缓冲区读就行了,缓冲区要足够大,在被读完后又触发fill()函数自动从存储介质的文件字节内容中读取字节存储到缓冲区数组。

注: BufferedInputStream 内部有一个缓冲区,默认大小为8M,每次调用read方法的时候,它首先尝试从缓冲区里读取数据,若读取失败(缓冲区无可读数据),则选择从物理数据源 (譬如文件)读取新数据(这里会尝试尽可能读取多的字节)放入到缓冲区中,最后再将缓冲区中的内容返回给用户.由于从缓冲区里读取数据远比直接从存储介质读取速度快,所以BufferedInputStream的效率很高。

下面写一个简单的读取文件的程序来说明这两种字节流:

package javaio;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

public class IODemo {

    /**
     * 字节输入流
     * FileInputStream & BufferedInputStream
     */

    public static void readFileByBytes(String filename){

        File file=new File(filename);

        InputStream in=null;

        System.out.println("以字节为单位读取文件内容,一次读一个字节:");
        try {
            in=new FileInputStream(file);
            int tempbyte;
            //以read方法为例
            while((tempbyte=in.read())!=-1){
                //read返回的是the next byte of data, or -1 if the end of the file is reached.
                System.out.print(tempbyte);
            }

            in.close();
        } catch (FileNotFoundException e) {

            e.printStackTrace();
        } catch (IOException e) {

            e.printStackTrace();
        }
    }

    public static void readFileByBufferedByte(String filename){
        File file=new File(filename);
        InputStream in;
        try {
            in = new FileInputStream(file);
            //BufferedInputStream的构造函数参数是InputStream
            BufferedInputStream bis=new BufferedInputStream(in);

            //以read方法为例
            int tempbyte;
            System.out.println("以字节缓冲流读取文件内容:");
            while((tempbyte=bis.read())!=-1){
                //while(bis.available()>0)也可以使用这个方法
                System.out.print(tempbyte);
            }

            bis.close();
        } catch (FileNotFoundException e) {

            e.printStackTrace();
        } catch (IOException e) {

            e.printStackTrace();
        }

    }

    public static void main(String[] args) {
        String filename="e:/test.txt";
        readFileByBytes(filename);
        System.out.println();
        readFileByBufferedByte(filename);
    }
}

输出的结果是:
以字节为单位读取文件内容,一次读一个字节:
49505152535455565748
以字节缓冲流读取文件内容:
49505152535455565748
可以通过char转换,将ascii码转换为char

这样的看FileInputStream和BufferedInputStream好像没多大的区别,接下来看一组图:这里写图片描述
如果我们把读取文件的过程比喻成从桶里(文件)放水(读取内容),FileInputStream相当于在桶的底部加一根细的管道然后一点一点的往外放水,而BufferedInputStream相当于在细的管道上再套接一个大的管道,然后从大的管道放水。这样是不是容易理解一点。

2.字节输出流
字节输出流的基类是OutputStream(抽象类),FileOutputStream和BufferedOutputStream是其子类。
(1)FileOutputStream:文件字节输出流,是文件字节输入流的逆过程,其实就是在创建时通过文件创建输出流链接到要写入的文件处,然后通过write方法把输出内容写到文件中。
(2)BufferedOutputStream:带缓冲的字节输出流,也可以理解为带缓冲的字节输入流的逆过程,先cpu直接把内容写到内存中的缓冲区,然后写到文件的存储介质。

注:在创建BufferedOutputStream时,通过一个outputStream参数(在创建outputStream时通过文件名建立起输出流)把已经建立的输出流包装成带缓冲的输出流,在内存中创建一个默认大小是8M的缓冲数组;
在程序运行过程中,通过write(int)/write(byte[])方法向缓冲数组写入数据,如果某时刻缓冲数组满了,则自动触发压入操作——把数组内容写到真正的输出流去,传输到文件中。
如果你想在某些write操作后确保内容能及时输出而不等到数组满时自动输出,则可以调用 flush() 方法强行刷新数组,把缓冲数组的内容全部写入输出流。
下面写一个简单的写入文件的程序来说明,这次只写FileOutputStream,另一种带缓冲的字节输出流请读者自行尝试,其实和FileOutputStream差不多可以参考带缓冲的字节输入流。

    /**
     * 字节输出流
     * FileOutputStream
     * BufferedOutputStream
     * @param filename
     */
    public static void writeByByte(String filename){
        File file=new File(filename);

        OutputStream os;
        try {       
            os=new FileOutputStream(file);
            System.out.println("字节输出流写内容到文件:");
            //1的ascii码是49
            os.write(49);
            System.out.println("ok");

        } catch (FileNotFoundException e) {

            e.printStackTrace();
        } catch (IOException e) {

            e.printStackTrace();
        }
    }

测试下:写到文件test1.txt,打开文件显示结果是1.

字符流
字符流是专门用来读写文档文件的高速输入输出流。在字节流中我们提到文件字节流可以读取一切的文件,那为什么还要字符流呢?
首先我们要知道,文档文件在系统中的呈现的原理:文件在系统中以字节的形式存在,当从系统中读取出来的时候会经过系统的某种编码格式进行编码,从而显示成了字符。比如:我们知道的UTF-8或者汉语系统中的GBK编码形式,就可以把中文文档的字节序列读取出来解码成中文呈现。
然而,Java程序是运作在jvm上的,jvm也是一个系统,但是它和文件直接保存的所在系统不一样,有可能双方对文档文件中的字节序列的解码格式不一样。所以,如果直接读取字节序列,然后让jvm来解码呈现的话就有可能出现编码格式不一致而导致的乱码。因此,需要有字符流来解决这个问题。

字符流原理:它可以在创建时,指定流的编码形式,使得读取到的字节序列根据其在系统中保存时采用的编码格式进行解码,然后把解析好的字符交给Java虚拟机使用,这样就避免了文件所在的系统与Java虚拟机解码不一致导致乱码。

1.字符输入流
字符输入流的基类是Reader(抽象类),常用子类有InputStreamReader(其子类是FileReader)和BufferedReader。
(1)InputStreamReader:最基本的字符输入流, 在创建时,通过包装一个连接到文档文件的字节输入流,并指定编码格式(不指定则采用默认字符集)对字节输入流进行解码。然后通过read()/read(char[] cbuf, int offset, int length) 来读取字符(读取过程中已经经过解码,所以获得的结果是字符char而不是byte)。

InputStreamReader(InputStream in)
Creates an InputStreamReader that uses the default charset.
InputStreamReader(InputStream in, Charset cs)
Creates an InputStreamReader that uses the given charset.
InputStreamReader(InputStream in, CharsetDecoder dec)
Creates an InputStreamReader that uses the given charset decoder.
InputStreamReader(InputStream in, String charsetName)
Creates an InputStreamReader that uses the named charset.

(2)BufferedReader:带缓冲的字符输入流,原理与BufferedInputStream一样,都是在内存中维护一个足够大的缓冲区,每次去读时从缓冲区读取数据并解码,缓冲区空了会自动条用fill填充缓冲区。
唯一区别在于:BufferedRead除了read()、read(char[])两个方法外,多了一个 readLine() 方法:读取一个文本行,通过下列字符之一认为某行已终止:换行 (‘\n’)、回车 (‘\r’) 或回车后直接跟着换行。

BufferedReader(Reader in)
Creates a buffering character-input stream that uses a default-sized input buffer.
BufferedReader(Reader in, int sz)
Creates a buffering character-input stream that uses an input buffer of the specified size.
/**
     * 字符输入流
     * InputStreamReader
     * BufferedReader
     * @param filename
     */
    public static void readByChar(String filename){
        File file=new File(filename);

        try {
            //按照字符,一次去读一个字符
            InputStream is=new FileInputStream(file);
            InputStreamReader isr=new InputStreamReader(is,"utf-8");

            int tempchar;
            while((tempchar=isr.read())!=-1){
                System.out.print((char)tempchar);
            }
            isr.close();
        } catch (FileNotFoundException e) {

            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {

            e.printStackTrace();
        } catch (IOException e) {

            e.printStackTrace();
        }
    }
    public static void readByBufferedChar(String filename){
        File file=new File(filename);

        try {
            FileReader fr=new FileReader(file);

            /**
             * 也可以按照这种方式来读取
             * InputStream is=new FileInputStream(file);
             * InputStreamReader isr=new InputStreamReader(is,"utf-8");
             * BufferedReader br1=new BufferedReader(isr);
             */
            BufferedReader br=new BufferedReader(fr);

            //按照一行来读取
            String tempString;
            while(br.ready()){
                tempString=br.readLine();
                System.out.println(tempString);
            }
            br.close();
        } catch (FileNotFoundException e) {

            e.printStackTrace();
        } catch (IOException e) {

            e.printStackTrace();
        }
    }

同样的图解显示下字符流的之间的联系,如上面提到的桶里放水的方式一样:
这里写图片描述

2.字符输出流
字符输出流的抽象基类是 Writer,其具体子类是OutputStreamWriter和BufferedWriter。其实字符输出流和上面提到的字节输出流的形式差不多,底层代码的实现是有差别的,具体的实现细节在这里就不重复说了。有兴趣的可以自行查看官网jdk文档。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值