1
A little older, a little wiser, but happy to see you.
——Interstellar

2018年了,再过一年就要30岁了,年龄有一些,智慧也有了一些。主要集中在技术、技术经验和工作经验。一直都记录在有道云笔记里,现在拿出来分享记录下,也希望能得到大家的feedback。
另外也想坚持做一些事情,2018年,我的目标是每天都过的有意义。
这就需要定义什么叫有意义:

  • 每天都能按照自己,真正的自己的意愿。不受集体思想的影响。做自己!
  • 每天都沉淀东西出来,一天天的积累。
  • 一次以上旅游。
  • 年底需计划下一年的事情

所以其中一件需要积累和沉淀的就是写作能力,或许刚开始需要参考一些书籍,但我希望能沉淀下来。所以给自己立个flag,每周在segmentfault发一篇文章。 话不多说,马上进入正题:

Java的IO系统

在学习IO之前,跟学其他新东西一样,最核心最难的就是理解概念,特别是一堆很相近的概念,往往让人产生困惑。如Java的IO系统,我们在这需区分以下概念:

  1. 流(Stream),是有IO系统设计者创造出来的概念,将具体设备的IO抽象为流,所以不同的设备IO对于不同的流,如FileInputStream和ByteArrayInputStream,前者的设备是操作系统的文件,后者则是操作系统的内存。
  2. 过滤器流(FilterInputStream),如BufferedInputStream等,是JavaIO类库,是为了提供一些类让你能够处理一些极为常见的数据格式。如BufferedInputStream是为了解决缓冲问题,DataInputStream是为了解决Java的数据格式。
  3. 读写器(Reader/Writer),由于流和过滤器流还是仅次于处理字节,也就是二进制。 而处理文本,就涉及到字符编码格式的问题。 实际上也是相当于过滤器流,也是用装饰模式,把面向字节的接口改为面向字符的接口。

I/O

对于(程序)语言设计者来说,其中一个难的任务是:创造一个好的I/O系统。

这个挑战时来源于要覆盖所有的可能性,不仅包括不同的I/O源和与之通讯的接受端(文件files、控制台the console、网络链接network connections等),而且还需要以多种不同的方式与它们进行通信(顺序sequential、随机读取random-access、缓冲buffered、二进制binary、按字符character、按行by lines、按字by words等)。
为了解决这个难的任务,Java类库的设计者创建了大量的类。也正是这么多类,导致刚学IO的人一开始就被如此多类给难倒。
自从Java1.0版本开始,Java的I/O类库发生明显的改变,在原来面向字节的类中添加面向字符和基于Unicode的类。在JDK 1.4中添加了NIO类(其中N代表new,过了那么多年,其实不新啦)。
因此,在充分理解IO系统和正确地运用之前,我们需要学习相当数量的类。除此之外,很有必要理解IO类库的演变过程,或许你会这么说“不要用历史打扰我,我只想知道怎么用”。 但问题是,如果缺乏历史的眼光,很快我们就会对什么时候该使用哪些类,以及什么时候不该使用它们而感到困惑。

输入和输出

使用IO库往往会接触到“流”这个概念,它代表任何有能力产出数据的数据源对象或者是有能力接收数据的接收端对象。设计“流”这个概念,是为了屏蔽实际的IO设备中处理数据的细节。

InputStream和OuputStream时相当原始的类。它们可以单个或成组地读/写字节,但仅此而已。要确定字节的含义(比如,它们时整数还是IEEE 754浮点数或是Unicode文本),这完全由程序员和代码来完成。不过,有一些极为常见的数据格式,如果在类中提供这些数据格式的固定实现,会很有好处。
于是Java提供了很多过滤器,可以附加到原始流中,在原始字节和各种格式之间来回转换。
过滤器有两个版本:过滤器流以及阅读器读写器
过滤器流主要处理二进制数据,也就是作为字节处理。
阅读器书写器主要时处理多种编码文本,如UTF-8和ISO 8859-1。

过滤器流——缓冲流BufferedInputStream和BufferedOutputStream

BufferedOutputStream类将写入的数据存储到缓冲区中(一个名为buf的保护字节数组字段),直到缓冲区满或刷新输出流。
它首先尝试从缓冲区去获取数据,当缓存区没有数据时,流才从底层的数据源中读取数据。这时,它会从源读取尽可能多的数据进入缓存区,而不管是否马上需要所有这些数据。那些不会立刻用到的数据(已缓存的)会在以后调用read()读取。
因为从磁盘中读取文件时,从底层流中读取几百字节的数据与读取1个字节数据的速度几乎一样快,因此缓冲可以显著提高性能。
但对于网络连接,这种效果则不明显,在这里瓶颈往往是网络传送数据的速度,而不是网络接口向程序传送数据的速度或程序运行的速度。尽管如此,缓冲输入没有什么坏处,随着网络的速度加快会变得更为重要。

过滤器流——PrintStream

PrintStream类是大多数程序员都会遇到的第一个过滤器输出流,因为System.out就是一个PrintStream。

PrintStream是有害的,网络程序员应当像躲避瘟疫一样躲避它。

第一个问题:println()方法输出是与平台有关的。在UNIX(包括Mac OS X)下是换行符(n),在Mac OS 9下时回车符(r),在Windows下时回车/换行对(rn)
第二个问题:PrintStream假定使用所在平台的默认编码方式。
第三个问题:PrintStream吞掉了所有异常。

过滤器流——数据流DataInputStream和DataOutputStream

DataInputStream和DataOutputStream提供了一些方法,可以用二进制格式读/写Java的基本数据类型和字符串。所用的二进制格式主要用于在两个不同的Java程序之间交换数据(可能通过网络链接、数据文件、管道或其他中间介质)。
所有数据都以big-endian格式写入。byte会写为1字节,short写为2字节,int写为4字节,long写为8字节。浮点数和双精浮点数分别写为4字节和8字节的IEEE 754格式。 布尔数写为1字节,1表示true,0表示false。字符写为两个无符号字节。
writeChars(String s)方法只是对String参数迭代(循环)处理,将各个字符按顺序写为一个2字节的big-endian Unicode字节(确切地讲是UTF-16码点)。
writeBytes(String s)方法迭代处理String参数,但只写入每个字符的低字节。因此如果包含有Latin-1字符集意外的字符,其中信息将会丢失。对于一些指定了ASCII编码的网络协议来说,这个方法或许有用,但多数情况下都应避免使用。
writeChars和writeBytes方法都不会对输出流的字符串的长度编码。因此,你无法真正区分原始字符和作为字符串一部分的字符。简单的说就是只支持ASCII编码格式的字符串。
writeUTF(String s)方法则包括了字符串的长度。它将字符串本身用Unicode UTF-8编码的一个变体进行编码。由于这个变体编码方式与大多数非Java软件不兼容,所以应当只用于其他使用DataInputStream读取字符串的Java程序进行数据交换。(如果Sun当初把这个方法及相应的读取方式命名为writeString()和readString(),而不是writeUTF()和readUTF(),那就不会产生任何混淆了)。
因此,为了与其他软件交换UTF-8文本,应使用带适当编码功能的InputStreamReader

阅读器和读写器

许多程序员在编码时有一个坏习惯,好像所有文本都是ASCII编码,或者至少是该平台的内置编码方式。
虽然有些协议的编码,采用ASCII编码方式,如较老的、较简单的网络协议(daytime, quote of the day和charge)。
但HTTP协议和其他很多较新的协议却不是这样,它们允许多种本地化编码,如K0I8-R西里尔文、Big-5中文和土耳其语使用的ISO8859-9。
Java的内置字符集时Unicode的UTF-16编码。

Java定义了两个抽象类,读/写字符的基本API,分别是java.io.Reader和java.io.Writer。

Reader和Writer最重要的具体子类是InputStreamReader和OutputStreamWriter类。InputStreamReader包含StreamDecoder类,而StreamDecoder类封装一个底层输入流InputStream,利用多态,可以从不同设备中读取原始字节,然后根据指定的编码方式,将这些字节转换为Unicode字符。OutputStreamWriter类包含类StreamEncoder,StreamEncoder类封装一个底层的输出流OutputStream,利用多态,可以往不同设备中写入数据,OutputStreamWriter从运行中的程序中接受Unicode字符,然后使用制定的编码方式将这些字符转为字节,再将这些字节写入底层输入流中。

阅读器和读写器的过滤器

与过滤器流一样,Reader和Writer有很多子类可以完成特定的过滤工作,包括:

  • BufferedReader
  • BufferedWriter
  • LineNumberReader
  • PushbackReader
  • PrintWriter

BufferedReader和BufferedWriter使用一个内部字符数组作为缓冲区。
当程序从BufferedReader读取时,文本会从缓冲区得到,而不是直接从底层输入流或其他文本源读取。当缓冲区清空时,才会去读取文本,将文本填充此缓冲区,尽管这些文本不是全部都立即需要,这样可以使以后的读取速度更快。当程序写入一个BufferedWriter时,文本被放置在缓冲区中。只有缓冲区填满或者当书写器显式刷新输出时,文本才会被移到底层输出流或其他目标,这使得写入也要快很多。

参考:
《Java编程思想》
《Java网络编程》

UML图:

图片描述

图片描述


电脑杂技集团
208 声望32 粉丝

这家伙好像很懂计算机~