关于java:Java-IO-要点梳理与总结

本篇文章主讲 Java IO,应用的 Java 版本为 Java 8,先说下结论:

字节流:应用字节进行输入输出。其余流都是基于这个流。Java 官网强调这个不举荐日常应用。

字符流:咱们平时输入输出简直都是字符,应用字节流一个一个字节读取就不适合了,于是呈现了字符流,字符流包装了字节流,它是操作字符的。

缓冲流:字节流和字符流,都是每一次进行读写就会进行一个物理 IO 操作,效率不高。于是呈现缓冲流,写操作先写进内存中的一个区域(缓冲区),写满在调用物理 IO。读操作也是先读取缓冲区,读满再展现。

Scanning 和 Formatting:平时读取和写入是须要一些格局的,比方像读取不同数据类型的数据、换行输出内容。这时就用到 Scanning 和 Formatting。Scanning 的代表是 Scanner 类,尽管它不是流,然而它包装了流。Formatting 最罕用的就是咱们的 System.out,它实际上是 PrintStream 对象。

命令行 I/O:规范流和 Console。用于命令行上的读写。规范流有三种:System.in、System.out、System.err。Console,必须要在命令行交互的状况下能力应用,它相比拟于规范流,能够平安的读取重要敏感数据(比方明码)。

Data Streams:用于解决二进制 I/O 根本数据类型和 String 的读写。它们是包装了字节流,更不便咱们操作根本数据类型和 String 的读写。

Object Streams:用于解决二进制对象的读写。它们也能够解决根本类型和 String,因为它们独特间接或间接实现了同样的接口 DataInput、DataOutput。领有同样的性能。

字节流

应用字节进行输入输出,所有的字节流类都源于 InputStream、OutputStream。

以 FileInputStream 和 FileOutPutStream 为例

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

public class CopyBytes {
    public static void main(String[] args) throws IOException {

        FileInputStream in = null;
        FileOutputStream out = null;

        try {
            in = new FileInputStream("xanadu.txt");
            out = new FileOutputStream("outagain.txt");
            int d;

            while ((d = in.read()) != -1) {
                out.write(d);
            }
        } finally {
            if (in != null) {
                in.close();
            }
            if (out != null) {
                out.close();
            }
        }
    }
}

读取图解

字节输出流读取数据 read,读取的数据赋值到 d,将 d 写入输入流。

注意事项

当流不再被应用时,肯定要敞开。能够看到程序中是在 finally 敞开字节流的(close 办法)。当出现异常时,in、out 可能为 null,所以敞开前进行了判空。

字节流的应用场景

字节流应该是被防止应用的一种低级 IO(low level I/O)。当 xanadu.txt 蕴含字符数据时,最好应用字符流。

So why talk about byte streams? Because all other stream types are built on byte streams.

那为什么还要学字节流,因为所有其余流类型都基于字节流。

字符流

在大多数利用中,字符流都能够代替字节流。

字符流应用

所有的字节流类都源于 Reader 和 Writer。

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;


public class CopyCharacters {
    public static void main(String[] args) throws IOException {

        FileReader inputStream = null;
        FileWriter outputStream = null;

        try {
            inputStream = new FileReader("xanadu.txt");
            outputStream = new FileWriter("characteroutput.txt");

            // 这里 int 存储的是一个字符值(应用 int 的后 16 位)
            int c;
            while ((c = inputStream.read()) != -1) {
                outputStream.write(c);
            }
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
            if (outputStream != null) {
                outputStream.close();
            }
        }
    }
}

这个程序和字节流的很像,区别在于:FileReader 被替换成 FileInputStream,FileWriter 被替换成 FileOutputStream。

字符流和字节流

• 字节流源于:InputStream、OutputStream
• 字符流源于:Reader、Writer。

字符流是字节流的包装。字符流应用字节流来进行物理 I/O,并且解决字符和字节的转换。
Java 官网不举荐应用字节流,只有数据蕴含字符,就应该应用字符流。

Scanning and Formatting

在应用输入输出时,个别会喜爱格式化。Java 提供两种 API 来帮忙实现。scanner 和 format,别离用来输出和输入不同格局的数据。

Scanning

代表类就是 Scanner 类,应用例子如下:

public class ScanSum {
    public static void main(String[] args) throws IOException {

        Scanner s = null;
        double sum = 0;

        try {
            s = new Scanner(new BufferedReader(new FileReader("usnumbers.txt")));
            // 设置语言环境(不同语言环境千分位可能不一样)
            s.useLocale(Locale.CHINA);

            // Scanner 对象能够调用 nextXXX 办法来读取不同数据类型的数据。
            while (s.hasNext()) {
                if (s.hasNextDouble()) {
                    sum += s.nextDouble();
                } else {
                    s.next();
                }
            }
        } finally {
            if (s != null) {
                s.close();
            }
        }

        // System.out 是 PrintStream 对象
        System.out.println(sum);
    }
}

Scanner 对象不是流,然而包装了输出流,所以能够进行 I/O 操作。通过它的对象调用 nextXXX 办法,就能够读取不同类型的值。

Formatting

咱们罕用的 System.out 其实就是 PrintStream 的对象。与它相似的还有 System.err。在须要自定义对象时,要应用类 PrintWriter 而不是 PrintStream。

罕用的办法是:print()、println()、format()。

public class FormatDemo {
    public static void main(String[] args) {
        int i = 2;
        double r = Math.sqrt(i);
        // 换行应用 %n 而不是 \n(\n 会生成一个换行符)
        System.out.format("The square root of %d is %f.%n", i, r);

        // 格式化日期,输入月份
        System.out.format("%tB", new Date());
    }
}

格局说明符:

最罕用的格局说明符:

d:整数

f:浮点数

s:字符串

%n:用来换行,须要换行时不举荐应用 \n,应用 %n,Java 会依据操作系统生成不同的换行符。

总结

当输出与输入的都是不同类型、不同格局的数据时,就能够用 Scanning 和 Formatting。

Scanning:代表类 Scanner。它不是流,然而因为包装了输出流,所以能够进行 IO 操作。

Formatting:最常被应用的就是 System.out 。他是 PrintStream 的对象,在须要自定义 Formatting 类型的对象时,要应用 PrintWriter 创建对象而不是 PrintStream。

命令行 IO

从命令行读写有两种形式:

  • 通过规范流(Standard Streams)
  • 通过控制台(Console)

Standard Streams

规范流,一般来说是从键盘读取,在控制台显示读取的内容。Java 有三种规范流:

  • System.in:规范输出
  • System.out:规范输入
  • System.err:规范谬误

System.in 是字节流不是字符流,如果想要应用字符规范输出,须要应用 InputStreamReader 包装(转为字符流):

public class StandardStreamDemo {
    public static void main(String[] args) throws IOException {
        int b = 0;
        InputStreamReader in = new InputStreamReader(System.in);
        try {
            while (((b = in.read()) != -1))
                System.out.println((char) b);
        } finally {
            in.close();
        }
    }
}

其实 Scanner 自身就做了这样的操作,它的其中一个构造方法如下:

    public Scanner(InputStream source) {
        this(new InputStreamReader(source), WHITESPACE_PATTERN);
    }

Console

相比拟于 Standard Streams,他更平安,能够用来平安的输出明码(readPassword 办法)。

public class Password {

    public static void main(String[] args) throws IOException {

        Console c = System.console();
        if (c == null) {
            System.err.println("No console.");
            System.exit(1);
        }

        String login = c.readLine("Enter your login: ");
        char[] oldPassword = c.readPassword("Enter your old password: ");

        if (verify(login, oldPassword)) {
            boolean noMatch;
            do {
                char[] newPassword1 = c.readPassword("Enter your new password: ");
                char[] newPassword2 = c.readPassword("Enter new password again: ");
                noMatch = !Arrays.equals(newPassword1, newPassword2);
                if (noMatch) {
                    c.format("Passwords don't match. Try again.%n");
                } else {
                    change(login, newPassword1);
                    c.format("Password for %s changed.%n", login);
                }
                Arrays.fill(newPassword1, ' ');
                Arrays.fill(newPassword2, ' ');
            } while (noMatch);
        }

        Arrays.fill(oldPassword, ' ');
    }

    // Dummy change method.
    static boolean verify(String login, char[] password) {
        // This method always returns
        // true in this example.
        // Modify this method to verify
        // password according to your rules.
        return true;
    }

    // Dummy change method.
    static void change(String login, char[] password) {
        // Modify this method to change
        // password according to your rules.
    }
}

下面的程序步骤:

  1. 拿到 Console 对象。(System.console())(必须在命令行下执行 Java 程序,如果用 IDE 会拿不到 Console 对象
  2. 通过 readLine 拿到登录用户
  3. 通过 readPassword 拿到旧明码(应用该办法命令行不会显示输出的内容)
  4. 验证(此处为假逻辑)
  5. 通过 readPassword 拿到新密码和确认明码
  6. 批改明码(此处为假逻辑)
  7. 旧明码已被笼罩

程序的成果大略是这样的:

总结

命令行 I/O 在 Java 有两种实现:

  • Stardard Streams
  • Console

其中 Stardard Streams 有三种:

System.in:规范输出

System.out:规范输入

System.err:谬误输入

而 Console 相比拟与 Stardard Streams,能够平安的,在命令行获取输出的明码(不会显示),然而必须是在命令行才能够获取 Console 对象。

Data Streams

Data Streams 反对二进制 I/O(八大根本数据类型和 String)。它们的实现类都实现接口 DataInput、DataOutPut。这次的例子应用的是它们最宽泛的实现类 DataInputStream、DataOutPutStream。

import java.io.*;

public class DataStreamDemo {
    static final String dataFile = "invoicedata.txt";

    static final double[] prices = { 19.99, 9.99, 15.99, 3.99, 4.99 };
    static final int[] units = { 12, 8, 13, 29, 50 };
    static final String[] descs = {
            "Java T-shirt",
            "Java Mug",
            "Duke Juggling Dolls",
            "Java Pin",
            "Java Key Chain"
    };

    public static void main(String[] args) throws IOException {
        // DataOutputStream 包装已有 buffer 字节输入流对象
        DataOutputStream out = new DataOutputStream(new BufferedOutputStream(
                new FileOutputStream(dataFile)));

        // 写数据到文件
        for (int i = 0; i < prices.length; i ++) {
            out.writeDouble(prices[i]);
            out.writeInt(units[i]);
            // 将 descs[i]以 UTF-8 编码的变动模式,写入文件
            out.writeUTF(descs[i]);
        }
        // 刷新缓冲区
        out.flush();

        // 读取文件
        // DataInputStream 包装已有字节流对象(包装的文件输出流)
        DataInputStream in = new DataInputStream(new
                BufferedInputStream(new FileInputStream(dataFile)));

        double price;
        int unit;
        String desc;
        double total = 0.0;

        try {
            while (true) {
                price = in.readDouble();
                unit = in.readInt();
                desc = in.readUTF();
                System.out.format("You ordered %d" + " units of %s at $%.2f%n",
                        unit, desc, price);
                total += unit * price;
            }
        } catch (EOFException e) {
            // 用异样来终止 while 循环(读取文件完结持续读取会抛出 EOFException 异样)
        }
    }
}

总结

Data Streams,是 Java I/O 提供的,给根本类型和 String 的二进制输入输出流。

数据流类都是实现的 DataInput 和 DataOutPut 接口。

下面只讲了最罕用的 DataInputStream 和 DataOutputStream。它们都是包装已有的字节流对象。

Object Streams

object streams 反对 Object I/O,然而前提是对象所属的类曾经实现 Serializable 接口

Object Streams 类是:ObjectInputStream、ObjectOutputStream。

ObjectInputStream 体系图:

能够看到它领有 DataInput、ObjectInput 接口的的所有性能,所以 Data Streams 的例子,对于 Object Streams 依然实用:

Object Streams 解决根本数据类型与 String 类型

DataOutputStream 换成 ObjectOutputStream :

         ObjectOutputStream out = new ObjectOutputStream(new BufferedOutputStream(
                new FileOutputStream(dataFile)));

DataInputStream 换成 ObjectIntputStream :

        ObjectInputStream in = new ObjectInputStream(new
                BufferedInputStream(new FileInputStream(dataFile)));

运行后后果与应用 Data Streams 统一。

残缺代码:

import java.io.*;

public class ObjectStreamDemo {
    static final String dataFile = "invoicedata.txt";

    static final double[] prices = { 19.99, 9.99, 15.99, 3.99, 4.99 };
    static final int[] units = { 12, 8, 13, 29, 50 };
    static final String[] descs = {
            "Java T-shirt",
            "Java Mug",
            "Duke Juggling Dolls",
            "Java Pin",
            "Java Key Chain"
    };

    public static void main(String[] args) throws IOException {
        // DataOutputStream 包装已有 buffer 字节输入流对象
        ObjectOutputStream out = new ObjectOutputStream(new BufferedOutputStream(
                new FileOutputStream(dataFile)));

        // 写数据到文件
        for (int i = 0; i < prices.length; i ++) {
            out.writeDouble(prices[i]);
            out.writeInt(units[i]);
            // 将 descs[i]以 UTF-8 编码的变动模式,写入文件
            out.writeUTF(descs[i]);
        }
        // 刷新缓冲区
        out.flush();

        // 读取文件
        // ObjectInputStream 包装已有字节流对象(包装的文件输出流)
        ObjectInputStream in = new ObjectInputStream(new
                BufferedInputStream(new FileInputStream(dataFile)));

        double price;
        int unit;
        String desc;
        double total = 0.0;

        try {
            while (true) {
                price = in.readDouble();
                unit = in.readInt();
                desc = in.readUTF();
                System.out.format("You ordered %d" + " units of %s at $%.2f%n",
                        unit, desc, price);
                total += unit * price;
            }
        } catch (EOFException e) {
            // 用异样来终止 while 循环(读取文件完结持续读取会抛出 EOFException 异样)
        }
    }
}

Object Streams 解决简单类型

通过 readObject 和 writeObject 解决简单类型。在写入对象时,会读取对象及对象的援用对象,如图所示, a 蕴含 b 和 c,b 蕴含 d 和 e,在写入时会将这些都写入。在读取时也会将这些对象都读出来。

实体类 Student:

public class Student implements Serializable {
    private String name;

    public Student(String name) {
        this.name = name;
    }
}

要进行读写的对象所属类肯定要实现 Serializable ,否则无奈应用 readObject 和 writeObject 办法。

应用示例:

public class ObjectStreamDemo2 {
    static final String dataFile = "invoicedata.txt";

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ObjectOutputStream out = new ObjectOutputStream(new BufferedOutputStream(
                new FileOutputStream(dataFile)));

        Student stu = new Student("zhangsan");
        out.writeObject(stu);
        out.writeObject(stu);
        // 刷新缓冲区
        out.flush();

        // 读取文件
        ObjectInputStream in = new ObjectInputStream(new
                BufferedInputStream(new FileInputStream(dataFile)));

        Object stu1 = in.readObject();
        Object stu2 = in.readObject();

        // true
        System.out.println(stu1.equals(stu2));
    }
}

咱们创立 Student 的对象 stu 写入文件两次,读取后,取出对象发现它们地址雷同(是同一个对象)。

A stream can only contain one copy of an object, though it can contain any number of references to it.

下面的大略意思是:尽管一个流能够蕴含多个援用,然而它只是对对象做了拷贝。

咱们从一个流写入两次,读取两次雷同对象时,发现对象地址是统一的。

总结

Object Streams 能够解决实现 Serializable 接口的实现类的对象的读写。

它实现 ObjectInput 接口,而 ObjectInput 接口是 DataInput 接口的子接口,所以它也能够解决根本数据类型和 String 的读写。

在进行读写时,对象蕴含的援用对象也会一起进行读写。

Java IO 小结

Java IO ,有字节流、字符流、缓冲流、命令行 IO、Data Streams、Object Streams。他们实际上最终都是用字节流来调用物理 IO 进行读写操作。其余流是为了让咱们更加不便、有效率的进行 IO 操作。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理