黑马程序员-Java I/O体系

java.io在jdk中的定义:为系统提供输入和输出通过数据流,序列化以及文件系统。

在学习IO体系的时候,我们可以遵循这样的规律:

     看父类的共性功能,用子类的对象。

而且,IO体系的好处在于:

     每个子类的后缀名都是所属体系的父类的名称,很容易区分所属的体系;而且每个子类前缀名都是该子类对象的功能体系。

 

Java IO体系:

|OutputStream

    |字符流抽象基类

    |Writer

常用的字符型的节点流有:

  • 文件:FileReader, FileWriter
  • 内存(数组):CharArrayReader,CharArrayWriter
  • 内存(字符串):StringReader,StringWriter
  • 管道:PipedReader,PipedWriter

 

下面先讲解FileWriter

示例:将一段文字数据写入到硬盘上

import java.io.FileWriter;  
import java.io.IOException;  
  
public class FileWriterDemo {  
    public static void main(String[] args) throws IOException {  
        /*   
        //需求:将一个段文字数据写入到硬盘上. 
        思路: 
        1,一段文字就是字符串数据。 
        2,写到硬盘上,从哪到哪呢?字符串数据在内存中,写到硬盘上——将内存中的数据搞到硬盘上, 
        这就涉及到了设备之间的数据处理。就要用到IO技术。 
        既然是从内存到硬盘,应该是输出流。 
        3,对于文字而言,io中提供了便捷的操作,比如字符流。 
        4,结合两者,需要输出流,需要字符流,可以使用字符输出流。Writer 
        5,具体用哪个子类对象呢?硬盘上用于存储数据的体现:文件。在这个体系中有对象FileWriter 。  
         */  
          
          
        //1,通过FileWriter创建流对象。构造时,必须明确写入数据需要存储的位置。   
        /* 
         * 该对象一创建,目的文件就会被创建。 
         * 如果该文件已经存在,会被覆盖。  
         * 做了什么事呢?在堆内存中创建一个对象。同时调用了系统的资源。 
         */  
        FileWriter fw = new FileWriter("demo.txt");  
          
        //2,使用字符输出流对象将字符串进行写入。调用写入方法。  
        //数据没有直接写入到目的文件中,而是写入到了临时缓冲中。  
        fw.write("abcdef");  
          
        //3,怎么把数据弄到文件中呢?发现Writer类中有一个flush()方法。刷新缓冲区,将缓冲的数据立即写入到目标中。  
        fw.flush();  
          
        fw.write("haha");         
        //4,关闭此流,关闭资源。在关闭之前,先刷一次缓冲,将数据都写入到目的中。  
        fw.close();  
                  
        /* 
         * flush()和close()有什么区别? 
         * flush():仅将缓冲中的数据刷新到目的地。流对象可以继续使用。可以使用多次。 
         * close():将缓冲中的数据刷到目的地后,直接关闭流资源,流无法继续使用。只能使用一次。 
         * 在close()方法当中其实在关闭之前都会调用一次flush(); 
         *  
         */  
    }  
}  

 示例展示了FileWriter的基本流程,但是在程序中的很多地方都会发生异常,因此,有必要了解Java中IO体系的异常处理规范。

 

示例二:IO异常处理

import java.io.FileWriter;  
import java.io.IOException;  
public class FileWriterDemo3 {  
      
    public static void main(String[] args) {  
        /* 
         * IO异常的处理规范。  
         * 创建流对象—————— 在try外创建流对象的引用。 在try内对流对象进行初始化。 
         */  
        FileWriter fw = null;  
        try {  
            fw = new FileWriter("k:\\demo3.txt");  
  
            fw.write("abcde");  
            fw.flush();  
  
        } catch (IOException e) {  
  
            System.out.println(e.toString());  
        } finally {  
            //在关闭之间必须得判断这个流是否存在,是否被创建。  
            if (fw != null)  
                try {  
                    fw.close();  
                } catch (IOException e) {  
  
                    // 相关的代码处理。比如说,将关闭失败的信息记录到日志文件中。  
                    throw new RuntimeException("关闭失败");  
                }  
        }  
    }  
}  

 

FileReader主要用于读取,读取方式的不同也必然导致效率的不同。

1. 读取单个字符:读取单个字符,在字符可用、发生I/O错误或者已到达流的末尾前,此方法一直阻塞。返回值为读取到的字符数,如果已到达流的末尾,则返回-1.

2. 读取字符数组:将读取到的字符存储到字符数组里面,并且返回字符数组的长度。如果到达流的末尾,返回-1.

import java.io.FileNotFoundException;  
import java.io.FileReader;  
import java.io.IOException;  
  
public class FileReaderTest {  
  
    public static void main(String[] args) throws IOException {  
        // TODO Auto-generated method stub  
        FileReader fr = new FileReader("IO流.txt");  
          
        //演示读取单个字符  
        long time = System.currentTimeMillis();  
        readChar(fr);  
        long timereadChar = System.currentTimeMillis();  
          
        System.out.println("time Read char is  = " + (timereadChar-time));  
        //演示读取字符到缓冲区中。  
          
        long time2 = System.currentTimeMillis();  
        readToBuf(fr);  
        long timeReadBuf = System.currentTimeMillis();  
        System.out.println("time Read to Buf is =  " + (timeReadBuf-time2));  
          
  
    }  
    private static void readToBuf(FileReader fr) throws IOException {  
        //定义一个字符缓冲区,用于存放读到的字符。  
        char[] buf = new char[50];  
        //设刚开始读到的字符为0  
        int len = 0 ;  
        //一直循环读取字符到缓冲区中,直到读到流的末尾。  
        while((len = fr.read(buf)) != -1){  
            //将每次读满的缓冲区中的字符,变成字符串打印出来。  
            System.out.println(new String(buf , 0 , len));        
        }     
    }  
    private static void readChar(FileReader fr) throws IOException {  
        //设每个读取到的字符整数值为ch.  
        int ch = 0;   
        //循环读取字符,直到流的末尾  
        while((ch = fr.read()) != -1){  
            //将读取到的字符,强制转换为 char  
            System.out.print((char) ch);  
        }  
    }  
}  

 

缓冲区:
流对象的read():是从目的地一次读取一个。

缓冲区的read():是通过流对象的read将一批数据读取到缓数组,然后再从数组中一次取一个,所以内存操作要比硬盘操作要高效。

 

在Java中,为了提高程序的效率,为我们提供了对应的类:

BufferedReader和BufferedWriter。

示例三:

import java.io.BufferedWriter;  
import java.io.FileWriter;  
import java.io.IOException;  
  
public class BufferedWriterDemo {  
  
    public static void main(String[] args) throws IOException {  
  
        //创建一个流对象。  
        FileWriter fw = new FileWriter("buf.txt");  
          
        //为了提高效率。创建缓冲区对象,并和要被提高效率的流相关联。   
        BufferedWriter bufw = new BufferedWriter(fw);  
      
        for(int x=0;x<4; x++){  
            bufw.write(x+"--hahaha");  
            //写入一个换行符。  
            bufw.newLine();  
            //要对缓冲区进行刷新。记住:一般只要使用了缓冲区,就一定要刷新。   
            bufw.flush();  
        }  
          
        //关闭缓冲区。   
        bufw.close();//问:还用关闭fw.close()?不用,因为关闭缓冲区,其实就是在关闭缓冲区关联的流。  
          
    }  
} 

示例四:

import java.io.BufferedReader;  
import java.io.FileReader;  
import java.io.IOException;  
  
public class BuffereReaderDemo {  
  
    public static void main(String[] args) throws IOException {  
        /** 
         * 演示:BufferedReader 
         * 1. 先有字符读取流; 
         * 2. 该类有一个特有方法。readLine(). 一次读一行。 
         */  
        //创建读取流对象 和 文件相关联。  
        FileReader fr = new FileReader("Demo.txt");  
          
        //创建读取缓冲区对象 和 流对象关联对其进行高效 操作;  
        BufferedReader  bufr = new BufferedReader(fr);  
        //简写形式:  
        //BufferedReader bufr = new BufferedReader(new FileReader("Demo.txt"));  
          
        String line = null ;  
        while((line = bufr.readLine()) != null){  
            System.out.println(line);  
        }  
        bufr.close();  
    }  
}  

 

BufferWriter的出现,增强了Writer体系中的功能,这种设计方式比原理更为灵活,避免了继承的臃肿。由此引出了java的又一种设计模式-装饰设计模式

 

具体实例: 

Writer

            |MediaWriter

运用缓冲技术提高效率:

 

Writer

            |BufferTextWriter

            |BufferMediaWriter

            |BufferDataWriter

通过继承的方式提高了效率。但是对于扩展性是一个问题。而且所需的功能越多,子类就越多。一旦加入新类,就需要为它提供高效。

 

既然都需要缓冲,对数据写入效率进行提高 。可以将缓冲技术单独进行封装。哪个对象需要缓冲,就把哪个对象传递给缓冲对象即可。

        class Buffer{

            Buffer(TextWriterw){

            }

            Buffer(MediaWriterw){

            } 

        }

体系就变成了这样:

        Writer

            |MediaWriter

            |--BufferWriter

        BufferWriter的出现,增强了Writer体系中的功能。

字符流的常用对象和装饰设计模式简单讨论完了。

 

字符流处理的数据都是文字,需要将指定的数据,查指定的编码表。而字节流处理的数据不一定都是文字数据,所以不需要指定查表,直接在操作文件数据的时候,就将具体的字节数据写入到目的地址。

下面通过一个示例演示。

示例:copyMp3

import java.io.BufferedInputStream;  
import java.io.BufferedOutputStream;  
import java.io.FileInputStream;  
import java.io.FileOutputStream;  
import java.io.FileReader;  
import java.io.FileWriter;  
import java.io.IOException;  
  
public class CopyMp3Test {  
  
    public static void main(String[] args) throws IOException {  
  
        long start = System.currentTimeMillis();  
        copy_1();  
        long end = System.currentTimeMillis();        
        System.out.println("毫秒值:"+(end-start));  
    }  
//不用缓冲区。  
    public static void copy_4() throws IOException {  
          
        FileInputStream fis = new FileInputStream("C:\\0.mp3");  
      
        FileOutputStream fos = new FileOutputStream("c:\\4.mp3");  
          
        int by = 0;  
          
        while((by=fis.read())!=-1){  
            fos.write(by);  
        }  
          
        fos.close();  
        fis.close();  
          
    }  
    //不建议。使用刚刚好的缓冲区。因为文件过大会溢出。  
    public static void copy_3() throws IOException {  
          
        FileInputStream fis = new FileInputStream("C:\\0.mp3");       
        FileOutputStream fos = new FileOutputStream("c:\\3.mp3");  
          
        byte[] buf = new byte[fis.available()];  
          
        fis.read(buf);  
          
        fos.write(buf);  
          
        fos.close();  
        fis.close();  
          
    }  
  
    //使用字节流已有的缓冲区。   
    public static void copy_2() throws IOException {  
          
          
        FileInputStream fis = new FileInputStream("C:\\0.mp3");       
        FileOutputStream fos = new FileOutputStream("c:\\2.mp3");  
          
        BufferedInputStream bufis = new BufferedInputStream(fis);  
        BufferedOutputStream bufos = new BufferedOutputStream(fos);  
          
//      //3,定义一个字节缓冲区。  
//      byte[] buf = new byte[1024];  
          
        int by = 0;  
          
        while((by=bufis.read())!=-1){  
            bufos.write(by);  
        }  
          
        bufos.close();  
        bufis.close();  
          
          
    }  
  
    //自定义数组缓冲区的方式。   
    public static void copy_1() throws IOException {  
          
        //1,读取流对象,和mp3关联。  
        FileInputStream fis = new FileInputStream("C:\\0.mp3");  
        //2,写入流对象,明确存储mp3数据的目的。  
        FileOutputStream fos = new FileOutputStream("c:\\1.mp3");  
          
        //3,定义一个字节缓冲区。  
        byte[] buf = new byte[1024*8];  
          
        int len = 0;  
          
        while((len=fis.read(buf))!=-1){  
            fos.write(buf,0,len);  
        }  
          
        fos.close();  
        fis.close();  
          
    }  
  
}  

通过示例可以发现,字节流不需要flush(),但close()仍是必须的。

 

原因:字节流直接是字节数据在源地址和目的地址之间的存储,而字符流会将数据进行临时存储,根据编码表解析成二进制数据后才进行传输,在底层也使用了字节流。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。