Java Io流

1.简述

  Java的核心库java.io提供了全面的IO接口。包括:文件读写、标准设备输出等。Java中IO是以流为基础进行输入输出的,所有数据被串行化写入输出流,或者从输入流读入。

2.流的概念

  在Java Io中,流是一个核心的概念。流从概念上来说是一个连续的数据流。你既可以从流中读取数据,也可以往流中写数据。流与数据源或者数据流向的媒介相关联。在Java Io中流既可以是字节流(以字节为单位进行读写),也可以是字符流(以字符为单位进行读写)。

3.Io流的分类

(1)流的类结构图

(2)流归类划分

  按照流的方向划分:

  • 输入流:只能读数据,不能写数据。
  • 输出流:只能写数据,不能读数据。

  按照流传输单元划分:

  • 字节流:读写字节,基类为InputStream/OutputStream。
  • 字符流:读写字符,基类为Reader/Writer。

  按照流的处理方式划分:

  • 节点流(低级流):直接从外部设备中读写数据。
  • 处理流(高级流):处理一个已存在的流,不单独存在。

4.Io流的类简介

(1)FileInputStream、FileOutputStream

  FileInputStream、FileOutputStream类用于本地文件读写,属于二进制格式读写并且是顺序读写,读和写要分别创建出不同的文件流对象。流文件的单元是字节,所以它不但可以读写文本文件,也可以读写图片、声音、影像文件,这种特点非常有用,因为我们可以把这种文件变成流,然后在网络上传输。

   实现示例如下

/**测试类
 */
public class Test{
    public static void main(String[] args) {
        System.out.println("开始复制图片....");
        //创建两个文件,face.gif是已经存在文件,newFace.gif是新创建的文件
        File inFile = new File("d:/Desktop/IO流.png");
        File outFile = new File("d:/Desktop/新IO流.png");
        FileInputStream inStream = null;
        FileOutputStream outStream = null;
        try {
            //1.创建流文件读入与写出类
            inStream = new FileInputStream(inFile);
            outStream = new FileOutputStream(outFile);
            //2.通过available方法取得流的最大字符数
            byte[] inOutb = new byte[inStream.available()];
            //读入流,保存在byte数组
            inStream.read(inOutb);
            //写出流,保存在文件newFace.gif中
            outStream.write(inOutb);
            //3.清空缓冲区数据
            outStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            try {
                //4.关闭输出、输入流
                inStream.close();
                outStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            System.out.println("完成图片复制....");
        }
    }
}
View Code

  通过上面的案例可以看出,使用FileInputStream、FileOutputStream类来进行图片复制操作。这样操作当文件大小超过64M的时候就会出现问题,因为byte数组最大存储值不超过64M,所以当一个文件大于60M 的时候,需要分开几个流操作。

  实现示例如下

/**测试类
 */
public class Test{
    public static void main(String[] args) {
        System.out.println("开始复制图片....");
        int MAX_BYTE = 60000000;    //最大的流为60Mb
        long streamTotal = 0;          //接受流的容量
        int streamNum = 0;          //流需要分开的数量
        int leave = 0;              //文件剩下的字符数
        byte[] inOutb;              //byte数组接受文件的数据
        
        //创建两个文件,face.gif是已经存在文件,newFace.gif是新创建的文件
        File inFile = new File("d:/Desktop/IO流.png");
        File outFile = new File("d:/Desktop/新IO流.png");
        FileInputStream inStream = null;
        FileOutputStream outStream = null;
        try {
            //1.创建流文件读入与写出类
            inStream = new FileInputStream(inFile);
            outStream = new FileOutputStream(outFile);
            //2.通过available方法取得流的最大字符数
            streamTotal = inStream.available();
            streamNum = (int)Math.floor(streamTotal/MAX_BYTE);//取得流文件需要分开的数量
            leave = (int)streamTotal % MAX_BYTE;//分开文件之后,剩余的数量
            //文件的容量大于60Mb时进入循环
            if (streamNum > 0) {
                for(int i = 0; i < streamNum; ++i){
                    inOutb = new byte[MAX_BYTE];
                    //读入流,保存在byte数组
                    inStream.read(inOutb, 0, MAX_BYTE);
                    outStream.write(inOutb);  //写出流
                    outStream.flush();  //清空缓冲区数据
                }
            }
            //写出剩下的流数据
            inOutb = new byte[leave];
            inStream.read(inOutb, 0, leave);
            outStream.write(inOutb);
            //3.清空缓冲区数据
            outStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            try {
                //4.关闭输出、输入流
                inStream.close();
                outStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            System.out.println("完成图片复制....");
        }
    }
}
View Code

  通过上面的案例可以看出,这样操作的话可以读写任意大文件应用。

(2)ByteArrayInputStream、ByteArrayOutputStream

  ByteArrayInputStream、ByteArrayOutputStream类对于要创建临时性文件的程序以及网络数据的传输、数据压缩后的传输等可以提高运行的的效率,可以不用访问磁盘。同样有StringReader与StringWriter类以字符IO流的方式处理字符串。流的来源或目的地并不一定是文件,也可以是内存中的一块空间,例如一个字节数组,就是将字节数组当作流输入来源、输出目的地的类。

  实现示例如下

/**测试类
 */
public class Test{
    public static void main(String args[]) {
        String str = "测试ByteArrayInputStream、ByteArrayOutputStream";//定义一个字符串
        //1.创建数组读入与写出对象
        ByteArrayInputStream bis = new ByteArrayInputStream(str.getBytes());
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        //2.准备从内存ByteArrayInputStream中读取内容
        int temp = 0;
        while ((temp = bis.read()) != -1) {
            char c = (char) temp;    // 读取的数字变为字符
            bos.write(c);
        }
        //所有的数据就全部都在ByteArrayOutputStream中
        String newStr = bos.toString();//取出内容
        try {
            bis.close();
            bos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println(newStr);
    }
}
View Code

  通过上面的案例可以看出,ByteArrayInputStream、ByteArrayOutputStream类用于在内存中输入、输出数据。

(3)PipedInputStream、PipedOutputStream

  PipedInputStream类、PipedOutputStream类用于在应用程序中创建管道通信。一个PipedInputStream实例对象必须和一个PipedOutputStream实例对象进行连接而产生一个通信管道。PipedOutputStream可以向管道中写入数据,PipedIntputStream可以读取PipedOutputStream向管道中写入的数据。这两个类主要用来完成线程之间的通信,一个线程的PipedInputStream对象能够从另外一个线程的PipedOutputStream对象中读取数据。

  实现示例如下

/**测试类
 */
public class Test{
    public static void main(String args[]) {
        Send s = new Send();
        Receive r = new Receive();
        try {
            s.getPos().connect(r.getPis());//连接管道
        } catch (IOException e) {
            e.printStackTrace();
        }
        new Thread(s).start();    // 启动线程
        new Thread(r).start();    // 启动线程
    }
}
/**管道输出流实现类
 */
class Send implements Runnable {
    private PipedOutputStream pos = null;

    Send() {
        pos = new PipedOutputStream();    // 实例化输出流
    }

    public void run() {
        String str = "测试PipedInputStream、PipedOutputStream!!";
        try {
            pos.write(str.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            pos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**得到此线程的管道输出流
     */
    PipedOutputStream getPos() {
        return pos;
    }
}
/**管道输输入实现类
 */
class Receive implements Runnable {
    private PipedInputStream pis = null;

    Receive() {
        pis = new PipedInputStream();
    }

    public void run() {
        byte b[] = new byte[1024];
        int len = 0;
        try {
            len = pis.read(b);
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            pis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("接收的内容为:" + new String(b, 0, len));
    }

    /**得到此线程的管道输入流
     */
    PipedInputStream getPis() {
        return pis;
    }
}
View Code

  通过上面的案例可以看出,一个线程将数据写入管道,另一个线程从该管道读取数据。管道用于将输出从一个程序(或线程)发送到另一个程序(或线程)的输入。 即PipedInputStream必须连接到PipedOutputStream,而PipedOutputStream必须连接到PipedInputStream。

(4)ObjectInputStream、ObjectOutputStream

  ObjectInputStream、ObjectOutputStream类主要的作用是用于写入对象信息与读取对象信息。 对象信息一旦写到文件上那么对象的信息就可以做到持久化了。对象的输出流将指定的对象写入到文件的过程,就是将对象序列化的过程,对象的输入流将指定序列化好的文件读出来的过程,就是对象反序列化的过程。既然对象的输出流将对象写入到文件中称之为对象的序列化,那么可想而知对象所对应的class必须要实现Serializable接口。

  实现示例如下

/**测试类
 */
public class Test{
    public static void main(String args[]) {
        String filepath = "d:/Desktop/person.txt";//文件路径
        Person per[] = {new Person("张三", 30), new Person("李四", 31), new Person("王五", 32)};//对象数组
        try {
            //对象数组进行读和写操作
            writeObject(filepath, per);
            Object o[] = readObject(filepath);
            for (int i = 0; i < o.length; i++) {
                Person p = (Person) o[i];
                System.out.println(p);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**将对象数组写入文件中保存
     */
    public static void writeObject(String filepath, Object obj[]) throws Exception {
        // 1.使用 File 类绑定一个文件
        File f = new File(filepath);
        // 2.把 File 对象绑定到流对象上
        OutputStream out = new FileOutputStream(f);
        ObjectOutputStream os = new ObjectOutputStream(out);
        // 3.进行写操作
        os.writeObject(obj);
        // 4.关闭流
        os.close();
    }
    /**读取文件中保存的对象数组
     */
    public static Object[] readObject(String filepath) throws Exception {
        // 1.使用 File 类绑定一个文件
        File f = new File(filepath);
        // 2.把 File 对象绑定到流对象上
        InputStream in = new FileInputStream(f);
        ObjectInputStream is = new ObjectInputStream(in);
        // 3.进行读操作
        Object[] objects = (Object[]) is.readObject();
        // 4.关闭流
        is.close();
        return objects;
    }
}
/**实体类(实体类必须实现Serializable接口)
 */
class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String toString() {
        return "姓名:" + this.name + ";年龄:" + this.age;
    }
}
View Code

  通过上面的案例可以看出,Person类实现了Serializable接口,只有实现Serializable接口的对象才能被写入ObjectOutputStream流。需要知道的person.txt文件中记录的是二进制数据。

  注意

  • 如果仅仅只是让某个类实现Serializable接口,而没有其它任何处理的话,那么就会使用默认序列化机制。默认机制,在序列化对象时,不仅会序列化当前对象本身,还会对其父类的字段以及该对象引用的其它对象也进行序列化。同样地,这些其它对象引用的另外对象也将被序列化,以此类推。
  • 当某个字段被声明为transient后,默认序列化机制就会忽略该字段。
  • 可序列化类实现Externalizable接口之后,基于Serializable接口的默认序列化机制就会失效。

(5)SequenceInputStream

   SequenceInputStream类会将与之相连接的流集组合成一个输入流并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末 尾为止。合并流的作用是将多个源合并合一个源。

  实现示例如下

/**测试类
 */
public class Test{
    public static void main(String args[]) throws Exception {
        String path = System.getProperty("user.dir");//获取当前工作目录
        InputStream s1 = new FileInputStream(path+"/src/org/tempuri/EQCService.java");
        InputStream s2 = new FileInputStream(path+"/src/org/tempuri/EQCServiceLocator.java");
        InputStream s3 = new FileInputStream(path+"/src/org/tempuri/EQCService.java");
        
        //1.构建流集合
        Vector<InputStream> v = new Vector<InputStream>();//创建一个动态输入流对象数组
        v.addElement(s1);
        v.addElement(s2);
        v.addElement(s3);
        
        //2.创建一个合并流的对象
        SequenceInputStream sis = new SequenceInputStream(v.elements());
        //3.创建输出流
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(path+"/src/org/tempuri/copySequence.java"));
        byte[] buf = new byte[1024];
        int len = buf.length;
        while((len = sis.read(buf, 0, len)) != -1) {
            bos.write(buf, 0, len);
        }
        sis.close();
        bos.close();
    }
}
View Code

  通过上面的案例可以看出,所有InputStream实例放入Vector中,并将Vector.elements()传递给SequenceInputStream构造函数。最后使用BufferedOutputStream实现合并为一个文件。

(6)StringBufferInputStream

  I/O类库提供StringBufferInputStream类的本意是把字符串转换为字节流,然后进行读操作,但是在这个类的实现中仅仅使用了字符编码的低8位,不能把字符串中的所有字符(比如中文字符)正确转换为字节,因此这个类已经被废弃,取而代之的是StringReader类。

(7)FilterInputStream、FilterOutputStream

  FilterInputStream和FilteOutputStream分别是过滤输入流和过滤输出流,它的作用是为基础流提供一些额外的功能。

  因为其只是一个标准、其方法都是调用传入的InputStream实现类的方法、意义大过于作用、所以这里暂不提供实例。有实际装饰效果的类中提供实例、比如后面的DataInputStream、BufferedInputStream、等装饰类

(8)BufferedInputStream、BufferedOutputStream

  BufferedInputStream、BufferedOutputStream类就是实现了缓冲功能的输入、输出流。正因为它们实现了缓冲功能,所以要注意在使用BufferedOutputStream写完数据后,要调用flush()方法或close()方法,强行将缓冲区中的数据写出。否则可能无法写出数据。

  有必要知道是不带缓冲的操作,每读一个字节就要写入一个字节,由于涉及磁盘的IO操作相比内存的操作要慢很多,所以不带缓冲的流效率很低。带缓冲的流,可以一次读很多字节,但不向磁盘中写入,只是先放到内存里。等凑够了缓冲区大小的时候一次性写入磁盘,这种方式可以减少磁盘操作次数,速度就会提高很多!

  实现示例如下

/**测试类
 */
public class Test{
    public static void main(String args[]) throws Exception {
        // 1.需要读和写的文件 文件的复制操作
        File file = new File("d:/Desktop/IO流.png");
        File file2 = new File("d:/Desktop/新IO流.png");
        // 2.创建相应的节点流(因为缓冲流是建立在节点流之上的)
        FileInputStream fis = new FileInputStream(file);
        FileOutputStream fos = new FileOutputStream(file2);
        // 3.将创建的节点流对象作为形参传递给缓冲流的构造器
        BufferedInputStream bis = new BufferedInputStream(fis);
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        byte[] b = new byte[1024];
        int len;
        while ((len = bis.read(b)) != -1) {
            bos.write(b, 0, len);
            bos.flush();//将数据写入到磁盘中
        }
        bos.close();
        bis.close();
    }
}
View Code

  通过上面的案例可以看出,使用一个byte数组来作为数据读入的缓冲区,以图片存取为例,一次性读取所有数据,内存可能装不下,但是如果每次只读一个字节数据,会耗时太慢。因此用BufferedInputStream一次读取1024*8个字节数据,用BufferedOutputStream放入缓冲区,分批读写。

(9)PushbackInputStream

  PushbackInputStream类实现了缓存的新应用之一回推(pushback)。回推用于输入流,以允许读取字节,然后再将它们返回(回推)到流中,可以“偷窥”来自输入流的内容而不对它们进行破坏。

  实现示例如下

/**测试类
 */
public class Test{
    public static void main(String args[]) throws Exception {
        String str = "www.baidu.com";
        PushbackInputStream push = null;
        ByteArrayInputStream bat = null;
        bat = new ByteArrayInputStream(str.getBytes());//创建数组输入流
        push = new PushbackInputStream(bat);//将输入流放入到回退流之中。
        int temp = 0;
        while((temp = push.read()) != -1){//读取数据
            if(temp == '.'){
                push.unread(temp);//回退一个数据到缓冲区前面
                temp = push.read();
                System.out.print("(回退" + (char) temp + ") ");
            }else{
                System.out.print((char) temp);
            }
        }
        bat.close();
        push.close();
    }
}
View Code

  通过上面的案例可以看出,通常情况下我们从流中读取数据时都是顺序操作的,也许流中的数据并不都是我们需要的,按照平常的流,我们要做的是就是将流中的数据依读取取出,并对取出的数据进行筛选,不符合条件的数据就丢弃。PushbackInputStream流则稍有不同,它通过内部的一个缓存从而支持了数据的回推,在上面的案例中,当遇到不需要的数据时,PushbackInputStream还可以将数据重新推回到流中。

(10)DataInputStream、DataOutputStream

  DataInputStream、DataOutputStream类是数据输入、输出流允许应用程序以与机器无关方式将Java基本数据类型读写到流中。

  实现示例如下

/**测试类
 */
public class Test{
    public static void main(String args[]) throws IOException {
        write("d:/Desktop/person.txt");//
        read("d:/Desktop/person.txt");//
    }

    private static void read(String path) throws IOException {
        // 创建数据输入流对象
        DataInputStream dis = new DataInputStream(new FileInputStream(path));

        // 读数据
        byte b = dis.readByte();
        short s = dis.readShort();
        int i = dis.readInt();
        long l = dis.readLong();
        float f = dis.readFloat();
        double d = dis.readDouble();
        char c = dis.readChar();
        boolean bb = dis.readBoolean();
        // 释放资源
        dis.close();
        
        System.out.println(b);
        System.out.println(s);
        System.out.println(i);
        System.out.println(l);
        System.out.println(f);
        System.out.println(d);
        System.out.println(c);
        System.out.println(bb);
    }

    private static void write(String path) throws IOException {
        // 创建数据输出流对象
        DataOutputStream dos = new DataOutputStream(new FileOutputStream(path));
        // 写数据了
        dos.writeByte(10);
        dos.writeShort(100);
        dos.writeInt(1000);
        dos.writeLong(10000);
        dos.writeFloat(12.34F);
        dos.writeDouble(12.56);
        dos.writeChar('a');
        dos.writeBoolean(true);
        // 释放资源
        dos.close();
    }
}
View Code

  通过上面的案例可以看出,DataInputStream、DataOutputStream类可以把java的基础数据类型读写流中。

(11)PrintStream

  PrintStream类是用来装饰其它输出流。它能为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式。与其他输出流不同,PrintStream永远不会抛出IOException;它产生的IOException会被自身的函数所捕获并设置错误标记, 可以通过checkError()返回错误标记,从而查看PrintStream内部是否产生了IOException。

  实现示例如下

/**测试类
 */
public class Test{
    public static void main(String args[]) throws IOException {
        // 下面3个函数的作用都是一样:都是将字母“abcde”写入到文件“file.txt”中。
        // 任选一个执行即可!
        testPrintStreamConstrutor1("d:/Desktop/person.txt") ;
        //testPrintStreamConstrutor2("d:/Desktop/person.txt") ;
        //testPrintStreamConstrutor3("d:/Desktop/person.txt") ;

        // 测试write(), print(), println(), printf()等接口。
        testPrintStreamAPIS("d:/Desktop/person.txt") ;
    }

    /**PrintStream(OutputStream out) 的测试函数
     * 函数的作用,就是将字母“abcde”写入到文件“file.txt”中
     */
    private static void testPrintStreamConstrutor1(String path) {
        // 0x61对应ASCII码的字母'a',0x62对应ASCII码的字母'b', ...
        final byte[] arr={0x61, 0x62, 0x63, 0x64, 0x65 }; // abced
        try {
            // 创建文件“file.txt”的File对象
            File file = new File(path);
            // 创建文件对应FileOutputStream
            PrintStream out = new PrintStream(new FileOutputStream(file));
            // 将“字节数组arr”全部写入到输出流中
            out.write(arr);
            // 关闭输出流
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**PrintStream(File file) 的测试函数
     * 函数的作用,就是将字母“abcde”写入到文件“file.txt”中
     */
    private static void testPrintStreamConstrutor2(String path) {
        final byte[] arr={0x61, 0x62, 0x63, 0x64, 0x65 };
        try {
            File file = new File(path);
            PrintStream out = new PrintStream(file);
            out.write(arr);
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**PrintStream(String fileName) 的测试函数
     * 函数的作用,就是将字母“abcde”写入到文件“file.txt”中
     */
    private static void testPrintStreamConstrutor3(String path) {
        final byte[] arr={0x61, 0x62, 0x63, 0x64, 0x65 };
        try {
            PrintStream out = new PrintStream(path);
            out.write(arr);
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**测试write(), print(), println(), printf()等接口。
     */
    private static void testPrintStreamAPIS(String path) {
        // 0x61对应ASCII码的字母'a',0x62对应ASCII码的字母'b', ...
        final byte[] arr = {0x61, 0x62, 0x63, 0x64, 0x65 }; // abced
        try {
            // 创建文件对应FileOutputStream
            PrintStream out = new PrintStream(path);

            // 将字符串“hello PrintStream”+回车符,写入到输出流中
            out.println("hello PrintStream");
            // 将0x41写入到输出流中
            // 0x41对应ASCII码的字母'A',也就是写入字符'A'
            out.write(0x41);
            // 将字符串"65"写入到输出流中。
            // out.print(0x41); 等价于 out.write(String.valueOf(0x41));
            out.print(0x41);
            // 将字符'B'追加到输出流中
            out.append('B');

            // 将"CDE is 5" + 回车  写入到输出流中
            String str = "CDE";
            int num = 5;
            out.printf("%s is %d\n", str, num);
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
View Code

  通过上面的案例可以看出,使用继承自父类的write方法写数据,那么查看数据的时候会查询编码表 0x41–>A,使用自己特有的方法print/println方法写数据,写的数据原样输出 0x41–>65(10进制)。

(12)CharArrayReader、CharArrayWriter

  CharArrayReader、CharArrayWriter类是一个把字符数组作为源的输入、输出流的实现。CharArrayReader、CharArrayWriter内部维护了一个字符数组,用来缓存数据。

  实现示例如下

/**测试类
 */
public class Test{
    private static final int LEN = 5;
    private static char[] chs = new char[]{'a','b','c','d','e','f','g','h','i','j','k','l','m',
            'n','o','p','q','r','s','t','u','v','w','x','y','z'};
    
    public static void main(String[] args) {
        write();
        read();
    }

    /**CharArrayWriter 测试
     */
    public static void write() {
        CharArrayWriter caw = null;
        CharArrayWriter caw2 = null;
        try {
            // 创建CharArrayWriter字符流
            caw = new CharArrayWriter();
            // 写入“A”个字符
            caw.write('A');
            // 写入字符串“BC”个字符
            caw.write("BC");
            // System.out.printf("caw=%s\n", caw);
            // 将ArrayLetters数组中从“3”开始的后5个字符(defgh)写入到caw中。
            caw.write(chs, 3, 5);
            // System.out.printf("caw=%s\n", caw);

            // (01) 写入字符0
            // (02) 然后接着写入“123456789”
            // (03) 再接着写入ArrayLetters中第8-12个字符(ijkl)
            caw.append('0').append("123456789").append(String.valueOf(chs), 8, 12);

            System.out.printf("caw=%s\n", caw);

            // 计算长度
            int size = caw.size();
            System.out.printf("size=%s\n", size);

            // 转换成byte[]数组
            char[] buf = caw.toCharArray();
            System.out.printf("buf=%s\n", String.valueOf(buf));

            // 将caw写入到另一个输出流中
            caw2 = new CharArrayWriter();
            caw.writeTo(caw2);
            System.out.printf("caw2=%s\n", caw2);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                if(caw != null)
                    caw.close();
                if(caw2 != null)
                    caw2.close();
            }catch(Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    /**CharArrayReader 测试
     */
    public static void read() {
        CharArrayReader car = null;
        try {
            // 创建CharArrayReader字符流,内容是ArrayLetters数组
            car = new CharArrayReader(chs);

            // 从字符数组流中读取5个字符
            for (int i = 0; i < LEN; i++) {
                // 若能继续读取下一个字符,则读取下一个字符
                if (car.ready() == true) {
                    // 读取“字符流的下一个字符”
                    char tmp = (char) car.read();
                    System.out.printf("%d : %c\n", i, tmp);
                }
            }

            // 若“该字符流”不支持标记功能,则直接退出
            if (!car.markSupported()) {
                System.out.println("make not supported!");
                return;
            }

            // 标记“字符流中下一个被读取的位置”。即--标记“f”,因为因为前面已经读取了5个字符,所以下一个被读取的位置是第6个字符”
            // (01), CharArrayReader类的mark(0)函数中的“参数0”是没有实际意义的。
            // (02),
            // mark()与reset()是配套的,reset()会将“字符流中下一个被读取的位置”重置为“mark()中所保存的位置”
            car.mark(0);

            // 跳过5个字符。跳过5个字符后,字符流中下一个被读取的值应该是“k”。
            car.skip(5);

            // 从字符流中读取5个数据。即读取“klmno”
            char[] buf = new char[LEN];
            car.read(buf, 0, LEN);
            System.out.printf("buf=%s\n", String.valueOf(buf));

            // 重置“字符流”:即,将“字符流中下一个被读取的位置”重置到“mark()所标记的位置”,即f。
            car.reset();
            // 从“重置后的字符流”中读取5个字符到buf中。即读取“fghij”
            car.read(buf, 0, LEN);
            System.out.printf("buf=%s\n", String.valueOf(buf));
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                if(car != null)
                    car.close();
            }catch(Exception e) {
                e.printStackTrace();
            }
        }
    }
}
View Code

  通过上面的案例可以看出,这个是内存操作流,用于临时存储信息,程序结束,数据从内存中消失。

(13)PipedReader、PipedWriter

  PipedWriter、PipedReader类是可以通过管道进行线程间的通讯。在使用管道通信时,必须将PipedWriter和PipedReader配套使用。

  实现示例如下

/**测试类
 */
public class Test{
    public static void main(String[] args) {
        /**管道流通信核心是,Writer和Reader公用一块缓冲区,缓冲区在Reader中申请,
         * 由Writer调用和它绑定的Reader的Receive方法进行写.
         */
        //1 建立输入输出流
        PipedReader reader = new PipedReader();
        PipedWriter writer = new PipedWriter();
        //2.绑定输入输出流
        Producer producer = new Producer(writer);
        Consumer consumer = new Consumer(reader);
        try {
            writer.connect(reader);
            //3.Writer写线程启动
            producer.start();
            //4.Reader读线程启动
            consumer.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
/**写线程
 */
class Producer extends Thread {
    //输出流
    private PipedWriter writer = new PipedWriter();
    public Producer(PipedWriter writer) {
        this.writer = writer;
    }

    @Override
    public void run() {
        try {
            StringBuilder sb = new StringBuilder();
            sb.append("PipedWriter、PipedReader测试!");
            writer.write(sb.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
/**读取线程
 */
class Consumer extends Thread{
    //输入流
    private PipedReader reader = new PipedReader();

    public Consumer(PipedReader reader) {
        this.reader = reader;
    }

    public void run() {
        try {
            char [] cbuf = new char[1024];
            reader.read(cbuf, 0, cbuf.length);
            System.out.println("管道流中的数据为: " + new String(cbuf));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
View Code

  通过上面的案例可以看出,PipedReader、PipedWriter两者离开哪一方都不能继续存在、PipedWriter先通过connect(PipedReader sink)来确定关系、并初始化PipedReader状态、告诉PipedReader只能属于这个PipedWriter、connect = true、当想赠与PipedReader字符时、就直接调用receive(char c) 、receive(char[] b, int off, int len)来将字符或者字符数组放入pr的存折buffer中。

(14)StringReader、StringWriter

  StringReader、StringWriter类都是Reader和Writer的装饰类,使它们拥有了对String类型数据进行操作的能力。

  实现示例如下

/**测试类
 */
public class Test{
    public static void main(String[] args) {
        //用法跟byteArrayInputStream和byteArrayOutputStream的用法差不多
        //1.创建StringReader、StringWriter输入、输出流对象
        StringReader sr = new StringReader("StringReader、StringWriter测试");
        StringWriter sw = new StringWriter();
        int len;
        try {
            //2.循环输入的数据,并写入到输出中
           while((len = sr.read())!=-1){
                sw.write(len);
           }
           //3.打印输出流的数据
           System.out.println(sw.getBuffer().toString());
        } catch (IOException e) {
           e.printStackTrace();
        }
    }
}
View Code

  通过上面的案例可以看出,StringReader、StringWriter本身也没什么特别的,跟CharArrayReader 、CharArrayWriter非常的类似,也仅仅是一种数据源的形式而已。

(15)BufferedReader、BufferedWriter

  BufferedReader、BufferedWriter类各拥有8192(8k)字符的缓冲区。当BufferedReader在读取文本文件时,会先尽量从文件中读入字符数据并置入缓冲区,而之后若使用read()方法,会先从缓冲区中进行读取。如果缓冲区数据不足,才会再从文件中读取,使用BufferedWriter时,写入的数据并不会先输出到目的地,而是先存储至缓冲区中。如果缓冲区中的数据满了,才会一次对目的地进行写出。

  实现示例如下

/**测试类
 */
public class Test{
    public static void main(String[] args) {
        String b = null;
        BufferedWriter bw = null;
        BufferedReader br = null;
         try {
            //创建输入、输出流对象
            bw = new BufferedWriter(new FileWriter("d:/Desktop/newPerson.txt.txt"));
            br = new BufferedReader(new FileReader("d:/Desktop/person.txt"));
            while ((b = br.readLine()) != null) {//循环读取,每次读取一行
                System.out.println(b);
                bw.write(b);//输出字符串
                bw.newLine();//换行
                bw.flush();//清除缓存区
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {  
            try {
                br.close();
                br.close();
            } catch (IOException e) {
                e.printStackTrace();  
            }
        }
    }
}
View Code

  通过上面的案例可以看出,读写英文字符时是不会出现问题,如果是读写中文字符就会出现乱码的情况,为了避免这种情况的出现,实现代码如下。

  实现示例如下

/**测试类
 */
public class Test{
    public static void main(String[] args) {
        String b = null;
        BufferedWriter bw = null;
        BufferedReader br = null;
         try {
            //创建输入、输出流对象
            bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("d:/Desktop/newPerson.txt"), "GBK"));
            br = new BufferedReader(new InputStreamReader(new FileInputStream("d:/Desktop/person.txt"), "GBK"));
            while((b = br.readLine()) != null){
                System.out.println(b);
                bw.write(b);//输出字符
                bw.newLine();//换行
                bw.flush();//刷新该流的缓冲区
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {  
            try {
                br.close();
                bw.close();
            } catch (IOException e) {
                e.printStackTrace();  
            }
        }
    }
}
View Code

  通过上面的案例可以看出,使用这种方式读写中文字符不会出现乱码的情况。

(16)InputStreamReader、OutputStreamWriter

  InputStreamReader、OutputStreamWriter类是字节流通向字符流的桥梁,它使用指定的charset读写字节并将其解码为字符。InputStreamReader的作用是将“字节输入流”转换成“字符输入流”。OutputStreamWriter 的作用是将“字节输出流”转换成“字符输出流”。

  实现示例如下

/**测试类
 */
public class Test{
    public static void main(String[] args) {
        String b = null;
        BufferedWriter bw = null;
        BufferedReader br = null;
         try {
            //创建输入、输出流对象
            bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("d:/Desktop/newPerson.txt"), "GBK"));
            br = new BufferedReader(new InputStreamReader(new FileInputStream("d:/Desktop/person.txt"), "GBK"));
            while((b = br.readLine()) != null){
                System.out.println(b);
                bw.write(b);//输出字符
                bw.newLine();//换行
                bw.flush();//刷新该流的缓冲区
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {  
            try {
                br.close();
                bw.close();
            } catch (IOException e) {
                e.printStackTrace();  
            }
        }
    }
}
View Code

  通过上面的案例可以看出,InputStreamReader读取的时候,会将内容转换成utf-8的内容并读出来。OutputStreamWriter写入的时候,会将写入的内容转换utf-8编码并写入。

(17)FileReader、FileWriter

  FileReader、FileWriter类是用来实现将字符读写到文件的IO。

  实现示例如下

/**测试类
 */
public class Test{
    public static void main(String[] args) {
        FileWriter fw = null;
        FileReader fr = null;
         try {
            //创建输入、输出流对象
            fw = new FileWriter("d:/Desktop/newPerson.txt");
            fr = new FileReader("d:/Desktop/person.txt");
            char[] chs = new char[1024];
            int len = 0;
            while ((len = fr.read(chs)) != -1) {
                fw.write(chs, 0, len);
                fw.flush();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {  
            try {
                fr.close();
                fw.close();
            } catch (IOException e) {
                e.printStackTrace();  
            }
        }
    }
}
View Code

  通过上面的案例可以看出,FileReader读取文件的过程中,FileReader继承了InputStreamReader,但并没有实现父类中带字符集参数的构造函数,所以FileReader只能按系统默认的字符集来解码,然后在UTF-8 -> GBK -> UTF-8的过程中编码出现损失,造成结果不能还原最初的字符。对于中文的话,会出现乱码的情况。

(18)FilterReader、FilterWriter

  FilterReader和FilterWriter作为抽象类,分别继承了父类Writer和Reader抽象类,除了简单覆盖父类方法,没有添加额外的方法。

(19)PushbackReader

  PushbackReader一个允许字符被推回到流中的字符流读取器。

  实现示例如下

/**测试类
 */
public class Test{
    public static void main(String[] args) throws IOException {
        StringReader stringReader = new StringReader("123456789");
        PushbackReader pushbackReader = new PushbackReader(stringReader, 100);
        StringBuilder stringBuilder = new StringBuilder();
        char[] buff = new char[20];
        //第一步,首先读取 “123”
        int n = pushbackReader.read(buff, 0, 3);
        stringBuilder.append(buff);
        System.out.println("第一步,读取了 " + n + " 个字符:");
        System.out.println(buff);
        //第二步,unread:abc, de, fghi
        pushbackReader.unread(new char[]{'a', 'b', 'c'});
        pushbackReader.unread(new char[]{'d', 'e'});
        pushbackReader.unread(new char[]{'f', 'g', 'h', 'i'});
        int c = 0;
        while (c != -1){
            c = pushbackReader.read();
            stringBuilder.append((char) c);
        }
        System.out.println("最终读取的数据:" + stringBuilder.toString());
    }
}
View Code

  通过上面的案例可以看出,其实就是将从流读取的数据再推回到流中。实现原理也很简单,通过一个缓冲数组来存放推回的数据,每次操作时先从缓冲数组开始,然后再操作流对象。

(20)PrintWriter

  PrintWriter类具有自动行刷新的缓冲字符输出流,特点是可以按行写出字符串,并且可以自动行刷新。

  实现示例如下

/**测试类
 */
public class Test{
    public static void main(String[] args) throws IOException {
        Scanner scanner=new Scanner(System.in);
        FileOutputStream fos = new FileOutputStream("d:/Desktop/person.txt");//文件流,(将字节写入到文件)
        OutputStreamWriter osw = new OutputStreamWriter(fos,"GBK");//转换流(将字符转换成字节)
        BufferedWriter bw = new BufferedWriter(osw);//缓冲字符流(加快写入文本数据))
        /*在流连接中创建pw时,构造方法允许我们再传入 一个boolean型参数,当这个值为true时,
        * 那么 当前pw就具有了自动行刷新功能,即:当我们调用println方法后,会自动flush。 但是需要注意:print方法是不会自动flush。
        */
        PrintWriter pw= new PrintWriter(bw,true);//缓冲字符输出流(按行写,并自动行刷新)
        System.out.println("请输入文字:");
        while(true) {
            String line = scanner.nextLine();//控制台写入文字
            if("exit".equals(line.toLowerCase())) {
                break;//退出
            }
            pw.println(line);
        }
        System.out.println("已写出");
        pw.close();//关闭
    }
}
View Code

posted on 2020-10-12 15:55  码农记录  阅读(142)  评论(0)    收藏  举报

导航