Java I/O 08 - PrintStream

  关于 java.io.PrintStream 的部分笔记,这个类除了实现了继承自FilterOutputStream的基本的write方法外,还提供了打印输出内容的方法来打印当前输出的内容。本文演示代码段的执行环境基于JDK版本1.7

概述

  PrintStream提供了打印输出内容的便捷方法,这样可以直接调用其提供的方法来完成输出内容的输出和打印显示两个操作。所有通过PrintStream输出打印的内容都会被转成字节流然后输出到目标位置。除此之外,PrintStream也不会抛出IOException,而是通过内置字段trouble来判断是否在调用过程中抛出了IOException。

继承关系

1
2
3
4
5
// PushbackInputStream
--java.lang.Object
--java.io.OutputStream
--java.io.FilterOutputStream
--java.io.PrintStream

实现接口

类名 实现接口
PrintStream Closeable, Flushable, Appendable, AutoCloseable

部分方法

public boolean checkError()

1
2
3
4
5
6
7
8
9
public boolean checkError() {
if (out != null)
flush();
if (out instanceof java.io.PrintStream) {
PrintStream ps = (PrintStream) out;
return ps.checkError();
}
return trouble;
}

  检查当前时间点之前是否存在过数据输出异常的情况,如果存在,那么返回true。这里仅针对在方法调用过程中抛出的IOException,或者setError方法被显式调用。如果在过程中出现了InterruptedIOException,那么则通过Thread.currentThread().interrupt()进行处理。

protected void setError()

1
2
3
protected void setError() {
trouble = true;
}

protected void clearError()

1
2
3
protected void clearError() {
trouble = false;
}

public void write(int b)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void write(int b) {
try {
synchronized (this) {
ensureOpen();
out.write(b);
if ((b == '\n') && autoFlush)
out.flush();
}
}
catch (InterruptedIOException x) {
Thread.currentThread().interrupt();
}
catch (IOException x) {
trouble = true;
}
}

  向底层输出流中写入一个字节。如果入参是换行符,那么就根据autoFlush的值来决定是否需要把写入的数据推到目标输出位置。

public void write(byte buf[], int off, int len)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void write(byte buf[], int off, int len) {
try {
synchronized (this) {
ensureOpen();
out.write(buf, off, len);
if (autoFlush)
out.flush();
}
}
catch (InterruptedIOException x) {
Thread.currentThread().interrupt();
}
catch (IOException x) {
trouble = true;
}
}

  向底层输出流中写入一个字节数组。在向底层输出流无异常写完那么就根据autoFlush的值来决定是否需要把写入的数据推到目标输出位置。

private void write(char buf[])

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void write(char buf[]) {
try {
synchronized (this) {
ensureOpen();
textOut.write(buf);
textOut.flushBuffer();
charOut.flushBuffer();
if (autoFlush) {
for (int i = 0; i < buf.length; i++)
if (buf[i] == '\n')
out.flush();
}
}
}
catch (InterruptedIOException x) {
Thread.currentThread().interrupt();
}
catch (IOException x) {
trouble = true;
}
}

  将字符数组输出到底层输出流中。首先将内容写入到textOut的缓冲区中(BufferedWriter),然后把数据强制刷新到textOut的底层输出流charOut (OutputStreamWriter)中。最后再把charOut的数据强制刷新到其中包含的底层输出流(StreamEncoder)。采用OutputStreamWriter的目的是因为这是一个字符流转字节流的类,可以实现把各种类型的数据转成对应的字节流输出。而BufferedWriter带有缓冲区,所以其效率相对来说会高一点。如果支持自动刷新,那么就判断入参字符数组中是否含有换行符“\n”,如果有就底层输出流的flush方法,将数据推到目标输出位置。

private void write(String s)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void write(String s) {
try {
synchronized (this) {
ensureOpen();
textOut.write(s);
textOut.flushBuffer();
charOut.flushBuffer();
if (autoFlush && (s.indexOf('\n') >= 0))
out.flush();
}
}
catch (InterruptedIOException x) {
Thread.currentThread().interrupt();
}
catch (IOException x) {
trouble = true;
}
}

  将字符串输出到底层输出流中。执行逻辑同方法write(char buf[]) ,故不予赘述。

private void newLine()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void newLine() {
try {
synchronized (this) {
ensureOpen();
textOut.newLine();
textOut.flushBuffer();
charOut.flushBuffer();
if (autoFlush)
out.flush();
}
}
catch (InterruptedIOException x) {
Thread.currentThread().interrupt();
}
catch (IOException x) {
trouble = true;
}
}

  跳转到下一行。执行逻辑同方法write(char buf[]) ,故不予赘述。

打印方法集合

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
// 打印布尔值
public void print(boolean b) {
write(b ? "true" : "false");
}

// 打印一个字符
public void print(char c) {
write(String.valueOf(c));
}

// 打印一个int型数值
public void print(int i) {
write(String.valueOf(i));
}

// 打印一个long型数值
public void print(long l) {
write(String.valueOf(l));
}

// 打印一个float型数值
public void print(float f) {
write(String.valueOf(f));
}

// 打印一个double型数值
public void print(double d) {
write(String.valueOf(d));
}

// 打印一个字符数组
public void print(char s[]) {
write(s);
}

// 打印一个字符串值
public void print(String s) {
if (s == null) {
s = "null";
}
write(s);
}

// 打印一个对象
public void print(Object obj) {
write(String.valueOf(obj));
}

// 另起一行打印
public void println() {
newLine();
}

// 打印一个布尔值并换行
public void println(boolean x) {
synchronized (this) {
print(x);
newLine();
}
}

// 打印一个字符并换行
public void println(char x) {
synchronized (this) {
print(x);
newLine();
}
}

// 打印一个int型数值并换行
public void println(int x) {
synchronized (this) {
print(x);
newLine();
}
}

// 打印一个long型数值并换行
public void println(long x) {
synchronized (this) {
print(x);
newLine();
}
}

// 打印一个float型数值并换行
public void println(float x) {
synchronized (this) {
print(x);
newLine();
}
}

// 打印一个double型数值并换行
public void println(double x) {
synchronized (this) {
print(x);
newLine();
}
}

// 打印一个字符数组并换行
public void println(char x[]) {
synchronized (this) {
print(x);
newLine();
}
}

// 打印一个字符串并换行
public void println(String x) {
synchronized (this) {
print(x);
newLine();
}
}

// 打印一个对象并换行
public void println(Object x) {
String s = String.valueOf(x);
synchronized (this) {
print(s);
newLine();
}
}

public PrintStream printf(Locale l, String format, Object … args)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public PrintStream printf(Locale l, String format, Object ... args) {
return format(l, format, args);
}

public PrintStream format(Locale l, String format, Object ... args) {
try {
synchronized (this) {
ensureOpen();
if ((formatter == null)
|| (formatter.locale() != l))
formatter = new Formatter(this, l);
formatter.format(l, format, args);
}
} catch (InterruptedIOException x) {
Thread.currentThread().interrupt();
} catch (IOException x) {
trouble = true;
}
return this;
}

  按照指定的输出格式打印入参内容。printf方法和单独调用format方法的执行结果是一致的。在format方法中,如果指定Locale,那么就会按照该区域的风格打印指定格式的内容。否则就会按照默认的区域风格打印。format字段用来指定具体的打印格式和结构。args则包含了具体填充的实际内容。在format方法中,实际发挥作用的是Formatter.format的方法。其内部处理逻辑如下:

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
public Formatter format(Locale l, String format, Object ... args) {
ensureOpen();

// index of last argument referenced
int last = -1;
// last ordinary index
int lasto = -1;

FormatString[] fsa = parse(format);
for (int i = 0; i < fsa.length; i++) {
FormatString fs = fsa[i];
int index = fs.index();
try {
switch (index) {
case -2: // fixed string, "%n", or "%%"
fs.print(null, l);
break;
case -1: // relative index
if (last < 0 || (args != null && last > args.length - 1))
throw new MissingFormatArgumentException(fs.toString());
fs.print((args == null ? null : args[last]), l);
break;
case 0: // ordinary index
lasto++;
last = lasto;
if (args != null && lasto > args.length - 1)
throw new MissingFormatArgumentException(fs.toString());
fs.print((args == null ? null : args[lasto]), l);
break;
default: // explicit index
last = index - 1;
if (args != null && last > args.length - 1)
throw new MissingFormatArgumentException(fs.toString());
fs.print((args == null ? null : args[last]), l);
break;
}
} catch (IOException x) {
lastException = x;
}
}
return this;
}

private FormatString[] parse(String s) {
ArrayList<FormatString> al = new ArrayList<>();
Matcher m = fsPattern.matcher(s);
for (int i = 0, len = s.length(); i < len; ) {
if (m.find(i)) {
// Anything between the start of the string and the beginning
// of the format specifier is either fixed text or contains
// an invalid format string.
if (m.start() != i) {
// Make sure we didn't miss any invalid format specifiers
checkText(s, i, m.start());
// Assume previous characters were fixed text
al.add(new FixedString(s.substring(i, m.start())));
}

al.add(new FormatSpecifier(m));
i = m.end();
} else {
// No more valid format specifiers. Check for possible invalid
// format specifiers.
checkText(s, i, len);
// The rest of the string is fixed text
al.add(new FixedString(s.substring(i)));
break;
}
}
return al.toArray(new FormatString[al.size()]);
}

  format(Locale l, String format, Object … args)方法用来打印一个格式化了的内容。第9行代码用来识别出入参format中含有的有效的和无效的占位符,具体逻辑可浏览第44 ~ 71行代码。第10 ~ 40则遍历识别的占位符结果,并根据每个特定的占位符从参数集合args中取出对应位置的数据放入占位符所处的位置完成内容的格式化和输出打印。

public PrintStream append(CharSequence csq)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 在当前输出内容尾部追加新内容,如果入参为null,那么就输出“null”这四个字符到目标位置。
public PrintStream append(CharSequence csq) {
if (csq == null)
print("null");
else
print(csq.toString());
return this;
}

// 在当前输出内容尾部追加新内容,如果入参为null,那么就输出“null”这四个字符到目标位置,否则就输出从start开始,end - 1位置结束的子序列到目标输出位置。
public PrintStream append(CharSequence csq, int start, int end) {
CharSequence cs = (csq == null ? "null" : csq);
write(cs.subSequence(start, end).toString());
return this;
}

// 在当前输出内容尾部追加一个新字符。
public PrintStream append(char c) {
print(c);
return this;
}

public int flush()

1
2
3
4
5
6
7
8
9
10
11
public void flush() {
synchronized (this) {
try {
ensureOpen();
out.flush();
}
catch (IOException x) {
trouble = true;
}
}
}

  把数据强制刷新到输出流中。这是一个线程安全的方法,需要注意的是,如果在方法调用过程中出现了异常,那么异常不会被向上抛出,而是将trouble字段标记为true,以此来判定方法执行过程中是否出现了异常。

涉及基础知识点

  1. NIL

参考文献

  1. NIL




------------- End of this article, thanks! -------------


  版权声明:本文由Nathan R. Lee创作和发表,采用署名(BY)-非商业性使用(NC)-相同方式共享(SA)国际许可协议进行许可,转载请注明作者及出处。
  本文作者为 Nathan R. Lee
  本文标题为 Java I/O 08 - PrintStream
  本文链接为 https://marcuseddie.github.io/2018/java-PrintStream.html