swiftR

字节流(Byte Streams)

下面是文件 I/O字节流 FileInputStream 和 FileOutputStream 的基本用法,以文件复制来举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
FileInputStream in = null;
FileOutputStream out = null;

try{

in = new FileInputStream("test.txt");
out = new FileOutputStream("new.txt");
int c;
while ((c = in.read())!= -1){
System.out.println(c);
out.write(c);
}
}catch (Exception e){
System.out.println(e);
}finally {
if (in != null){
in.close();
}
if (out != null){
out.close();
}
}

最后使用完毕后记得关闭流,有助于避免严重的资源泄漏。上面的实例一种低级别的 I/O,如果操作字符数据,使用字符流更好

字符流

1
2
3
4
5
6
7
inputStream = new FileReader("resources/xanadu.txt");
outputStream = new FileWriter("resources/characteroutput.txt");

int c;
while ((c = inputStream.read()) != -1) {
outputStream.write(c);
}

在 CopyCharacters,int 变量保存在其最后的16位字符值;在 CopyBytes,int 变量保存在其最后的8位字节的值。这是因为java中字符使用Unicode编码,一个字符占用两个字节

面向行的 I/O

行结束符可以是回车/换行序列(“\r\n”),一个回车(“\r”),或一个换行符(“\n”)。支持所有可能的行结束符,程序可以读取任何广泛使用的操作系统创建的文本文件。
当然这里使用了缓冲流

1
2
3
4
5
6
7
inputStream = new BufferedReader(new FileReader("resources/xanadu.txt"));
outputStream = new PrintWriter(new FileWriter("resources/characteroutput.txt"));

String l;
while ((l = inputStream.readLine()) != null) {
outputStream.println(l);
}

缓冲流(Buffered Streams)

缓冲输入流从被称为缓冲区(buffer)的存储器区域读出数据;仅当缓冲区是空时,本地输入 API 才被调用。同样,缓冲输出流,将数据写入到缓存区,只有当缓冲区已满才调用本机输出 API。
这样可以减少系统操作,提高效率

BufferedInputStream 和 BufferedOutputStream 用于创建字节缓冲字节流, BufferedReader 和 BufferedWriter 用于创建字符缓冲字节流。

如果要手动刷新流,请调用其 flush 方法。flush 方法可以用于任何输出流,

扫描

Scanner 使用空格字符分隔标记。(空格字符包括空格,制表符和行终止符。)

1
2
3
4
5
s = new Scanner(new BufferedReader(new FileReader("resources/xanadu.txt")));

while (s.hasNext()) {
System.out.println(s.next());
}

Scanner除了支持字符串以外,还支持java的基本类型

1
2
3
4
5
6
7
while (s.hasNext()) {
if (s.hasNextDouble()) {
sum += s.nextDouble();
} else {
s.next();
}
}

调用 print 或 println 输出使用适当 toString 方法变换后的值的单一值。
这两个方法调用的时候使用的是PrintStream

1
2
3
4
5
6
7
8
 * @param      s   The <code>String</code> to be printed
*/
public void print(String s) {
if (s == null) {
s = "null";
}
write(s);
}

println的实现多了一个newLine()来换行

1
2
3
4
5
6
public void println(String x) {
synchronized (this) {
print(x);
newLine();
}
}

命令行 I/O

命令行 I/O 描述了标准流(Standard Streams)和控制台(Console)对象。
Java 支持两种交互方式:标准流(Standard Streams)和通过控制台(Console)。

标准流

Java平台支持三种标准流:标准输入(Standard Input, 通过 System.in 访问)、标准输出(Standard Output, 通过System.out 的访问)和标准错误( Standard Error, 通过System.err的访问)

1
InputStreamReader cin = new InputStreamReader(System.in);
Console (控制台)

程序想使用 Console ,它必须尝试通过调用 System.console() 检索 Console 对象。如果 Console 对象存在,通过此方法将其返回。如果返回 NULL,则 Console 操作是不允许的,要么是因为操作系统不支持他们或者是因为程序本身是在非交互环境中启动的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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, ' ');

数据流(Data Streams)

支持基本数据类型的值((boolean, char, byte, short, int, long, float, 和 double)以及字符串值的二进制 I/O。所有数据流实现 DataInput 或 DataOutput 接口。本节重点介绍这些接口的广泛使用的实现,DataInputStream 和 DataOutputStream 类。

例子如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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]);
out.writeUTF(descs[i]);
}

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) {
System.out.println(e);
}

对象流(Object Streams)

正如数据流支持的是基本数据类型的 I/O,对象流支持的对象 I/O。大多数,但不是全部,标准类支持他们的对象的序列化,都需要实现 Serializable 接口。
例子如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
 public static void main(String[] args) throws IOException, ClassNotFoundException {

User user = new User();
user.name = "swiftr";

System.out.println(user);

ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream("test.out"));
oss.writeObject(user);
oss.flush();
oss.close();

ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.out"));
// 反序列化
User user2 = (User) ois.readObject();
ois.close();
System.out.println(user2);
}
}

class User implements Serializable{

private static final long serialVersionUID = 3831264392873197003L;

String name;

@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}

}

复杂对象io流

如下图所示,其中 writeObject 调用名为 a 的单个对象。这个对象包含对象的引用 b和 c,而 b 包含引用 d 和 e。调用 writeObject(a) 写入的不只是一个 a,还包括所有需要重新构成的这个网络中的其他4个对象。当通过 readObject 读回 a 时,其他四个对象也被读回,同时,所有的原始对象的引用被保留。

upload successful

流只包含一个对象的一个拷贝,尽管它可以包含任何数量的对它的引用。因此,如果你明确地写一个对象到流两次,实际上只是写入了2此引用。例如,如果下面的代码写入一个对象 ob 两次到流:

1
2
3
Object ob = new Object();
out.writeObject(ob);
out.writeObject(ob);

每个 writeObject 都对应一个 readObject, 所以从流里面读回的代码如下:

1
2
Object ob1 = in.readObject();
Object ob2 = in.readObject();

ob1和ob2都是相同对象的引用
如果一个单独的对象被写入到两个不同的数据流,它被有效地复用 - 一个程序从两个流读回的将是两个不同的对象。