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

38次阅读

共计 9629 个字符,预计需要花费 25 分钟才能阅读完成。

本篇文章主讲 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 操作。

正文完
 0