System
System.in
System.in是一个典型的连接控制台程序和键盘输入的InputStream流。
通常当数据通过命令行参数或者配置文件传递给命令行Java程序的时候,System.in并不是很常用。
图形界面程序通过界面传递参数给程序,这是一块单独的Java IO输入机制。
System.out
System.out是一个PrintStream流。System.out一般会把你写到其中的数据输出到控制台上。
System.out通常仅用在类似命令行工具的控制台程序上。
System.out也经常用于打印程序的调试信息(尽管它可能并不是获取程序调试信息的最佳方式)。
System.err
System.err是一个PrintStream流。
System.err与System.out的运行方式类似,但它更多的是用于打印错误文本。
为了让错误信息更加显眼,会将错误信息以红色文本的形式通过System.err输出到控制台上。
实例
public static void main(String[] args) {
Scanner scanner = new Scanner( System.in );
System.out.print("请输入一个正整数 : ");
int number = scanner.nextInt();
if(number >= 0) {
System.out.println("你输入的是: " + number);
} else {
System.err.println("你输入的不合法!");
}
}
替换系统流
只需要把一个新的InputStream设置给System.in或者一个新的OutputStream设置给System.out或者System.err,之后的数据都将会在新的流中进行读取、写入。
OutputStream output = new FileOutputStream("system.out.txt");
PrintStream printOut = new PrintStream(output);
System.setOut(printOut);
请记住,务必在JVM关闭之前冲刷System.out(调用 flush()
),确保System.out把数据输出到了文件中。
常见的流相关类
InputStream/OutputStream
FileInputStream/FileOutputStream
PipedInputStream/PipedOutputStream
ObjectInputStream/ObjectOutputStream
字节流
数组
- ByteArrayInputStream
ByteArrayInputStream允许你从字节数组中读取字节流数据,代码如下:
byte[] bytes = ... //get byte array from somewhere.
InputStream input = new ByteArrayInputStream(bytes);
int data = input.read();
while(data != -1) {
//do something with data
data = input.read();
}
input.close();
如果数据存储在数组中,ByteArrayInputStream可以很方便地读取数据。
如果你有一个InputStream变量,又想从数组中读取数据呢?很简单,只需要把字节数组传递给ByteArrayInputStream的构造函数,在把这个ByteArrayInputStream赋值给InputStream变量就可以了(译者注:InputStream是所有字节输入流流的基类,Reader是所有字符输入流的基类,OutputStream与Writer同理)。
- ByteArrayOutputStream
ByteArrayOutputStream允许你以数组的形式获取写入到该输出流中的数据,代码如下:
ByteArrayOutputStream output = new ByteArrayOutputStream();
//write data to output stream
byte[] bytes = output.toByteArray();
Filter
- FilterInputStream
FilterInputStream是实现自定义过滤输入流的基类,基本上它仅仅只是覆盖了InputStream中的所有方法。
就我自己而言,我没发现这个类明显的用途。
除了构造函数取一个InputStream变量作为参数之外,我没看到FilterInputStream任何对InputStream新增或者修改的地方。
如果你选择继承FilterInputStream实现自定义的类,同样也可以直接继承自InputStream从而避免额外的类层级结构。
FilterInputStream inputStream = new FilterInputStream(new FileInputStream("c:\\myfile.txt"));
- FilterOutputStream
同上。
Buffered
- BufferedInputStream
BufferedInputStream能为输入流提供缓冲区,能提高很多IO的速度。
你可以一次读取一大块的数据,而不需要每次从网络或者磁盘中一次读取一个字节。特别是在访问大量磁盘数据时,缓冲通常会让IO快上许多。
为了给你的输入流加上缓冲,你需要把输入流包装到BufferedInputStream中,代码如下:
InputStream input = new BufferedInputStream(new FileInputStream("c:\\data\\input-file.txt"));
很简单,不是吗?你可以给BufferedInputStream的构造函数传递一个值,设置内部使用的缓冲区设置大小(译者注:默认缓冲区大小8 * 1024B),就像这样:
InputStream input = new BufferedInputStream(new FileInputStream("c:\\data\\input-file.txt"), 8 * 1024);
这个例子设置了8KB的缓冲区。最好把缓冲区大小设置成1024字节的整数倍,这样能更高效地利用内置缓冲区的磁盘。
除了能够为输入流提供缓冲区以外,其余方面BufferedInputStream基本与InputStream类似。
- BufferedOutputStream
与BufferedInputStream类似,BufferedOutputStream可以为输出流提供缓冲区。
可以构造一个使用默认大小缓冲区的BufferedOutputStream(译者注:默认缓冲区大小8 * 1024B),代码如下:
OutputStream output = new BufferedOutputStream(new FileOutputStream("c:\\data\\output-file.txt"));
也可以手动设置缓冲区大小,代码如下:
OutputStream output = new BufferedOutputStream(new FileOutputStream("c:\\data\\output-file.txt"), 8 * 1024);
为了更好地使用内置缓冲区的磁盘,同样建议把缓冲区大小设置成1024的整数倍。
除了能够为输出流提供缓冲区以外,其余方面BufferedOutputStream基本与OutputStream类似。
唯一不同的时,你需要手动 flush()
方法确保写入到此输出流的数据真正写入到磁盘或者网络中。
Data
- DataInputStream
DataInputStream可以使你从输入流中读取Java基本类型数据,而不必每次读取字节数据。
你可以把InputStream包装到DataInputStream中,然后就可以从此输入流中读取基本类型数据了,代码如下:
DataInputStream input = new DataInputStream(new FileInputStream("binary.data"));
int aByte = input.read();
int anInt = input.readInt();
float aFloat = input.readFloat();
double aDouble = input.readDouble();//etc.
input.close();
当你要读取的数据中包含了int,long,float,double这样的基本类型变量时,DataInputStream可以很方便地处理这些数据。
- DataOutputStream
DataOutputStream可以往输出流中写入Java基本类型数据,例子如下:
DataOutputStream output = new DataOutputStream(new FileOutputStream("binary.data"));
output.write(45);
//byte data output.writeInt(4545);
//int data output.writeDouble(109.123);
//double data output.close();
其他方面与DataInputStream类似,不再赘述。
字符流
BufferedReader
BufferedReader能为字符输入流提供缓冲区,可以提高许多IO处理的速度。
你可以一次读取一大块的数据,而不需要每次从网络或者磁盘中一次读取一个字节。特别是在访问大量磁盘数据时,缓冲通常会让IO快上许多。
BufferedReader和BufferedInputStream的主要区别在于,BufferedReader操作字符,而BufferedInputStream操作原始字节。
只需要把Reader包装到BufferedReader中,就可以为Reader添加缓冲区(译者注:默认缓冲区大小为8192字节,即8KB)。代码如下:
Reader input = new BufferedReader(new FileReader("c:\\data\\input-file.txt"));
你也可以通过传递构造函数的第二个参数,指定缓冲区大小,代码如下:
Reader input = new BufferedReader(new FileReader("c:\\data\\input-file.txt"), 8 * 1024);
这个例子设置了8KB的缓冲区。最好把缓冲区大小设置成1024字节的整数倍,这样能更高效地利用内置缓冲区的磁盘。
除了能够为输入流提供缓冲区以外,其余方面BufferedReader基本与Reader类似。BufferedReader还有一个额外readLine()方法,可以方便地一次性读取一整行字符。
BufferedWriter
与BufferedReader类似,BufferedWriter可以为输出流提供缓冲区。
可以构造一个使用默认大小缓冲区的BufferedWriter(译者注:默认缓冲区大小8 * 1024B),代码如下:
Writer writer = new BufferedWriter(new FileWriter("c:\\data\\output-file.txt"));
也可以手动设置缓冲区大小,代码如下:
Writer writer = new BufferedWriter(new FileWriter("c:\\data\\output-file.txt"), 8 * 1024);
为了更好地使用内置缓冲区的磁盘,同样建议把缓冲区大小设置成1024的整数倍。
除了能够为输出流提供缓冲区以外,其余方面BufferedWriter基本与Writer类似。
类似地,BufferedWriter也提供了writeLine()方法,能够把一行字符写入到底层的字符输出流中。
值得注意是,你需要手动flush()方法确保写入到此输出流的数据真正写入到磁盘或者网络中。
FilterReader
与FilterInputStream类似,FilterReader是实现自定义过滤输入字符流的基类,基本上它仅仅只是简单覆盖了Reader中的所有方法。
就我自己而言,我没发现这个类明显的用途。除了构造函数取一个Reader变量作为参数之外,我没看到FilterReader任何对Reader新增或者修改的地方。
如果你选择继承FilterReader实现自定义的类,同样也可以直接继承自Reader从而避免额外的类层级结构。
FilterWriter
内容同FilterReader,不再赘述。
PipedReader
PipedReader能够从管道中读取字符流。与PipedInputStream类似,不同的是PipedReader读取的是字符而非字节。
换句话说,PipedReader用于读取管道中的文本。代码如下:
Reader reader = new PipedReader(pipedWriter);
int data = reader.read();
while(data != -1) {
//do something with data...
doSomethingWithData(data);
data = reader.read();
}
reader.close();
read()方法返回一个包含了读取到的字符内容的int类型变量(译者注:0~65535)。
如果方法返回-1,表明PipedReader中已经没有剩余可读取字符,此时可以关闭PipedReader。
-1是一个int类型,不是byte或者char类型,这是不一样的。
正如你所看到的例子那样,一个PipedReader需要与一个PipedWriter相关联,当这两种流联系起来时,就形成了一条管道。
PipedWriter
PipedWriter能够往管道中写入字符流。与PipedOutputStream类似,不同的是PipedWriter处理的是字符而非字节,PipedWriter用于写入文本数据。
代码如下:
PipedWriter writer = new PipedWriter(pipedReader);
while(moreData()) {
int data = getMoreData();
writer.write(data);
}
writer.close();
PipedWriter的write()方法取一个包含了待写入字节的int类型变量作为参数进行写入,同时也有采用字符串、字符数组作为参数的write()方法。
CharArrayReader
CharArrayReader能够让你从字符数组中读取字符流。
代码如下:
char[] chars = ... //get char array from somewhere.
Reader reader = new CharArrayReader(chars);
int data = reader.read();
while(data != -1) {
//do something with data
data = reader.read();
}
reader.close();
如果数据的存储媒介是字符数组,CharArrayReader可以很方便的读取到你想要的数据。
CharArrayReader会包含一个字符数组,然后将字符数组转换成字符流。
(译者注:CharArrayReader有2个构造函数,一个是CharArrayReader(char[] buf),将整个字符数组创建成一个字符流。
另外一个是CharArrayReader(char[] buf, int offset, int length),把buf从offset开始,length个字符创建成一个字符流。
CharArrayWriter
CharArrayWriter能够把字符写入到字符输出流writer中,并且能够将写入的字符转换成字符数组。
代码如下:
CharArrayWriter writer = new CharArrayWriter();
//write characters to writer.
char[] chars = writer.toCharArray();
当你需要以字符数组的形式访问写入到writer中的字符流数据时,CharArrayWriter是个不错的选择。
其他流
PushbackInputStream
PushbackInputStream用于解析InputStream内的数据。
有时候你需要提前知道接下来将要读取到的字节内容,才能判断用何种方式进行数据解析。
PushBackInputStream允许你这么做,你可以把读取到的字节重新推回到InputStream中,以便再次通过 read() 读取。
代码如下:
PushbackInputStream input = new PushbackInputStream(new FileInputStream("c:\\data\\input.txt"));
int data = input.read();
input.unread(data);
可以通过PushBackInputStream的构造函数设置推回缓冲区的大小,代码如下:
PushbackInputStream input = new PushbackInputStream(new FileInputStream("c:\\data\\input.txt"), 8);
SequenceInputStream
SequenceInputStream把一个或者多个InputStream整合起来,形成一个逻辑连贯的输入流。
当读取SequenceInputStream时,会先从第一个输入流中读取,完成之后再从第二个输入流读取,以此推类。
代码如下:
InputStream input1 = new FileInputStream("c:\\data\\file1.txt");
InputStream input2 = new FileInputStream("c:\\data\\file2.txt");
InputStream combined = new SequenceInputStream(input1, input2);
通过SequenceInputStream,例子中的2个InputStream使用起来就如同只有一个InputStream一样(译者注:SequenceInputStream的read()方法会在读取到当前流末尾时,关闭流,并把当前流指向逻辑链中的下一个流,最后返回新的当前流的read()值)。
PrintStream
PrintStream允许你把格式化数据写入到底层OutputStream中。
比如,写入格式化成文本的int,long以及其他原始数据类型到输出流中,而非它们的字节数据。
代码如下:
PrintStream output = new PrintStream(outputStream);
output.print(true);
output.print((int) 123);
output.print((float) 123.456);
output.printf(Locale.UK, "Text + data: %1$", 123);
output.close();
PrintStream包含2个强大的函数,分别是format()和printf()(这两个函数几乎做了一样的事情,但是C程序员会更熟悉printf())。
public PrintStream printf(String format, Object ... args) {
return format(format, args);
}
PushbackReader
PushbackReader与PushbackInputStream类似,唯一不同的是PushbackReader处理字符,PushbackInputStream处理字节。
代码如下:
PushbackReader reader = new PushbackReader(new FileReader("c:\\data\\input.txt"));
int data = reader.read();
reader.unread(data);
同样可以设置缓冲区大小,代码如下:
PushbackReader reader = new PushbackReader(new FileReader("c:\\data\\input.txt"), 8);
LineNumberReader
LineNumberReader是记录了已读取数据行号的BufferedReader。
默认情况下,行号从0开始,当LineNumberReader读取到行终止符时,行号会递增(译者注:换行\n,回车\r,或者换行回车\n\r都是行终止符)。
你可以通过getLineNumber()方法获取当前行号,通过setLineNumber()方法设置当前行数
(译者注:setLineNumber()仅仅改变LineNumberReader内的记录行号的变量值,不会改变当前流的读取位置。流的读取依然是顺序进行,意味着你不能通过setLineNumber()实现流的跳跃读取)。
代码如下:
LineNumberReader reader = new LineNumberReader(new FileReader("c:\\data\\input.txt"));
int data = reader.read();
while(data != -1){
char dataChar = (char) data;
data = reader.read();
int lineNumber = reader.getLineNumber();
}
如果解析的文本有错误,LineNumberReader可以很方便地定位问题。
当你把错误报告给用户时,如果能够同时把出错的行号提供给用户,用户就能迅速发现并且解决问题。
StreamTokenizer
StreamTokenizer(译者注:请注意不是StringTokenizer)可以把输入流(译者注:InputStream和Reader。
通过InputStream构造StreamTokenizer的构造函数已经在JDK1.1版本过时,推荐将InputStream转化成Reader,再利用此Reader构造StringTokenizer)分解成一系列符号。比如,句子”Mary had a little lamb”的每个单词都是一个单独的符号。
当你解析文件或者计算机语言时,为了进一步的处理,需要将解析的数据分解成符号。通常这个过程也称作分词。
通过循环调用nextToken()可以遍历底层输入流的所有符号。
在每次调用nextToken()之后,StreamTokenizer有一些变量可以帮助我们获取读取到的符号的类型和值。
这些变量是:
ttype 读取到的符号的类型(字符,数字,或者行结尾符)
sval 如果读取到的符号是字符串类型,该变量的值就是读取到的字符串的值
nval 如果读取到的符号是数字类型,该变量的值就是读取到的数字的值
- 代码如下:
StreamTokenizer tokenizer = new StreamTokenizer(new StringReader("Mary had 1 little lamb..."));
while(tokenizer.nextToken() != StreamTokenizer.TT_EOF){
if(tokenizer.ttype == StreamTokenizer.TT_WORD) {
System.out.println(tokenizer.sval);
} else if(tokenizer.ttype == StreamTokenizer.TT_NUMBER) {
System.out.println(tokenizer.nval);
} else if(tokenizer.ttype == StreamTokenizer.TT_EOL) {
System.out.println();
}
}
译者注:TT_EOF表示流末尾,TT_EOL表示行末尾。
StreamTokenizer可以识别标示符,数字,引用的字符串,和多种注释类型。
你也可以指定何种字符解释成空格、注释的开始以及结束等。在StreamTokenizer开始解析之前,所有的功能都可以进行配置。
PrintWriter
与PrintStream类似,PrintWriter可以把格式化后的数据写入到底层writer中。由于内容相似,不再赘述。
值得一提的是,PrintWriter有更多种构造函数供使用者选择,除了可以输出到文件、Writer以外,还可以输出到OutputStream中(译者注:PrintStream只能把数据输出到文件和OutputStream)。
StringReader
StringReader能够将原始字符串转换成Reader,代码如下:
Reader reader = new StringReader("input string...");
int data = reader.read();
while(data != -1) {
//do something with data...
doSomethingWithData(data);
data = reader.read();
}
reader.close();
StringWriter
StringWriter能够以字符串的形式从Writer中获取写入到其中数据,代码如下:
StringWriter writer = new StringWriter();
//write characters to writer.
String data = writer.toString();
StringBuffer dataBuffer = writer.getBuffer();
toString() 方法能够获取StringWriter中的字符串数据。
getBuffer() 方法能够获取StringWriter内部构造字符串时所使用的StringBuffer对象。
参考资料
http://ifeve.com/java-io-system-in-system-out-system-err/
https://blog.csdn.net/euller/article/details/50967096