Java I/O流基础知识点详解

目录

一、Java-IO流的概述

二、流的概念和作用

三、流的分类

       3.1  输入流和输出流

       3.2 字节流和字符流

       3.3 节点流和处理流

四、字节流和字符流

       4.1 字节输入流InputStream

       4.2 输出流OutputStream和Writer


一、Java-IO流的概述

Java输入/输出(Input/Output)系统简称IO,又称为输入/输出流,它是程序设计语言中最基础的部分。输入/输出是指应用程序与外部设备及其他计算机进行数据交流的操作,可以在程序设计中根据需要实现不同类型的输入/输出功能。

Java中IO是通过“流”的形式进行数据的输入和输出,所有数据被串行化写入输出流或从输入流读入。Java的核心库java.io提供了全面的IO接口,包括文件的读写和标准设备输出等。

二、流的概念和作用

 Java中将输入/输出抽象称为流,可以理解为输入/输入的途径。就好像水管,将两个容器连接起来。当程序需要读取或者写入数据时,就会开启一个通向数据源或者目的地的通道,而流是数据在数据源和目的地之间有序运动着的数据序列,该数据序列是有顺序、有起点和终点的,整个过程就好像数据在其中“流”动一样。

个人观点:Java输入/输出流就是在数据源和目的地建立传输通道(作用),根据数据的类型有序的运输到目的地中(本质)。

三、流的分类

3.1  输入流和输出流

       流可以分为两类:输入流和输出流。输入流不关心数据源自何种设备,输出流也不关心数据的目的是何种设备。

        输入流:只能从中读取数据,而不能向其写入数据。(只能读)

       输出流:只能向其写入数据,而不能从中读取数据。(只能写)

       相对于程序来说,输出流是往存储介质或数据通道写入数据,而输入流是从存储介质或数据通道中读取数据,一般来说关于流的特性有下面几点:

       a. 先进先出。最先写入输出流的数据最先被输入流读取到。

       b. 顺序存取。一个接一个地往流中写入一串字节,读出时也将按写入顺序读取一串字节,不能随机访问中间的数据(RandomAccessFile可以从文件的任意位置进行存取操作

       c. 只读或只写。每个流只能是输入流或输出流的一种,不能同时具备两个功能输入流只能进行读操作,对输出流只能进行写操作。在一个数据传输通道中,如果既要写入数据,又要读取数据,则要分别提供两个流。 

       注意的是,Java的输入流主要由InputStream和Reader作为基类,而输出流则主要由OutputStream和Writer作为基类。他们都是一些抽象基类,无法直接创建实例,但是可以通过他们的子类来创建流。例如针对存储在磁盘、光盘或其他存储设备上的文件的字节流,可以通过FileInputStream和FileOutputStream的子类来创建。

3.2 字节流和字符流

       字节流和字符流的用法几乎完全一样,区别在于两者所操作的数据单元不同。流序列中的数据可以是没有进行加工的原始数据(二进制字节数据),也可以是经过编码的符合某种编码格式的数据。其中,字节流以字节为基本单位来处理数据的输入/输出,一般用于对二进制数据的读写,例如声音、图像等数据;字符流以字符为基本单位来处理数据的输入和输出,一般用于文本类型数据的读写,例如文本文件、网络中发送的文本信息等。

       字节流和字符流主要由4个抽象类来表示:InputStream、OutputStream、Reader、Writer,输入和输出各两种。其中InputStream和OutputStream表示字节流,Reader和Writer表示字符流,其他各种各样的流均是继承这4个抽象类而来的。

       个人观点:字节流以字节为单位,通过InputStream和OutputStream抽象类进行读写,而字符流是以字符为单位,通过Reader和Writer抽象类进行读写。

3.3 节点流和处理流

       按照流的角色来分,可以分为节点流和处理流。

       可以从/向一个特定的IO设备(如磁盘、网络)读/写数据的流,称为节点流。节点流也被称为低级流。

       处理流则用于对一个已存在的流进行连接或封装,通过封装后的流来实现数据读/写功能。处理流也被称为高级流。

       从图15.4中可以看出,当使用处理流进行输入/输出时,程序并不会直接连接到实际的数据源,没有和实际的输入/输出节点连接。使用处理流的一个明显好处时,只要使用相同的处理流,程序就可以采用相同的输入/输出代码来访问不同的数据源,随着处理流所包装节点流的变化,程序实际所访问的数据源也相应地发生变化。

四、字节流和字符流

       InputStream和Reader是所有输入流的抽象基类,本身并不能创建实例来执行输入,但它们将称为所有输入流的模板,所以他们的方法是所有输入流都可以使用的方法。

4.1 字节输入流InputStream

       1.InputStream主要包含如下四个方法

       * int read() 从输入流读取一个字节,并转换为0~255的整数最后返回这个整数。为了提高I/O操作的效率,建议使用下面两种形式。

       * int read(byte[] b) 从输入流中最多读取b.length个字节的数据,并将其存储在数组b中,返回实际读取的字节数。

       * int read(byte[] b,int int off,int length) 从输入流中的off位置开始,最多读取length个字节的数据,并将其存储在数组b中,该方法返回实际读取的字节数。

       * void close() 关闭输入流。在读操作完成后,应该关闭输入流,系统将会释放与这个输入流相关的资源。注意,InputStream类本身的close()方法不执行任何操作,但是它的子类重写了close()方法。

       InputStream类的子类

       * ByteArrayInputStream类将字节数组转换为字节输入流,从中读取字节。

       * FileInputStream类从文件中读取数据。

       * PipedInputStream类连接到一个PipedOutputStream(管道输出流)。

       * SequenceInputStream类将多个字节输入流串联成一个字节输入流。

       * ObjectInputStream类将对象反序列化

 

       2. 在Reader里包含如下三个方法

       * int read()  从输入流读取单个字符,返回所读取的字符数据

       * int read(char[] cbuf)  从输入流中最多读取cbuf.length个字符的数据,并将其存储在字符数组cbuf中,返回实际读取的字符数

       * int read(char[] cbuf,int int off,int length)  从输入流中的off位置开始,最多读取length个字符的数据,并将其存储在字符数组cbuf中,该方法返回实际读取的字符数

       Reader类的子类

       * CharArrayReader类将字符数组转换为字符输入流,从中读取字符。

       * StringReader类字符串转换为字符输入流,从中读取字符。

       * BufferedReader类为其他字符输入流提供读缓冲区。

       * PipedReader类连接到一个PipedWriter。

       * InputStreamReader类将字节输入流转换为字符输入流,可指定字符编码。

       InputStream()的基本方法时没有参数的read()方法。这个方法从输入流的源中读取1字节数据,作为一个0~255的int返回,流的结束通过返回-1来表示。read()方法会等待并阻塞其后任何代码的执行,直到有1字节的数据可供读取。输入和输出很慢,所以如果程序在做其他重要的工作,要尽量将I/O放在单独的线程中,下面是一个简单的例子。

//创建文本字节输入流
FileInputStream fis = new FileInputStream("文件路径");
//创建一个长度为1024的“竹筒”
byte[] bbuf = new byte[1024];
//用于保存实际读取字节数
for(int i=0;i<bbuf.length;i++){
    int b = fis.read();
    if(b == -1){
        System.out.println("关闭");
        break;
    }else{
        bbuf[i] = (byte)b;
        System.out.println(b);
    }
}
//关闭文件输入流,放在finally块里更安全
fis.close();

       read()方法都用返回-1来表示流的结束。如果流已经结束,而又没有读取的数据,多字节read()方法会返回这些数据,直到缓冲区清空。-1永远不会放进数组中,因为数组只包含实际的数据,所以如果获取数据的字节数小于数据的存储长度,一定要判断流是否读取完毕,如果读取完毕则结束读取并关闭输入流。例如上面的例子那样,存储数组的长度大于实际的数据长度,下面我们看看存储长度小于实数据长度的情况:
 

      从上图可以看到,循环中没有输出关闭的字符串。和输出流一样,当结束对输入流的操作时,通过调用close()方法将其关闭,释放与这个流关联的所有数据。一旦输入流已经关闭,进一步读取这个流会抛出IOException异常。

 

4.2 输出流OutputStream和Writer

      1.OutputStream主要包含如下五个方法

      * void write(int b)  向输出流写入一个字节,其中c既可以代表字节,也可以代表字符。这里的参数是int类型,但是它允许使用表达式,而不用强制转换成byte类型。

      * void write(byte[] b)  将b字节数组/字符数组的所有数据写入到输出流中。

      * void write(byte[] b,int off,int length)  从b字节数组中的off位置开始,长度为length的子字节写入输出流中。

      * void flush()  将数据缓冲区中全部数据写入到输出流,并清空缓冲区。(为提高效率,在向输出流中写入数据时,数据一般会保存到内存缓冲区,只有当缓冲区中的数据达到一定程度时,缓冲区的数据才会被写入到输出流中。而flush方法则可以强制将缓冲区的数据输入到输出流中)

      * void close()  关闭输出流并释放与流相关的系统资源。

      OutputStream类的子类

      * ByteArrayOutputStream类向内存缓冲区中的字节数组中写入数据

      * FileOutputStream类向文件中写入数据

      * PipedOutputStream类连接到一个PipedIntputStream

      * ObjectOutputStream类将对象序列化

  

      2. Writer主要包含如下几个方法

      因为字符流可以直接以字符作为操作单位,所以Writer可以用字符串来代替字符数组,即以String对象作为参数

      * void write(int b)  向输出流中写入一个字符

      * void write(char[] buf)  将buf字符数组中的所有字符写入到输出流中

      * void write(char[] buf,int off,int length)  从buf字符数组中的off位置开始,长度为length的子字符写入输出流。

      * void write(String Str)  向输出流中写入一个字符串

      * void write(String str,int off,int length)  从str字符串中的off起始偏移量开始,长度为length的子字符写入输出流。

      Writer类的子类

      * CharArrayWriter类向内存缓冲区中的字符数组写数据

      * StringWriter类向内存缓冲区中的字符串写数据

      * BufferedWriter类为其他字符输出流提供写缓冲区

      * PipedWriter类连接到一个PipedReader

      * OutputStreamWriter类将字节输出流转换为字符输出流,可指定字符编码

      下面程序使用FileInputStream来执行输出,并使用FileOutputSteam来执行输出,实现复制指定文件的功能。

try{
    //创建字节输入流
    FileInputStream fis = new FileInputStream("文件路径");
    //创建字节输出流
    FileOutputStream fos = new FileOutputStream("newFile.java");
    //创建字节存储数据,并定义长度
    byte[] bbuf = new byte[32];
    //创建读取结果的存储变量
    int hasRead = 0;
    while((hasRead = fis.read(bbuf)) > 0){
        //如果读取返回的值不为-1,则将每次读取到的32个字节数据写入到输出流
        fos.write(bbuf,0,hasRead);
    }
}catch(IOException ex){
    System.err.println(ex.getMessage());
}

      注意:每执行一次fis.read(bbuf)函数,流中的数据都会往前移动32位字节。

       和网络硬件中缓存一样,流还可以在软件中得到缓冲。一般说来,可以通过把BufferedOutputStream或BufferedWriter串联到底层流上来实现。因此,在写入数据之后,刷新(flush)输出流非常重要。例如,如果输出流有一个1024字节的缓冲区,那么这个流在发送缓冲区中的数据之前会等待更多的数据到达。在服务器响应到达之前不会向流写入更多的数据,但是响应也不会到来,因为请求还没有发送,而flush()方法可以强迫缓冲的流发送数据,即使缓冲区还没有满,以此来打破这种死锁状态。

       个人观点:在缓冲区的数据没有充满之前,请求和数据是不会发送到服务器中,通过flush()方法可以释放缓冲区的数据。

       最后,当结束一个流的操作时,通过调用close()方法将其关闭,释放与这个流关联的所有数据。如果流来自一个网络,那么关闭这个流也会终止这个连接。一旦输出流关闭,继续写入时就会抛出IOException异常。不过,有些流仍允许对这个对象做一些处理,例如:ByteArrayOutputStream仍转换为实际的字节数组,关闭的DigestOutputStream仍然可以返回其摘要。

       为了得到正确的变量作用域,必须在try块之外声明流变量,但是必须在try块内完成初始化。另外,为了避免NullPointerExcepiton异常,在关闭流之前需要检查流变量是否为null。最后,通常都希望希望忽略关闭流时出现的异常,或者最多只是将这些异常写入日志。

OutputStream Out = null;
try{
    Out = new FileOutputStream(“/test/data.txt”);
    //处理输出流...
}catch(IOException ex){
    System.err.println(ex.getMessage());
}finally{
    if(out != null){
        try{
            Out.close();
        }catch(IOException ex){
            //忽略
        }
    }
}

       这个技术有时称为释放模式(dispose pattern),这对于需要在垃圾回收前先进行清理的对象很常见,这个技术不仅用于流,还可以用于Socket、通道、JDBC连接等。

       Java 7引入了“带资源的try”构造,不需要在try块之外声明流变量,完全可以在try块的参数表中声明,所以也就不需要在Finally字句。例如:

try(OutputStream Out = new FileOutputStream(“/test/data.txt”)){
    //处理输出流...
}catch(IOException ex){
    System.err.println(ex.getMessage());
}

       Java会对try块参数表中声明的所有AutoCloseable对象自动调用close();

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值