Java 杂谈每日一篇Java

Java中的IO

2018-08-02  本文已影响2人  sixleaves

什么是IO

IO顾名思义就是Input和Output.主要是以程序作为参照物来定义.

Java中对于IO的分类

所谓分类就是根据特点可以对事物进行归类.而对IO进行分类, 也就是以IO类的特点进行分类.先看如下图

流的分类

按流方向划分

可以划分为输入和输出流

按流操作的数据单位划分

可以划分为字节流和字符流。
字节流能操作读写任意类型文件, 字符流只能操作文本文件

按流的角色划分

可以分为节点流和处理流。

四大IO抽象类和常用的节点流处理流

抽象类 节点流 处理流(提供的是缓冲功能)
InputStream FileInputPutStream BufferedInputStream
OutputSteam FileOutputStream BufferedOutputStream
Reader FileReader BufferedReader
Writer FileWriter BufferedWriter

FileInputStream

其常用的API

示例代码

   /**
     * read(buffer, off, len)
     */
    @Test
    public void testInputStreamWithBytesArray2() {
        File file = new File("1.txt");
//        System.out.println(file.getAbsolutePath());
        InputStream is = null;
        try {
            is = new FileInputStream(file);
            int len = 3;
            int off = 0;
            int totalLength = 0;
            byte[] buffer = new byte[100];

            while ((len = is.read(buffer, off, len)) != -1) {
                off += len;
                System.out.print("len = " + len);
            }

            for (int i = 0; i < off; i++) {
                System.out.print((char)buffer[i]);
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {

            if (is!=null) {
                try {
                    is.close();
                }catch (IOException e) {

                }
            }

        }
    }


    @Test
    public void testInputStreamWithBytesArray() {

        File file = new File("1.txt");
//        System.out.println(file.getAbsolutePath());
        InputStream is = null;
        try {
            is = new FileInputStream(file);
            int len = -1;
            byte[] buffer = new byte[5];
            while ((len = is.read(buffer)) != -1) {
                System.out.print("len = " + len);
                for (int i = 0; i < len; i++) {
                    System.out.print((char)buffer[i]);
                }

            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {

            if (is!=null) {
                try {
                    is.close();
                }catch (IOException e) {

                }
            }

        }
    }

 @Test
    public void testInputStreamWithSingleByte() {
        File file = new File("1.txt");
//        System.out.println(file.getAbsolutePath());
        InputStream is = null;
        try {
            is = new FileInputStream(file);

            int len = -1;

            while ((len = is.read()) != -1) {
                System.out.print((char)len);
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {

            if (is!=null) {
                try {
                    is.close();
                }catch (IOException e) {

                }
            }

        }
    }


源码解析

read()和read0
read(byte b[], int off, int len)
readBytes(byte b[], int off, int len)

跟踪FileInputStream起代码可见其调用的是native的read0方法。证明该方法是一个系统调用, 频繁调用的话效率和性能会很低。
而后两者是对native类型的方法readBytes(byte[], off, len)的一层包装,这个方法也是一个系统调用,但它可以次调用读取多个
这就降低了系统调用带来的性能开销, 提高了程序性能

文件字节输入流总结

FileOutputStream

示例代码

@Test
    public  void testOutputStream() {

        File file = new File("testOutput.txt");
        OutputStream os = null;
        try {
           os = new FileOutputStream(file, true);
           os.write("I love Beijing".getBytes());

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {

        }finally {
            if (null != os) {
                try {
                    os.close();
                }catch (IOException e) {

                }
            }
        }
    }

文件的写操作和读差不多,其接口使用就不一一列举。直接总结

文件字节输出流总结

FileReader

FileReader接口和FileInputStream基本一样

示例代码


    /**
     *
     * FileReader继承自InputStreamReader
     */
    @Test
    public void testFileReader() {

        File file = new File("2.txt");
        FileReader fr = null;
        try {
            fr = new FileReader(file);

            int len;
            char[] buf = new char[100];
            while ((len = fr.read(buf)) != -1) {
                String str = new String(buf, 0, len);
                System.out.print(str);
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {

            if (null != fr) {
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
    }

FileWriter

FileWriter接口和FileInputStream基本一样

示例代码

  @Test
    public void testFileReaderAndWriter() {

        FileReader fr = null;
        FileWriter fw = null;

        try {
            fr = new FileReader("2.txt");
            fw = new FileWriter("2_copy.txt");

            int len;
            char[] buf = new char[100];
            while ((len = fr.read(buf)) != -1) {
                fw.write(buf, 0, len);
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {

            try {
                if (null != fw)
                    fw.close();
                if (null != fr)
                    fr.close();
            }catch (IOException e) {

            }

        }
    }

BufferedReader和BufferedWriter

示例代码

    @Test
    public void testBufferedReaderAndWriter() throws IOException {


        FileReader fr = new FileReader("2.txt");
        FileWriter fw = new FileWriter("bufferedWriterCopy3.txt");


        BufferedReader br = new BufferedReader(fr);
        BufferedWriter bw = new BufferedWriter(fw);

        int len;
        char[] buf = new char[100];
        // 方式一
//        while ((len = br.read(buf)) !=-1) {
//            bw.write(buf, 0, len);
//        }

        // 方式二
        String line;
        while ((line = br.readLine()) != null) {
            bw.write(line+"\n");
        }

        bw.close();
        br.close();
        fw.close();
        fr.close();

    }

转换流(处理流二)

转换流主要实现了字节流和字符流之间的转换.主要涉及到以下两个流。对于程序来说,输入输出都是字符串表示。只有从磁盘刚读出后者要写入磁盘才会转成字节
涉及的流

编码解码

转换流使用步骤

常见的编码表名称

常见的编码集

示例代码
将GBK编码的文件读取出来, 并转换成UTF-8编码写出到新文件


    @Test
    public void testISRAndOSW() throws IOException {
        
        // 读取出来的时候也是用字节
        FileInputStream fis = new FileInputStream("test_gbk.txt");
        InputStreamReader isr = new InputStreamReader(fis, "GBK");

        // 写入的时候是用字节
        FileOutputStream fos= new FileOutputStream("test_utf8.txt");
        OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8");

        int len;
        char[] buf = new char[100];
        while ((len = isr.read(buf)) != -1) {
            String line = new String(buf, 0, len);
            System.out.print(line);
            osw.write(buf, 0, len);
        }
        isr.close();
        fis.close();

        osw.close();
        fos.close();
    }

注意

标准输入输出流(处理流三)

打印流(处理流四)

PrintStream\PrintWriter都是输出流

打印流也是处理流, 其争强了字节输出流的功能. System.out是PrintStream的实例, 其关联的是键盘节点

print和println

示例代码

使用打印流PrintStream包装字节输出流,提供各种数据类型的便利输出方法


    /**

     * PrintStream

     *

     */

    @Test

    public void testPrintStream() throws FileNotFoundException {

        // 创建一个PrintStream对象,将其关联到printStream.txt节点

        FileOutputStream fos = new FileOutputStream("printStream.txt");

        PrintStream ps = new PrintStream(fos, true);

        if (ps != null) {

            System.setOut(ps);

        }

        System.out.println("我爱北京!!!!");

    }

运行后创建的文件

System.out重定向示例

数据流(处理流五)

数据流主要用来序列化java字符串和基本数据类型

为什么不直接使用字节流和字符流读写基本数据类型

因为如果你想使用FileOutputStream或者FileWriter来将java中的数据输出, 对于FileOutputStream需要将所有的基本数据类型转为字节或者字节数组。而FileWriter你需要将所有基本数据类型转为字符串。实现起来就十分麻烦。

数据流的特点

DataInputStream中的方法

DataInputStream中的方法

示例代码

    @Test
    public void testDataOutputStream() throws IOException {

        String name = "张三";
        int age = 24;
        char gender = '男';
        double salary = 10000.0;

        FileOutputStream fos = new FileOutputStream("test_dataOutputStream.txt");
        DataOutputStream dos = new DataOutputStream(fos);
        dos.writeUTF(name);
        dos.writeInt(age);
        dos.writeChar(gender);
        dos.writeDouble(salary);
    }

test_dataOutputStream.txt文件


使用文本打开是乱码

之所以乱码, 是因为我们写入到文件中本质上也是字节, 但系统的文本编辑器并不知道java存储这些数据的志节规则,比如int它是序列化为几个字节.所以会乱码, 这时候如果我们还还原数据需要使用数据输入流来读出。

DataOutputStream中的方法

将上述的方法的read改为相应的write即可.其提供了将基本类型数据写入字节流的功能, 可以用数据输入流再读出.

使用数据输入流来读取上面生成的文件.读取的顺序要和写入的顺序一样.

示例代码


    @Test
    public void testDataInputStream() throws IOException {

        FileInputStream fis = new FileInputStream("test_dataOutputStream.txt");

        DataInputStream dis = new DataInputStream(fis);

        String name = dis.readUTF();
        int age = dis.readInt();
        char gender = dis.readChar();
        double salary = dis.readDouble();
        System.out.println("name = " + name);
        System.out.println("age = " + age);
        System.out.println("gender = " + gender);
        System.out.println("salary = " + salary);

    }

输出

DataOutputStream

总结数据流

对象流(处理流六)

对象流是java中用来序列化对象和反序列化对象的流.其对应的类是ObjectOutputStream和ObjectInputStream.

序列化反序列化概念

对象流和数据流的异同

序列化步骤

public class TestObjectOutputStreamAndObjectInputStream {


    @Test
    public void testObjectSerialization() throws IOException {
        Student stu = new Student("苏轼", 20, '男', 10000.0);

        FileOutputStream fos = new FileOutputStream("stu.dat");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(stu);

        oos.close();        // 记得关闭流
        fos.close();
        /**
         * 流的操作步骤
         * 1.创建节点流和相应的节点关联
         * 2.根据需要的功能的创建处理流, 对节点流进行争强.
         * 3.进行输入输出操作
         * 4.关闭流
         */
    }

    @Test
    public void testObjectDeserialize() throws IOException, ClassNotFoundException {


        FileInputStream fis = new FileInputStream("stu.dat");
        ObjectInputStream ois = new ObjectInputStream(fis);

        Object o = ois.readObject();

        System.out.println(o);
        ois.close();
        fis.close();
    }
    
}

class Student implements Serializable {
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", gender=" + gender +
                ", salary=" + salary +
                '}';
    }

    public Student(String name, int age, char gender, double salary) {
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.salary = salary;
    }

    private String name;
    private int age;
    private char gender;
    private double salary;
    // 代码过长省略了getter和setter

transient

可以使用transient关键字声明对象中哪些字段不被序列化,以提高序列化和反序列化效率.

serialVersionUID

在对象进行序列化或反序列化操作的时候,要考虑JDK版本的问 题,如果序列化的JDK版本和反序列化的JDK版本不统一则就有可能造成异常。所以在序列化操作中引入了一个serialVersionUID的常量,可以通过此常量来验证版本的一致性,在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体(类)的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现版本不一致的异常。
但是在进行序列化或反序列化操作的时候,对于不同的JDK版本,实际上会出现版本的兼容问题。

Externalizable接口

作用

既然序列化可以使用Serializable接口, 为什么还有Externalizable接口?该接口的功能其实等效于 Serializable + transient.

两个方法

示例代码,将Student类替换成实现Externalziable接口, 并实现上述两个方法。然后重新运行代码。

class Student implements Externalizable {

    private static final long serialVersionUID = 1L;

    public Student() {

    }

    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeUTF(name);
        out.writeInt(age);
        out.writeChar(gender);
    }

    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        this.name = in.readUTF();
        this.age = in.readInt();
        this.gender = in.readChar();
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", gender=" + gender +
                ", salary=" + salary +
                '}';
    }

    public Student(String name, int age, char gender, double salary) {
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.salary = salary;
    }


    private String name;
    private int age;
    private char gender;
    private transient double salary;
}

控制台输出
由于我们没有定制salary的序列化,所以反序列化出来的时候改字段默认值就是0.0

salary没有序列化

流的使用总结

上一篇 下一篇

猜你喜欢

热点阅读