本篇文章主讲 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. }}
下面的程序步骤:
- 拿到 Console 对象。(System.console())(必须在命令行下执行 Java 程序,如果用 IDE 会拿不到 Console 对象)
- 通过 readLine 拿到登录用户
- 通过 readPassword 拿到旧明码(应用该办法命令行不会显示输出的内容)
- 验证(此处为假逻辑)
- 通过 readPassword 拿到新密码和确认明码
- 批改明码(此处为假逻辑)
- 旧明码已被笼罩
程序的成果大略是这样的:
总结
命令行 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 操作。