Java基础—IO流(二)

IO流(二) 字节流

这里写图片描述

一、OutputStream类

1.概述
OutputStream类可以在硬盘上创建一个文件,并写入或添加数据。该类的子类还能实现写入过程中的不同功能。

2.FileOutputStream类
FileOutputStream类用于在硬盘上创建文件,并以字节的形式写入数据。其使用方式和FileWriter类相似,只是以字节的形式操作数据。下面的代码在指定目录创建一个文件并写入自定义数据。

示例代码:

package com.heisejiuhuche.io;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileOutputStreamDemo {
    public static void main(String[] args) {
        FileOutputStream fos = null;
        try {
            /* 创建FileOutputStream对象并关联文件 */
            fos = new FileOutputStream("C:/Users/jeremy/Documents/javaTmp/fos.txt");

            /* 写入数据,通过String类的getBytes方法将字符串转换为字节数组 */
            fos.write("abcdefg".getBytes());
        } catch(FileNotFoundException e) {
            throw new RuntimeException("文件不存在");
        } catch(IOException e) {
            throw new RuntimeException("操作失败");
        } finally {
            try {
                if(fos != null)
                    fos.close();
            } catch(IOException e) {
                throw new RuntimeException("输出流关闭失败");
            }
        }
    }
}

该程序在指定目录创建fos.txt并写入abcdefg

注意:
字节输出流在直接使用的时候不需要调用flush()方法,与字符流不用。由于字符流底层操作的也是字节,同时用的是字节流的缓冲区;该缓冲区中有个数组,用于临时存储数据。要将数组中的数据写入目的地文件,字符流就需要调用flush()方法进行刷新。而字节输出流是对最小单位字节直接进行操作,没有使用具体缓冲区,所以不需要刷新,直接往目的地文件写入数据。

二、InputStream类

1.概述
InputStream类以字节形式读取硬盘上的文件数据。该类的子类还能实现读取过程中的不同功能。

2.FileInputStream类
FileInputStream类用于以字节形式读取文件数据。其特有的方法使创建字节数组有了明确的大小。该类的使用方式和FileReader相似。下面的代码读取文件中的数据。

示例代码:

package com.heisejiuhuche.io;

import java.io.FileInputStream;
import java.io.IOException;

public class FileInputStreamDemo {
    public static void main(String[] args) throws IOException {
        read_1();
        System.out.println();
        read_2();
        read_3();

    }

    private static void read_3() throws IOException {
        FileInputStream fis = new FileInputStream("C:/Users/jeremy/Documents/javaTmp/fos.txt");

        /* available方法返回文件的大小 */
        byte[] buf = new byte[fis.available()];

        fis.read(buf);

        System.out.println("read_3: " + new String(buf));
    }

    private static void read_2() throws IOException {
        FileInputStream fis = new FileInputStream("C:/Users/jeremy/Documents/javaTmp/fos.txt");

        byte[] buf = new byte[1024];

        int len = 0;

        while((len = fis.read(buf)) != -1) {
            System.out.println("read_2: " + new String(buf, 0, len));
        }
    }

    private static void read_1() throws IOException {
        FileInputStream fis = new FileInputStream("C:/Users/jeremy/Documents/javaTmp/fos.txt");

        int ch = 0;
        System.out.print("read_1: ");
        while((ch = fis.read()) != -1) {
            System.out.print((char)ch);
        }
    }
}

程序输出结果:

read_1: abcdefg
read_2: abcdefg
read_3: abcdefg

注意:
如果使用字节输入流读取的文件较大,建议使用1024字节整数倍方法读入数据,而不要使用创建available()方法返回值大小的数组;后者可能造成内存溢出

三、字节流练习

1.拷贝图片到指定目录

示例代码:

package com.heisejiuhuche.io;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class PicCopy {
    public static void main(String[] args) {
        picCopy();
    }

    private static void picCopy() {
        FileInputStream fis = null;
        FileOutputStream fos = null;

        try {
            /* 创建输入输出流并关联文件 */
            fos = new FileOutputStream("C:/Users/jeremy/Documents/me.jpg");
            fis = new FileInputStream("C:/Users/jeremy/Documents/javaTmp/me.jpg");
            /* 创建缓冲区 */
            byte[] buf = new byte[1024];
            int len = 0;
            /* 复制文件 */
            while((len = fis.read(buf)) != -1) {
                fos.write(buf, 0, len);
            }
        } catch(FileNotFoundException e) {
            throw new RuntimeException("文件不存在");
        } catch(IOException e) {
            throw new RuntimeException("复制失败");
        } finally {
            try {
                if(fis != null)
                    fis.close();
            } catch(IOException e) {
                throw new RuntimeException("输入流关闭失败");
            }
            try {
                if(fos != null)
                    fos.close();
            } catch(IOException e) {
                throw new RuntimeException("输出流关闭失败");
            }
        }
    }
}

四、字节流缓冲区

1.缓冲区对应的类
字节流缓冲区对应BufferedOuputStream类和BufferedInputStream类。

2.缓冲区应用
1)用缓冲区拷贝一个Mp3文件

示例代码:

package com.heisejiuhuche.io;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class CopyMp3Demo {
    public static void main(String[] args) throws IOException {
        long start = System.currentTimeMillis();
        copy();
        long end = System.currentTimeMillis();
        System.out.println((end - start) + "毫秒");
    }

    private static void copy() throws IOException {
        /* 创建Buffered缓冲区对象并关联文件 */
        BufferedOutputStream bufos = new BufferedOutputStream(new FileOutputStream(
                "C:/Users/jeremy/Documents/javaTmp/back.mp3"));
        BufferedInputStream bufis = new BufferedInputStream(new FileInputStream(
                "C:\\Users\\jeremy\\Music\\BaiduMusic\\Songs\\Backseat Serenade - All Time Low,Cassadee Pope.mp3"));

        int ch = 0;
        /* 复制文件 */
        while((ch = bufis.read()) != -1) {
            bufos.write(ch);
        }
    }
}

程序输出结果:136毫秒

2)自定义缓冲区

这里写图片描述

假设内存中字节数组的大小定义为1024字节;那么字节流缓冲区在工作时,首先由FileInputStream从硬盘抓取1024字节的数据存入字节数组,然后由BufferedInputStreamread()方法依次一个字节一个字节读取。缓冲区中有两个控制读取过程的变量,分别是数组的索引指针,和一个计数器。下标用于控制不断读取下一个字节,计数器用于控制下一次从硬盘抓数据存入缓冲区数组的时间。read()方法每读取一个字节,指针右移一位,计数器自减1;当计数器减至0的时候,意味着数组中已经没有字节可读,这时再由FileInputStream从硬盘抓取1024个字节存入数组,指针归零,计数器回到1024,再次进行以上步骤的循环,直至硬盘数据全部被抓取。

要自定义缓冲区,需要定义一个字节数组,两个变量(指针和计数器)。

示例代码:

package com.heisejiuhuche.io;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class MyBufferedInputStreamDemo {
    public static void main(String[] args) throws IOException {
        long start = System.currentTimeMillis();
        copy();
        long end = System.currentTimeMillis();
        System.out.println((end - start) + "毫秒");
    }

    private static void copy() throws IOException {
        /* 创建Buffered缓冲区对象并关联文件 */
        BufferedOutputStream bufos = new BufferedOutputStream(new FileOutputStream(
                "C:/Users/jeremy/Documents/javaTmp/back.mp3"));
        MyBufferedInputStream bufis = new MyBufferedInputStream(new FileInputStream(
                "C:\\Users\\jeremy\\Music\\BaiduMusic\\Songs\\Backseat Serenade - All Time Low,Cassadee Pope.mp3"));

        int ch = 0;
        /* 复制文件 */
        while((ch = bufis.read()) != -1) {
            bufos.write(ch);
        }

        bufos.close();
        bufis.close();
    }
}

class MyBufferedInputStream {
    private FileInputStream fis;
    private int pos;
    private int count;
    private byte ch;
    private byte[] buf = new byte[1024];

    MyBufferedInputStream(FileInputStream fis) {
        this.fis = fis;
    }

    public int read() throws IOException {
        /* 如果count=0了,继续抓取数据到缓冲区数组 */
        if(count == 0) {
            /* 抓取数据后,count回到1024 */
            count = fis.read(buf);
            /* 读到文件末尾,fis的read()方法返回-1,必须判断如果count < 0,说明读到了最后 */
            if(count < 0) {
                return -1;
            }
            /* 抓取数据后指针归0 */
            pos = 0;
            /* 取出第一个字节 */
            ch = buf[pos];
            /* 每取一个数据,指针+1,右移 */
            pos++;
            /* 每取一个数据,count-1 */
            count--;
            /* 返回第一个字节 */
            return ch;
        } else if(count > 0) {
            ch = buf[pos];
            pos++;
            count--;
            /* 返回第一个字节后的每一个字节 */
            return ch;
        }
        /* 读完文件,返回-1 */
        return -1;
    }

    public void close() throws IOException {
        fis.close();
    }
}

问题:
上面的代码运行结果只拷贝了8K到目的文件。

原因:
是因为媒体文件在硬盘上的数据以二进制形式存在;read()方法在读取第一个字节的时候,有可能会读到:11111111;这样8个1的情况;而8个1的的二进制就是十进制的-1;程序中while循环的跳进等于-1时,循环停止;因此只复制了8K大小。

理解BufferedInputStream的read()方法:
细看BufferedInputStream类的read()方法,其返回的是int类型。而方法中读到的字节都是byte类型;这样做的原因,就是为了解决读取字节读到8个1的情况。read()方法在返回byte类型字节数据的时候,将byte类型提升为int类型,存储位数由1个8位,变为4个8位。为了确保返回的数据与原数据相同而不产生-1的情况,read()方法在类型提升之后,补了3个8位的0在原byte数据前面。过程如下图:

这里写图片描述

在自定义缓冲区中,虽然方法返回了int类型,进行了数据类型提升,但是没有进行补0的操作,意味着当读到8个1组成的byte数据时,返回了一个由32个1组成的int类型数据,结果还是-1。那么,如果要完成相同功能,只需取32个1的最后8位即可。取最后8位,将原数据与上255

这里写图片描述

将每个return ch;语句改为return ch & 255;即可。

五、键盘录入

1.System标准输出输入
System类中对应的成员outin分别是:
System.out-标准输出流
System.in-标准输入流

System.in用于读取键盘录入。

2.接收键盘录入
从键盘接收输入,并打印在控制台。

示例代码:

package com.heisejiuhuche.io;

import java.io.IOException;
import java.io.InputStream;

public class SysteminDemo {
    public static void main(String[] args) {
        /* 创建标准输入对象 */
        InputStream in = System.in;
        try {
            /* 录入一个字符 */
            int ch = in.read();
            /* 打印该字符的ASCII码 */
            System.out.println(in.read());
        } catch(IOException e) {
            throw new RuntimeException("运行出错~");
        }
    }
}

程序输出结果:

a
97

3.练习
接收键盘录入,当回车时打印整行内容;当输入over回车时,结束输入。

示例代码:

package com.heisejiuhuche.io;

import java.io.IOException;
import java.io.InputStream;

public class SysteminDemo {
    public static void main(String[] args) {
        InputStream in = System.in;
        StringBuilder sb = new StringBuilder();
        int ch = 0;
        try {
            /* 一直读入字符,每输入一个字符,就往StringBuilder添加一个;
             * 如果读到回车符,继续读入;
             * 如果读到换行符,将StringBuilder中的字符组成字符串
             * 判断该字符串是否等于over,如果是,结束程序;
             * 如果不是,就打印该字符串,并将StringBuilder容器清空
             */
            while(true) {
                ch = in.read();
                if(ch == 13)
                    continue;
                if(ch == 10) {
                    String str = sb.toString();
                    if(str.equals("over")) {
                        break;
                    }
                    System.out.println(str);
                    sb.delete(0, sb.length());
                } else 
                    sb.append((char)ch);
            }
        } catch(IOException e) {
            throw new RuntimeException("运行出错~");
        }
    }
}

六、转换流

1.InputStreamReader类
InputStreamReader类用于将字节流转换为字符流。该类可以将读取到的字节数据转换为字符数据。其使用的编码表可以由开发者指定,也可以使用系统默认。利用转换流将字节流转换为字符流,就意味着该字节流可以使用字符流的缓冲区技术,调用其readLine()方法,使键盘录入的读取过程更加高效便捷。

利用转换流修改键盘录入并打印的代码:

package com.heisejiuhuche.io;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

public class InputStreamReaderDemo {
    public static void main(String[] args) {
        /* 创建标准输入对象 */
        InputStream in = System.in;

        /* 利用转换流将字节流转换为字符流 */
        InputStreamReader isr = new InputStreamReader(in);

        /* 转换后的字节流,就可以像字符流一样使用字符流的缓冲技术 */
        BufferedReader bufr = new BufferedReader(isr);

        /* 调用BufferedReader的readLine()方法整行获取键盘录入 */
        String line = null;
        try {
            while((line = bufr.readLine()) != null) {
                /* 如果输入over 结束键盘录入 */
                if(line.equals("over"))
                    break;
                System.out.println(line.toUpperCase());
            }
        } catch(IOException e) {
            throw new RuntimeException("Exception occured...");
        }
    }
}

将字节流转换为字符流,相当于在字节流上套了两根管子;一根使字节流变成字符流;另一根使字节流可以使用字符流的缓冲技术。

2.OutputStreamWriter类
OutputStreamWriter类用于将字符流转换为字节流。该类的编码表同样可以指定或使用系统默认。

用OutputStreamWriter类修改上面的代码:

package com.heisejiuhuche.io;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;

public class InputStreamReaderDemo {
    public static void main(String[] args) {
        InputStream in = System.in;
        InputStreamReader isr = new InputStreamReader(in);
        BufferedReader bufr = new BufferedReader(isr);

        /* 创建标准输出对象 */
        OutputStream out = System.out;

        /* 用转换流将字符流转换为字节流 */
        OutputStreamWriter osw = new OutputStreamWriter(out);

        /* 使用缓冲字符输出流增强字符输出流 */
        BufferedWriter bufw = new BufferedWriter(osw);

        String line = null;
        try {
            while((line = bufr.readLine()) != null) {
                /* 如果输入over 结束键盘录入 */
                if(line.equals("over"))
                    break;
                bufw.write(line.toUpperCase());
                bufw.newLine();
                bufw.flush();
            }
        } catch(IOException e) {
            throw new RuntimeException("Exception occured...");
        } finally {
            try {
                if(bufw != null)
                    bufw.close();
            } catch(IOException e) {
                throw new RuntimeException("资源关闭失败");
            }
        }
    }
}

可以将字节流转字符流的三个步骤简化为一行代码:

BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));

3.转换流的作用
转换流可以指定读写时使用的字符编码集;如不指定,将使用系统默认的编码集,本机默认使用GBK。下面的代码演示了用UTF-8写入,用GBK读取会发生乱码的情况。

示例代码:

package com.heisejiuhuche.io;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;

public class CharSetDemo {
    public static void main(String[] args) {
        write();
    }

    private static void read() {
        BufferedReader bufr = null;

        try {
            /* 使用默认GBK字符集读取数据,结果会出现乱码 */
            bufr = new BufferedReader(new FileReader("C:/Users/jeremy/Documents/javaTmp/demo.txt"));
            System.out.println(bufr.readLine());
        } catch(FileNotFoundException e) {
            e.printStackTrace();
        } catch(IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(bufr != null)
                    bufr.close();
            } catch(IOException e) {
                e.printStackTrace();
            }
        }
    }

    private static void write() {
        InputStreamReader isr = null;
        OutputStreamWriter osw = null;
        BufferedReader bufr = null;
        BufferedWriter bufw = null;
        String line = null;

        try {
            bufr = new BufferedReader(new InputStreamReader(System.in));
            /* 指定使用UTF-8字符集写入数据 */
            bufw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(
                    "C:/Users/jeremy/Documents/javaTmp/demo.txt"),"UTF-8"));

            while((line = bufr.readLine()) != null) {
                if(line.equals("over")) {
                    break;
                }
                bufw.write(line);
                bufw.newLine();
                bufw.flush();
            }
            /* 将写入的数据读取打印在控制台 */
            read();

        } catch(FileNotFoundException e) {
            e.printStackTrace();
        } catch(IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(bufr != null)
                    bufr.close();
            } catch(IOException e) {
                e.printStackTrace();
            }
            try {
                if(bufw != null)
                    bufw.close();
            } catch(IOException e) {
                e.printStackTrace();
            }
        }

    }
}

程序输出结果:

你好,这里是虹桥镇~
over
浣犲ソ锛岃繖閲屾槸铏规ˉ闀噡

为了正常显示,用InputStreamReader指定读取时编码集为UTF-8即可

bufr = new BufferedReader(new InputStreamReader(new FileInputStream(
                    "C:/Users/jeremy/Documents/javaTmp/demo.txt"),"UTF-8"));

程序输出结果:

你好,这里是虹桥镇~
over
你好,这里是虹桥镇~

小扩展
使用System类的方法setIn()和setOut()改变标准输入输出。

示例代码:

package com.heisejiuhuche.io;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;

public class SystemSetDemo {
    public static void main(String[] args) throws IOException {
        BufferedReader bufr = null;
        BufferedWriter bufw = null;

        /* 改变标准输入源,从键盘变为指定目录的文件 */
        System.setIn(new FileInputStream("C:/Users/jeremy/Documents/javaTmp/fos.txt"));

        /* 改变标准输出目的地,从控制台变为指定目录的文件 */
        System.setOut(new PrintStream("C:/Users/jeremy/Documents/javaTmp/fos1.txt"));

        bufr = new BufferedReader(new InputStreamReader(System.in));
        bufw = new BufferedWriter(new OutputStreamWriter(System.out));
        String line = bufr.readLine();
        bufw.write(line);
        bufw.flush();
        bufr.close();
        bufw.close();
    }
}