本文摘要自Java I/O一书,选取中间精炼简单的部分,译成中文,并书写成文章。
1.什么是流? What is Stream?
A stream is an ordered sequence of bytes of indeterminate length.
流是长度不确定的字节序列.
输入流将其他通用的外部目标移入Java程序,而输出流则是将Java程序中的数据移到外部。
输入到Java程序的来源是多样的,输出流也有多种目的地。
所有的输入输出流在形式上是简单的,你使用相同的类和相同的方法来进行操作。
2.流来自什么地方?
最简单的输入流莫过于 System.in
输出流则是 System.out System.err
当然还有 文件流,网络流,管道流
3.大体有一些什么类呢?
最基本的:java.io.InputStream java.io.OutputStream
此外,还有(不必记住它们,后面会介绍)
BufferedInputStream
BufferedOutputStream
ByteArrayInputStream
ByteArrayOutputStream
DataInputStream
DataOutputStream
FileInputStream
FileOutputStream
FilterInputStream
FilterOutputStream
ObjectInputStream
ObjectOutputStream
PipedInputStream
PipedOutputStream
PrintStream
PushbackInputStream
SequenceInputStream
还有来自java.util.zip,java.util.jar包与压缩相关的流;
来自java.security和JCE的摘要和安全方面相关的流。
4.数值对象
由于InputStream 和OutputStream是以byte(字节)为单位进行读取写入的;
而Readers和Writers是以character(字符)为单位进行读写操作的,
所以必须理解Java是如何处理基本类型,以及如何将其中一个转化为另一个。
int 4byte ,big-endian(大端字节序)
BE:将高序字节存储在起始地址
例如:0x4321
0x4000 0x4001 0x4002 0x4003
4 3 2 1
byte :8bit
但是,由于任何 int以下数字的加法都将转化为int,所以直接使用byte并非最佳实践。
byte b1 = 22; byte b2 = 23; byte b3 = b1 + b2;//error!need obvious cast
5.输入输出基本方法
处于这样的原因,所以输入流虽然是对byte进行读操作,但是返回值确实int
public abstract int read( ) throws IOException使用0~255来表示读到的数据,使用-1表示流已经到达末尾。
而在输出流,也可以看到这样的实现:
public abstract void write(int b) throws IOException注意,传入的数据b只运行0~255,否则将截取
使用b = b & 0x000000FF;
此外,允许读取数组:
public int read(byte[] data) throws IOException public int read(byte[] data, int offset, int length) throws IOException
一批次读取多个byte能显著的提高效率,这是因为
单一的byte任然需要在JVM中占据4byte空间,但bytes数组占据了它实际所需空间的总和。
虽然byte只能保存-128~127,但是下面这个方法用于有符号型到无符号型的转化:
int unsignedByte = signedByte >= 0 ? signedByte : 256 + signedByte;
6.字符数据
Java程序需要进行处理的内容:数字是一部分,而文本是另一部分。
而字符码是和编码有关的,例如在Ascii中将A编码成65,B编码成66;不同的编码,不同的结果。
Java支持多种语言的多种编码方式,
Java内置使用Unicode字符集,它是Latin-1字符集的超集。
7. ASCII和Latin-1
American Standard Code for Information Interchange, is a 7-bit character set
Latin-1, is an 8-bit character set that's a strict superset of ASCII
Latin-1相当于在标准ASCII的基础上增加了1bit,然后新增了128个字符,用于表示拉丁文和一些图形
8. Unicode
Latin-1能够满足西欧国家的使用,但是却满足不了Cyrillic, Greek, Arabic, Hebrew, or Devanagari,
而中国,日本这类象形文字的就更不可能了,所以开发了Unicode。
它有100万种可能,能够满足绝大多数国家语言的需求。
[重要]Unicode只是一个字符集,而不是一种编码方式。
也就是说,虽然Unicode规定了A编码成65,但并没有说是用1个字节,2个字节还是4个字节,这就导致了多种可能。
但实际上最常用的编码方式还是UTF-8, UTF-16, and UTF-32。
UTF-32是最幼稚的编码方式,简单的使用4byte代表所有的数据。
UTF-16,使用2字节来表示常用字符,使用4字节来表示中文,音乐数学符号,和其他已经死亡的语言。
UTF-8,最高效的方式,1字节表示ASCII,2字节表示其他字母表,3~4字节来表示亚洲语系。
除了上面介绍的三种ASCII,Latin-1,Unicode以外,还有很多的字符集。
9. char数据类型
在Java中,文本主要由char,char array,String组成。
char由2个字节构成,使用默认的UTF-16进行编码。
[注意]String.getBytes()将会根据字符集来返回byte数目。
10. Reader和Writers
输入输出流是byte-based;而读写器是character-based,它依据不同的编码集而具有不同位宽。
例如ASCII 和 Latin-1 使用 1-byte characters. UTF-32 使用 4-byte characters. UTF-8 使用可变宽度的characters。
java.io.Reader和java.io.Writer是顶层的抽象类
包括各8个读写器:
对比与流,则是将其中的byte替换成了char
例如在OutputStream中,其中一个写方法为
public void write(byte[] data) throws IOException则在Writer中,变为了
public void write(char[] data) throws IOException,但是 Writer提供了两个额外的方法,用于操作字符串。
public void write(String s) throws IOException public void write(String s, int offset, int length) throws IOException
11. 缓冲与通道 Buffers and Channels
传统的流会阻塞进程,直到流结束,而过去的处理方式是起一个单独的线程来处理它。
而线程的创建和管理都需要大量工作。
Java1.4中引入了非阻塞I/O。
流只是一个代表,实际工作都在buffer和channel中完成了;
可多通道读取写入流,这样一个线程就处理多个通道的信息。
通道和缓冲还可用于内存映射的I/O,文件被当做大的内存块来使用。
被映射的文件可以使用诸如
int x = file.getInt(1067)来的形式来读取,或者
file.putInt(x, 1067) 形式来写入。
通道和缓冲比流和字节要复杂,但是对于特定的应用程序,这些复杂度是值得的。
12. 普遍存在的异常 The Ubiquitous IOException
自从计算机运转开始,输入输出就不可靠。
几乎每一个输入输出流都将抛出 IOException,它是一个受检异常。
所以,你使用这些流时,要么抛出异常,要么try catch 它。唯一的例外是PrintStream and PrintWriter。
IOException有很多子类,用于表示特定的I/O异常。
13. 控制台 System.out, System.in, and System.err
System.out与 C或者Unix中的stdout一致
System.err通常没啥用,debug的时候可能会用到
System.in标准输入
这几个流都可重定向:
public static void setIn(InputStream in) public static void setOut(PrintStream out) public static void setErr(PrintStream err)