Java中的I/O流可以实现数据的输入与输出操作。这里的输入与输出是相对而言的,比如文件与程序之间的交流:

191937217.png

   一:流

   Java中把不同的输入源(如文件,网络资源等)/输出源(如屏幕灯)抽象为“流“(可以把它看做一个装数据的容器),这里的流指的是一连串流动的字符,流动方式为先进先出。

二:流的分类

   1:按流处理的内容

   (1)字节流:两个基础的类为InputStream和OutputStream;

   (2)字符流:两个基础的类为Reader和Writer。

2:按流的方向

   (1)输入流:只能从中读取数据,而不能向其中写入数据。输入流构造方法中参数指定的文件必须存在。如:InputStream、Reader

   (2)输出流:只能向其中写入数据,而不能从中读取数据。输出流构造方法中参数指定的文件不一定要存在,不存在可以重新创建。如:OutputStream、Writer

   3:按流的角色

   (1)基础流:构造方法的参数为介质(比如一个File文件等)。比如InputStream/Reader派生的类,都有一个read()方法,只能用于读取单一的字节或字节数组

   (2)包装流:构造方法的参数为基础流,因此有人称它为"基础流的流"。通过多种基础流之间的相互组合,实现的是对一个已经存在的流的封装,通过所封装的流的功能来丰富流的一些操作。比如包装流DataOutputStream通过对基础流OutputStream的封装,可以实现写一些不同于byte类型数据的方法,如writeUTF()

   三:流的简单划分

下面的图只是先简单的概括一下,有助于我们以后的学习。

195933379.png

   1:从上面的图中可以找到几对儿相呼应的流,你可能会问,我们就是完成一个输入输出,需要这么多复杂的流吗?

   答案:是一定的。尽管我们有包装流,可以丰富基础流的操作,但是不要忘记包装流是基础流的没有基础流的基本操作,包装流也是没有意义的。

  2: 那么这么多的流之间有什么区别呢?

       (1)FileInputStream/OutputStream:完成的是文件的字节读取,只能读取一个字节或者一个字节数组,类型比较单一且读写效率不高。

 构造方法:InputStream 流的名称 = new FileInputStream/OutputStream(new File("文件路径字符串"));分别实现输入流、输出流的创建。

   一般方法有:

       a:int read():从此输入流中读取一个数据字节。如果已到达文件末尾,则返回 -1

       b:int read(byte[] b):从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。返回读入的字节总数。如果因为已经到达文件末尾而没有更多的数据,则返回 -1

       c:int read(byte[] b,int off,int len):从此输入流中将最多 len 个字节的数据读入一个 从off位置坐标开始的byte 数组中。返回读入的字节总数。如果因为已经到达文件末尾而没有更多的数据,则返回 -1

       d:void write(int b):将指定字节写入此文件输出流。实现 OutputStreamwrite 方法。

e:void write(byte[] b,int off,int len):将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此文件输出流。

       f:void write(byte[] b):将 b.length 个字节从指定 byte 数组写入此文件输出流中。

       d:voidclose():关闭此文件输入流/输出流,并释放与此流有关的所有系统资源。切记:一定要操作结束后,关闭流。

(2)DataInputStream/DataOutputStream:这一对弥补了上一组中的”类型比较单一“这一问题,他可以实现以读写不同数据类型的数据来操作流。可见他是上一组的包装流。

一般方法有:(1)中的方法在这里依然适用。传递和操作的仍是字节。(为说明问题,这里只给出几对儿特殊的方法,具体请参见帮助文档)

       a:readUTF()/writeUTF(String str):以与机器无关方式使用 UTF-8 修改版编码将一个字符串写入基础输出流。  

b:readChar()/writeChar(Char ch):将一个 char 值以 2-byte 值形式写入基础输出流中,先写入高字节。

(3)BufferedInputStream/BufferedOutputStream:他的出现解决了一中的”读写效率不高“的问题。可见他是第一组的包装流。

一般方法有:(1)中的方法在这里依然适用。传递和操作的仍是字节。这里只给出特殊的方法

       a:void flush():刷新此缓冲的输出流。这迫使所有缓冲的输出字节被写出到底层输出流中。 前面提到过缓冲区的概念,这一对儿在实现输入或输出时,实现先把数据写到缓冲区中,然后用此方法可以实现冲刷的功能,把存在于缓冲区中的数据”冲刷“到文件中。特殊的几点:

       ◆close():在关闭流之前也会冲刷缓冲区中的数据到文件中

       ◆缓存区满了以后,即时不用flush()重刷一下,也会自动写道文件中去

       ◆不flush,不close则拷贝的文件字节数相对而言较少。因为缓存区满了以后,会自动写道文件中去。假使最后剩了5K,一次flush8K,则最后的5K不够他冲刷一次的,就留在缓冲区中了,所以就少了一点

       ◆flush不可以写到循环当中,没有意义。因为那相当于读一个字节,就放到缓冲区,就重刷了,会和以前的FileInputStream一样慢。

(4)FileReader/FileWriter:完成的是文件的字符的读取,只能读取一个字符或者一个字符数组。按系统默认的编码方式(gbk)提供字节到字符的转换。

一般方法有:(1)中的方法在这里依然适用。只不过注意传递和操作的是字符。

(5)InputStreamReader/OutputStreamWriter:完成的是文件的字符的读取。提供了不同的编码方式,已解决乱码的问题。对于中文来说读的是2个字节,UTF-8编码为3个字节可见他是第一组的包装流。

一般方法有:(1)中的方法在这里依然适用。只不过注意传递和操作的是字符。

   (6)BufferedReader/BufferedWriter:完成的是文件的字符的读取,实现数据的大量读取。可以readLine()读一行/newLine()重新开一行。可见他是上一组的包装流,当然也可以是其他对应组的。

一般方法有:(1)中的方法在这里依然适用。只不过注意传递和操作的是字符。

   a:readLine():读一行

   b:newLine():重新开一行。

   四:各种流的具体使用

   注意:在这里为说明问题,则吧异常均在main方法后向上抛出,在实际的开发中是不允许的。

   1:FileInputStream/OutputStream

import java.io.*;
public class TestFISFOS {
    public static void main(String[] args) {
        InputStream is = null;
        OutputStream os = null;
        try {
            // 输入流的文件必须存在
            is = new FileInputStream(new File("d:\\a.txt"));
            char j = (char)is.read();// 从a.txt中读取一个字节,返回的对应的码值,强转
            System.out.println(j);
            // 输出流的文件不必须存在
            // true表示在源文件基础上追加, false表示覆盖。默认是false
            os = new FileOutputStream(new File("d:\\a.txt"),true);
            os.write(43);// 向b.txt中写了一个“+”号
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();// 关闭输入流
                os.close();// 关闭输出流
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}


   测试:测试之前在D盘下建一个a.txt文件,文件中写入hello world

   结果:在源文件基础上追加了一个+号。

h

(2)DataInputStream/DataOutputStream

import java.io.*;
public class TestDISDOS {
    public static void main(String[] args) throws IOException {
        OutputStream os = new FileOutputStream(new File("d:\\a.txt"));
        DataOutputStream dos = new DataOutputStream(os);//包装基础流OutputStream
        dos.writeUTF("中国");//写UTF类型的数据
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
        InputStream is = new FileInputStream(new File("d:\\a.txt"));
        DataInputStream dis = new DataInputStream(is);//包装基础流putStream
        String str = dis.readUTF();//用对应的方法读取数据
        System.out.println(str);
        dis.close();
        dos.close();
    }
}

测试:测试之前在D盘下建一个a.txt文件,文件中写入hello world

结果:在源文件中写了一个乱码的数据

(3)BufferedInputStream/BufferedOutputStream

   实现了大量数据的快速读取。

import java.io.*;
public class TestBISBOS {
    public static void main(String[] args) throws IOException {
        InputStream is = new FileInputStream(new File("d:/src.rar"));//创建基础输入流
        BufferedInputStream bis = new BufferedInputStream(is);//包装基础流InputStream
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              
        OutputStream os = new FileOutputStream(new File("d:/dest.rar"));//创建基础输出流
        BufferedOutputStream bos = new BufferedOutputStream(os);//包装基础流OutputStream
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              
        int  i = 0;
        while((i = bis.read())!=-1){//判断是不是读到了文件的末尾
            //缓存区满了以后,即时不用flush重刷一下。也会自动写道文件中去
            bos.write(i);
        }
        bos.flush();//把文件从缓冲区冲刷到文件中去
        bos.close();//在关闭之前,也flush以下
        /**
         * 1不flush,不close则拷贝的文件字节数相对而言较少,因为,缓冲区满了以后会自动写到
         * 文件中. 如果最后剩了5K,不够他flush一次的,则把它留到缓冲区中了,所以少了
         * 2:flush写到循环里,没有意义,和以前的FileInputStream一样慢。因为那相当于读一个
         * 字节放到缓冲区.就重刷了。
         */
    }
}

结果:可以试一试,用FileInputStream/OutputStream完成此操作的速度,这个相对而言快很多。

(4)FileReader/FileWriter:

import java.io.*;
public class TestFRFW {
    public static void main(String[] args) throws IOException {
        FileReader fr = new FileReader(new File("d:\\a.txt"));//创建基本流输入对象
        char ch = (char) fr.read();//读的是字符
        System.out.println(ch);
        FileWriter fw = new FileWriter(new File("d:\\a.txt"));//创建基本流输出对象
        fw.write("北京欢迎你");//写的是字符串
        fw.close();
        fr.close();
    }
}

   测试:测试之前在D盘下建一个a.txt文件,文件中写入"哈尔滨欢迎你"

   结果:以正确的编码方式,覆盖了源文件,写入了”北京欢迎你“

   (5)InputStreamReader/OutputStreamWriter

   a:用不同的方式读写

public class TestISROSW {
    public static void main(String[] args) throws IOException { 
        File file = new File("d:/a.txt");   
        //utf=8读的是三个字节,gbk读的是两个字节,英文字母都是是一个字节
        FileInputStream fis =new FileInputStream(file);
        InputStreamReader isr = new InputStreamReader(fis,"gbk");//包装基础输入流且指定编码方式
        char i = (char)isr.read();
        //用utf-8的格式去写,3个字节,用gbk去读两个字节,所以出现乱码,
        System.out.println(i);
        FileOutputStream fos = new FileOutputStream(file);
        OutputStreamWriter osw = new OutputStreamWriter(fos,"utf-8");//包装基础输出流且指定编码方式
        osw.write("北京");    
        osw.close();
        isr.close();    
    }
}


结果:用utf-8的格式去写,3个字节,用gbk去读两个字节,所以出现乱码

b:以相同的方式读取

public class TestISROSW {
    public static void main(String[] args) throws IOException { 
        File file = new File("d:/a.txt");   
        //utf=8读的是三个字节,gbk读的是两个字节,英文字母都是是一个字节
        FileInputStream fis =new FileInputStream(file);
        InputStreamReader isr = new InputStreamReader(fis,"gbk");//包装基础输入流且指定编码方式
        char j = (char)isr.read();
        System.out.println(j);
        //用gbk的格式去写,2个字节,用gbk去读两个字节,所以不出现乱码,
        FileOutputStream fos = new FileOutputStream(file);
        OutputStreamWriter osw = new OutputStreamWriter(fos,"gbk");//包装基础输出流且指定编码方式
        osw.write("北京");    
        osw.close();
        isr.close();    
    }
}

    结果:用gbk的格式去写,2个字节,用gbk去读两个字节,所以不出现乱码

(6)BufferedReader/BufferedWriter:

public class TestBWBR {
    public static void main(String[] args) throws IOException {
        File file = new File("d:\\a.txt");//创建基础文件
        FileInputStream fis = new FileInputStream(file);//创建基础输入流
        InputStreamReader isr = new InputStreamReader(fis);//创建一层包装流
        BufferedReader br = new BufferedReader(isr);//创建二层包装流
        String str = br.readLine();//读一行
        System.out.println(str);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         
        FileOutputStream fos = new FileOutputStream(file);//创建基础输出流
        OutputStreamWriter osw = new OutputStreamWriter(fos);//创建一层包装流
        BufferedWriter bw = new BufferedWriter(osw);//创建二层包装流
        bw.write("马上就出发");//写的字符串
        bw.newLine();//回车换行
        bw.write("谁也不知道");//在写一行
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         
        bw.flush();
        bw.close();
        br.close();
    }
}

测试:测试之前在D盘下建一个a.txt文件,文件中写入"一二三四五六七"

结果:在a.txt中第一行为:马上就出发,第二行为:谁也不知道

马上就出发

   五:关于脏数据    

   所谓的脏数据是指不必要的数据。比如读多了,看如下的程序注释

public class TestWriteANDRead {
    public static void main(String[] args) throws Exception {   
            coppyFile("d:/a.txt", "d:/aa.txt");//拷贝函数的调用    
    }
    //自定义拷贝函数
    private static void coppyFile(String file1, String file2)
            throws IOException {
        InputStream is = null;//定义基础输入流
        OutputStream os = null;//定义基础输出流
        is = new FileInputStream(new File(file1));// 源文件地址
        os = new FileOutputStream(new File(file2)); // 目标文件地址
        int  i=0;//用来装以后read()的返回值
        byte[] b = new byte[8*1024];//定义一个byte数组,实现一次读8个字节
        while ((i = is.read(b)) != -1) { // 判断是不是读到了文件末尾
            os.write(b,0,i);// 没读到的话,就从a.txt读一个写到aa.txt中一个
            //达到了解决脏数据的目的
            /**
             * 一次度八个k,最后很可能就不够8k了,可能就只有5K,那么剩下的8K包含的是:
             * 剩下的那5K以及最后一个8K的后3K。 所以就出现了后面那3K的脏数据
             */
        }
        is.close();
        os.close();
    }
}

六:重定向输入输出流

我们知道从控制台读入数据用的是System.in,向控制台输出数据用System.out,即默认的输入设备为键盘,默认的输出设备是屏幕。那么我们可以通过System.setIn()方法和System.setOut()方法改变默认的输出与输入设备,下面的列子说明的是如何将默认的输入输出变为文件的:

public class TestSISO {
    public static void main(String[] args) throws IOException {
        File file = new File("d:\\a.txt");//创建基本文件
        PrintStream ps = new PrintStream(file);//定义包装流,使其写入到文件
        System.out.println("输出到控制台");//重定向之前,在控制台输出的
        System.setOut(ps);//输入输出流的重定向问题,改变了默认的输出设备
        System.out.println("输出到文件");//这句话在a.txt中可以看到
        ps.println("hello word");//在后面追加一句
        //PrintStream的println方法与BufferedReader的readLine方法经常一起使用
        InputStream is = System.in;
        InputStreamReader isr = new InputStreamReader(is);
        BufferedReader br = new BufferedReader(isr);
        String str = br.readLine();//读一行
        System.out.println(str);
    }
}

   结果:

   在控制台中的结果如下:

输出到控制台

     在a.txt文件的结果为

输出到文件
hello word


Java中虽然提供了这些看起来很麻烦的IO流操作,但是只要细心,常用就不会有问题,我们都一样,加油才是硬道理!!t_0016.gif