共计 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.
}
}
下面的程序步骤:
- 拿到 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 操作。