欢迎光临
我们一直在努力

TIJ阅读笔记(第十二章)-JSP教程,Java技巧及代码

建站超值云服务器,限时71元/月

12: java i/o 系统
对编程语言的设计者来说,创建一套好的输入输出(i/o)系统,是一项难度极高的任务。
file 类
在介绍直接从流里读写数据的类之前,我们先介绍一下处理文件和目录的类。

你会认为这是一个关于文件的类,但它不是。你可以用它来表示某个文件的名字,也可以用它来表示目录里一组文件的名字。如果它表示的是一组文件,那么你还可以用list( )方法来进行查询,让它会返回string数组。由于元素数量是固定的,因此数组会比容器更好一些。如果你想要获取另一个目录的清单,再建一个file对象就是了。
目录列表器
假设你想看看这个目录。有两个办法。一是不带参数调用list( )。它返回的是file对象所含内容的完整清单。但是,如果你要的是一个"限制性列表(restricted list)"的话 —— 比方说,你想看看所有扩展名为.java的文件 —— 那么你就得使用"目录过滤器"了。这是一个专门负责挑选显示file对象的内容的类。

filenamefilter接口的声明:
public interface filenamefilter { boolean accept(file dir, string name);}

accept( )方法需要两个参数,一个是file对象,表示这个文件是在哪个目录里面的;另一个是string,表示文件名。虽然你可以忽略它们中的一个,甚至两个都不管,但是你大概总得用一下文件名吧。记住,list( )会对目录里的每个文件调用accept( ),并以此判断是不是把它包括到返回值里;这个判断依据就是accept( )的返回值。

切记,文件名里不能有路径信息。为此你只要用一个string对象来创建file对象,然后再调用这个file对象的getname( )就可以了。它会帮你剥离路径信息(以一种平台无关的方式)。然后再在accept( )里面用正则表达式(regular expression)的matcher对象判断,regex是否与文件名相匹配。兜完这个圈子,list( )方法返回了一个数组。
匿名内部类
注意,filter( )的参数必须是final的。要想在匿名内部类里使用其作用域之外的对象,只能这么做。

可以用匿名内部类来创建专门供特定问题用的,一次性的类。这种做法的好处是,它能把解决某个问题的代码全都集中到一个地方。但是从另一角度来说,这样做会使代码的可读性变差,所以要慎重。
查看与创建目录
file类的功能不仅限于显示文件或目录。它还能帮你创建新的目录甚至是目录路径(directory path),如果目录不存在的话。此外它还能用来检查文件的属性(大小,上次修改的日期,读写权限等),判断file对象表示的是文件还是目录,以及删除文件。

renameto( )这个方法会把文件重命名成(或者说移动到)新的目录,也就是参数所给出的目录。而参数本身就是一个file对象。这个方法也适用于目录。
输入与输出
i/o类库常使用"流(stream)"这种抽象。所谓"流"是一种能生成或接受数据的,代表数据的源和目标的对象。流把i/o设备内部的具体操作给隐藏起来了。

java的i/o类库分成输入和输出两大部分。所有inputstream和reader的派生类都有一个基本的,继承下来的,能读取单个或byte数组的read( )方法。同理,所有outputstream和writer的派生类都有一个基本的,能写入单个或byte数组的write( )方法。但通常情况下,你是不会去用这些方法的;它们是给其它类用的 —— 而后者会提供一些更实用的接口。因此,你很少会碰到只用一个类就能创建一个流的情形,实际上你得把多个对象叠起来,并以此来获取所需的功能。java的流类库之所以会那么让人犯晕,最主要的原因就是"你必须为创建一个流而动用多个对象"。
inputstream的种类
inputstream的任务就是代表那些能从各种输入源获取数据的类。这些源包括:
byte数组 string对象 文件 类似流水线的"管道(pipe)"。把东西从一头放进去,让它从另一头出来。 一个"流的序列(a sequence of other streams)",可以将它们组装成一个单独的流。 其它源,比如internet的连接。(这部分内容在thinking in enterprise java中讨论。)
这些数据源各自都有与之相对应的inputstream的子类。此外,filterinputstream也是inputstream的子类,其作用是为基类提供"decorator(修饰)"类,而decorator又是为inputstream配置属性和接口的。

表12-1. inputstream的种类 类 功能 构造函数的参数 用法 bytearrayinputstream 以缓冲区内存为inputstream 要从中提取byte的那个缓冲区 一种数据源:要把它连到filterinputstream对象,由后者提供接口。 stringbufferinputstream 以string为inputstream 需要一个string对象。实际上程序内部用的是stringbuffer。 一种数据源:要把它连到filterinputstream对象,由后者提供接口。 fileinputstream 专门用来读文件的 一个表示文件名的string对象,也可以是file或 filedescriptor对象。 一种数据源:要把它连到filterinputstream对象,由后者提供接口。 pipedinputstream 从pipedoutputstream提取数据。实现"管道"功能。 pipedoutputstream 一种多线程环境下的数据源,把它连到filterinputstream对象,由后者提供的接口。 sequenceinputstream 将两个或更多的inputstream合并成一个inputstream。 两个inputstream对象,或一个inputsteam对象容器的enumerator 一种数据源:要把它连到filterinputstream对象,由后者提供接口。 filterinputstream 一个为decorator定义接口用的抽象类。而decorator的作用是为inputstream实现具体的功能。详见表12-3。 见表 12-3 见表 12-3
outputstream的种类
这部分都是些决定往哪里输出的类:是byte的数组(不能是string;不过你可以根据byte数组创建字符串)还是文件,或者是"管道"。

此外,filteroutputstream还是decorator类的基类。它会为outputstream安装属性和适用的接口。

表12-2. outputstream的种类 类 功能 构造函数的参数 用法 bytearrayoutputstream 在内存里创建一个缓冲区。数据送到流里就是写入这个缓冲区。 缓冲区初始大小,可选。 要想为数据指定目标,可以用filteroutputstream对其进行包装,并提供接口。 fileoutputstream 将数据写入文件。 一个表示文件名的字符串,也可以是file或filedescriptor对象。 要想为数据指定目标,可以用filteroutputstream对其进行包装,并提供接口。 pipedoutputstream 写入这个流的数据,最终都会成为与之相关联的pipedinputstream的数据源。否则就不成其为"管道"了。 pipedinputstream 要想在多线程环境下为数据指定目标,可以用filteroutputstream对其进行包装,并提供接口。 filteroutputstream 一个给decorator提供接口用的抽象类。而decorator的作用是为outputstream实现具体的功能。详见表12-4 见表12-4 见表12-4
添加属性与适用的接口
使用"分层对象(layered objects)",为单个对象动态地,透明地添加功能的做法,被称为decorator pattern。(模式是thinking in patterns (with java)的主题。)decorator模式要求所有包覆在原始对象之外的对象,都必须具有与之完全相同的接口。这使得decorator的用法变得非常的透明–无论对象是否被decorate过,传给它的消息总是相同的。这也是java i/o类库要有"filter(过滤器)"类的原因:抽象的"filter"类是所有decorator的基类。(decorator必须具有与它要包装的对象的全部接口,但是decorator可以扩展这个接口,由此就衍生出了很多"filter"类)。

decorator模式常用于如下的情形:如果用继承来解决各种需求的话,类的数量会多到不切实际的地步。java的i/o类库需要提供很多功能的组合,于是decorator模式就有了用武之地。但是decorator有个缺点,在提高编程的灵活性的同时(因为你能很容易地混合和匹配属性),也使代码变得更复杂了。java的i/o类库之所以会这么怪,就是因为它"必须为一个i/o对象创建很多类",也就是为一个"核心"i/o类加上很多decorator。

为inputstream和outputstream定义decorator类接口的类,分别是filterinputstream和filteroutputstream。这两个名字都起得不怎么样。filterinputstream和filteroutputstream都继承自i/o类库的基类inputstream和outputstream,这是decorator模式的关键(惟有这样decorator类的接口才能与它要服务的对象的完全相同)。
用filterinputstream读取inputstream
filterinputstream及其派生类有两项重要任务。datainputstream可以读取各种primitive及string。(所有的方法都以"read"打头,比如readbyte( ), readfloat( ))。它,以及它的搭档dataoutputstream,能让你通过流将primitive数据从一个地方导到另一个地方。这些"地方"都列在表12-1里。

其它的类都是用来修改inputstream的内部行为的:是不是做缓冲,是不是知道它所读取的行信息(允许你读取行号或设定行号),是不是会弹出单个字符。后两个看上去更像是给编译器用的(也就是说,它们大概是为java编译器设计的),所以通常情况下,你是不大会用到它们的。

不论你用哪种i/o设备,输入的时候,最好都做缓冲。所以对i/o类库来说,比较明智的做法还是把不缓冲当特例(或者去直接调用方法),而不是像现在这样把缓冲当作特例。

表12-3. filterinputstream的种类 类 功能 构造函数的参数 用法 datainputstream 与dataoutputstream配合使用,这样你就能以一种"可携带的方式(portable fashion)"从流里读取primitives了(int,char,long等) inputstream 包含了一整套读取primitive数据的接口。 bufferedinputstream 用这个类来解决"每次要用数据的时候都要进行物理读取"的问题。你的意思是"用缓冲区。" inputstream,以及可选的缓冲区的容量 它本身并不提供接口,只是提供一个缓冲区。需要连到一个"有接口的对象(interface object)"。 linenumberinputstream 跟踪输入流的行号;有getlinenumber( )和setlinenumber(int)方法 inputstream 只是加一个行号,所以还得连一个"有接口的对象"。 pushbackinputstream 有一个"弹压单字节"的缓冲区(has a one byte push-back buffer),这样你就能把最后读到的那个字节再压回去了。 inputstream 主要用于编译器的扫描程序。可能是为支持java的编译器而设计的。用的机会不多。
用filteroutputstream往outputstream里面写东西
datainputstream的另一半是dataoutputstream。它的任务是把primitive数据和string对象重新组织成流,这样其它机器就能用datainputstream读取这个流了。dataoutputstream的方法都是以"write"开头的,比如writebyte( ),writefloat( )等等。

printstream的用意是要以一种大家都能看懂的方式把primitive数据和string对象打印出来。这一点同dataoutputstream不同,后者是要将数据装入一个流,然后再交给 datainputstream处理。

printstream的两个最重要的方法是print( )和println( )。这两个方法都已经作了重载,因此可以打印各种数据。print( )和println( )的区别在于,后者会多打印一个换行符。

使用printstream的时候会比较麻烦,因为它会捕捉所有的ioexception(所以你必须直接调用checkerror( )来检查错误条件,因为这个方法会在碰到问题的时候返回true)。再加上,printstream的国际化做得也不好,而且还不能以与平台无关的方式处理换行(这些问题都已经在printwriter里得到解决,我们接下来再讲)。

bufferedoutputstream 是个decorator,它表示对流作缓冲,这样每次往流里写东西的时候它就不会再每次都作物理操作了。输出的时候大致都要这么做。

表12-4. filteroutputstream的种类 类 功能 构造函数的参数 用法 dataoutputstream 与datainputstream配合使用,这样你就可以用一种"可携带的方式(portable fashion)"往流里写primitive了(int, char, long,等) outputstream 包括写入primitive数据的全套接口。 printstream 负责生成带格式的输出(formatted output)。dataoutputstrem负责数据的存储,而printstream负责数据的显示。 一个outputstream以及一个可选的boolean值。这个boolean值表示,要不要清空换行符后面的缓冲区。 应该是outputstream对象的最终包覆层。用的机会很多。 bufferedoutputstream 用 这个类解决"每次往流里写数据,都要进行物理操作"的问题。也就是说"用缓冲区"。用flush( )清空缓冲区。 outputstream, 以及一个可选的缓冲区大小 本身并不提供接口,只是加了一个缓冲区。需要链接一个有接口的对象。
reader 和 writer类系
java 1.1对最底层的i/o流类库作了重大修改。第一次看到reader和writer的时候,你会觉得"它们大概是用来取代inputstream和outputstream的" (和我一样)。但事实并非如此。虽然inputstream和outputstream的某些功能已经淘汰了(如果你继续使用,编译器就会发警告),但它们仍然提供了很多很有价值的,面向byte的i/o功能,而reader和writer则提供了unicode兼容的,面向字符的i/o功能。此外:
java 1.1还对inputstream和outputstream作了新的补充,所以很明显这两个类系并没有被完全替代。有时,你还必须同时使用"基于byte的类"和"基于字符的类"。为此,它还提供了两个"适配器(adapter)"类。inputstreamreader负责将inputstream转化成reader,而outputstreamwriter则将outputstream转化成writer。
reader和writer要解决的,最主要的问题就是国际化。原先的i/o类库只支持8位的字节流,因此不可能很好地处理16位的unicode字符流。unicode是国际化的字符集(更何况java内置的char就是16位的unicode字符),这样加了reader和writer之后,所有的i/o就都支持unicode了。此外新类库的性能也比旧的好。
数据源和目的
几乎所有的java i/o流都有与之对应的,专门用来处理unicode的reader和writer。但有时,面向byte的inputstream和outputstream才是正确的选择;特别是java.util.zip;它的类都是面向byte的。所以最明智的做法是,先用reader和writer,等到必须要用面向byte的类库时,你自然会知道的,因为程序编译不过去了。

下面这张表格列出了这两个类系的数据源和目的之间的关系(也就是说,在这两个类系里,数据是从哪里来的,又是到那里去的)。
数据源和目的 java 1.0的类 java 1.1的类 inputstream reader的适配器:inputstreamreader outputstream writer的适配器: outputstreamwriter fileinputstream filereader fileoutputstream filewriter stringbufferinputstream stringreader (没有对应的类) stringwriter bytearrayinputstream chararrayreader bytearrayoutputstream chararraywriter pipedinputstream pipedreader pipedoutputstream pipedwriter
总之,这两个类系即便不是一摸一样,也至少是非常相像。
修改流的行为
不管是inputstream还是outputstream,用的时候都要先交给filterinputstream和filteroutputstrem,并由后者,也就是decorator做一番改造。reader和writer继承了这一传统,不过不是完全照搬。

下面这张表的对应关系比前面那张更粗略。这是因为这两个类系的组织结构不同。比方说bufferedoutputstream是filteroutputstream的子类,但bufferedwriter却不是filterwriter的子类(后者虽然是一个abstract类,但却没有子类,所以它看上去只是起一个"占位子"的作用,这样你就不会去惦记它在哪里了)。但不管怎么说,它们的接口还是很相似的。
filter类 java 1.0的类 java 1.1的类 filterinputstream filterreader filteroutputstream filterwriter(这是个无派生类的抽象类) bufferedinputstream bufferedreader(也有readline( )) bufferedoutputstream bufferedwriter datainputstream 尽量用datainputstream (除非你用bufferedreader的时候要用readline( )) printstream printwriter linenumberinputstream(过时了) linenumberreader streamtokenizer streamtokenizer(换一个构造函数,把reader当参数传给它) pushbackinputstream pushbackreader
有一条很清楚:别再用datainputstream的readline( )(编译时会警告你这个方法已经"过时了(deprecated)"),要用就用bufferedreader的。此外,datainputstream仍然是i/o类库的"种子选手"。

为了让向printwriter的过渡变得更简单,printwriter除了有一个拿writer做参数的构造函数之外,还有一个拿outputstream做参数的构造函数。但是printwriter格式上并不比printstream的更好;它们的接口实际上是完全相同的。

printwriter的构造函数里还有一个可选的,能自动地进行清空操作的选项。如果你设了这个标记,那么每次println( )之后,它都会自动清空。
没变过的类
java从1.0升到1.1时,有几个类没有变过:
在java 1.1 中无相对应的类的 java 1.0 的类dataoutputstreamfilerandomaccessfilesequenceinputstream
特别是dataoutputstream,用法都一点没变,所以你就可以用inputstream和outputstream来读写可以传输的数据了。
自成一派: randomaccessfile
randomaccessfile是用来访问那些保存数据记录的文件的,这样你就可以用seek( )方法来访问记录,并进行读写了。这些记录的大小不必相同;但是其大小和位置必须是可知的。

首先,你可能会不太相信,randomaccessfile竟然会是不属于inputstream和outputstream类系的。实际上,除了实现datainput和dataoutput接口之外(datainputstream和dataoutputstream也实现了这两个接口),它和这两个类系毫不相干,甚至都没有用inputstream和outputstream已经准备好的功能;它是一个完全独立的类,所有方法(绝大多数都只属于它自己)都是从零开始写的。这可能是因为randomaccessfile能在文件里面前后移动,所以它的行为与其它的i/o类有些根本性的不同。总而言之,它是一个直接继承object的,独立的类。

基本上,randomaccessfile的工作方式是,把datainputstream和dataoutputstream粘起来,再加上它自己的一些方法,比如定位用的getfilepointer( ),在文件里移动用的seek( ),以及判断文件大小的length( )。此外,它的构造函数还要一个表示以只读方式("r"),还是以读写方式("rw")打开文件的参数 (和c的fopen( )一模一样)。它不支持只写文件,从这一点上看,假如randomaccessfile继承了datainputstream,它也许会干得更好。

只有randomaccessfile才有seek方法,而这个方法也只适用于文件。bufferedinputstream有一个mark( )方法,你可以用它来设定标记(把结果保存在一个内部变量里),然后再调用reset( )返回这个位置,但是它的功能太弱了,而且也不怎么实用。

randomaccessfile的绝大多数功能,如果不是全部的话,已经被jdk 1.4的nio的"内存映射文件(memory-mapped files)"给取代了。下面我们会讲到这部分内容的。
常见的i/o流的使用方法
虽然i/o流的组合方式有很多种,但最常用的也就那么几种。下
输入流
第一到第四部分演示了如何创建和使用inputstream。第四部分还简单地演示了一下outputstream的用法。
1. 对输入文件作缓冲
要想打开打开文件读取字符,你得先用string或file对象创建一个fileinputreader。为了提高速度,你应该对这个文件作缓冲,因此你得把fileinputreader的reference交给bufferedreader。由于bufferedreader也提供了readline( )方法,因此它就成为你最终要使用的那个对象,而它的接口也成为你使用的接口了。当你读到了文件的末尾时,readline( )会返回一个null,于是就退出while循环了。

最后,用close( )来关闭文件。单从技术角度上说,程序退出的时候(不管有没有垃圾要回收)都应该调用finalize( ),而finalize( )又会调用close( )。不过各种jvm的实现并不一致,所以最好还是明确地调用close( )。

system.in是一个inputstream,而bufferedreader需要一个reader作参数,所以要先通过inputstreamreader来转转手。
2. 读取内存
(stringreader的)read( )方法会把读出来的byte当作int,所以要想正常打印的话,你得先把它们转换成char。
3. 读取格式化的内存
要想读取"格式化"的数据,你就得用datainputstream了,它是一个面向byte的i/o类 (不是面向char的),因此你只能从头到底一直用inputstream了。当然你可以把所有东西(比方说文件) 都当成byte,然后用inputstream读出来,但这里是string。要想把string变成成byte数组,可以用string的getbytes( )方法,而bytearrayinputstream是可以处理byte数组的。到了这一步,你就不用担心没有合适的inputstream来创建datainputstream了。

如果你是用readbyte( )逐字节地读取datainputstream的话,那么无论byte的值是多少,都是合法的,所以你无法根据返回值来判断输入是否已经结束了。你只能用available( )来判断还有多少字符。

注意,available( )的工作方式会随读取介质的不同而不同;严格地讲,它的意思是"可以不被阻塞地读取的字节的数目。"对文件来说,它就是整个文件,但如果是其它流,情况就不一定了,所以用之前要多留一个心眼。

你也可以像这样,用异常来检查输入是不是完了。但不管怎么说,把异常当成控制流程来用总是对这种功能的滥用。
4. 读取文件
(试试把bufferedwriter去掉,你就能看到它对性能的影响了—— 缓冲能大幅提高i/o的性能)。

linenumberinputstream这是一个傻乎乎的,没什么用的类

输入流用完之后,readline( )会返回null。如果写文件的时候不调用close( ),它是不会去清空缓冲区的,这样就有可能会落下一些东西了。
输出流
根据写数据的方式不同,outputstream主要分成两类;一类是写给人看的,一类是供datainputstream用的。虽然randomaccessfile的数据格式同datainputstream和dataoutputstream的相同,但它不属于outputstream的。
5. 存储和恢复数据
printwriter会对数据进行格式化,这样人就能读懂了。但是如果数据输出之后,还要恢复出来供其它流用,那你就必须用dataoutputstream来写数据,再用datainputstream来读数据了。当然,它们可以是任何流,不过我们这里用的是一个经缓冲的文件。dataoutputstream和datainputstream是面向byte的,因此这些流必须都是inputstream和outputstream。

如果数据是用dataoutputstream写的,那么不管在哪个平台上,datainputstream都能准确地把它还原出来。这一点真是太有用了,因为没人知道谁在为平台专属的数据操心。如果你在两个平台上都用java,那这个问题就根本不存在了 。

用dataoutputstream写string的时候,要想确保将来能用datainputstream恢复出来,唯一的办法就是使用utf-8编码,也就是像例程第5部分那样,用writeutf( )和readutf( )。utf-8是unicode的一种变形。unicode用两个字节来表示一个字符。但是,如果你处理的全部,或主要是ascii字符(只有7位),那么无论从存储空间还是从带宽上看,就都显得太浪费了,所以utf-8 用一个字节表示ascii字符,用两或三个字节表示非ascii的字符。此外,字符串的长度信息存在(字符串)的头两个字节里。writeutf( )和readutf( )用的是java自己的utf-8版本,所以如果你要用一个java程序读取writeutf( )写的字符串的话,就必须进行一些特殊处理了。

有了writeutf( )和readutf( ),你就能放心地把string和其它数据混在一起交给dataoutputstream了,因为你知道string是以unicode的形式存储的,而且可以很方便地用dataoutputstream恢复出来。

writedouble( )会往流里写double,而它"影子"readdouble( )则负责把它恢复出来(其它数据也有类似的读写方法)。但是要想让读取方法能正常工作,你就必须知道流的各个位置上都放了些什么数据。因为你完全可以把double读成byte,char,或其它什么东西。所以要么以固定的格式写文件,要么在文件里提供额外的解释信息,然后一边读数据一边找数据。先提一下,对于复杂数据的存储和恢复,对象的序列化可能会比较简单。
6. 读写随机文件
正如我们前面所讲的,如果不算它实现了datainput和dataoutput接口,randomaccessfile几乎是完全独立于其它i/o类库之外的,所以它不能与inputstream和outputstream合起来用。虽然把bytearrayinputstream当作"随机存取的元素(random-access element)"是一件很合情合理的事,但你只能用randomaccessfile来打开文件。而且,你只能假定randomaccessfile已经做过缓冲了,因为即便没做你也无能为力。

构造函数的第二个参数的意思是:是以只读("r") 还是读写("rw")方式打开randomaccessfile。

randomaccessfile的用法就像是datainputstream和dataoutputstream的结合(因为它们的接口是等效的)。此外,你还能用seek( )在文件里上下移动,并进行修改。

随着jdk 1.4的new i/o的问世,你该考虑一下是不是用"内存映射文件(memory-mapped file)"来代替randomaccessfile了。
管道流
这一章只会大致地提一下pipedinputstream,pipedoutputstream,pipedreader和pipedwriter。这并不是说它们不重要,只是因为管道流是用于线程间的通信的,所以除非你已经理解了多线程,否则是不会理解它的价值的。我们会在第13章用一个例子来讲解这个问题。
读写文件的实用程序
把文件读进内存,改完,再写文件。这是再普通不过的编程任务了。但是java的i/o就是有这种问题,即便是做这种常规操作,你也必须写一大串代码——根本就没有辅助函数。更糟的是,那些喧宾夺主的decorator会让你忘了该怎样打开文件。因此比较明智的做法还是自己写一个辅助类。下面就是这样一个类,它包含了一些"能让你将文本文件当作字符串来读写"的static方法。此外,你还可以创建一个"会把文件的内容逐行存入arraylist的"textfile类,(这样在处理文件的时候,就能使用arraylist的功能了):

//: com:bruceeckel:util:textfile.java// static functions for reading and writing text files as// a single string, and treating a file as an arraylist.// {clean: test.txt test2.txt}package com.bruceeckel.util;import java.io.*;import java.util.*;public class textfile extends arraylist { // tools to read and write files as single strings: public static string read(string filename) throws ioexception { stringbuffer sb = new stringbuffer(); bufferedreader in = new bufferedreader(new filereader(filename)); string s; while((s = in.readline()) != null) { sb.append(s); sb.append("\n"); } in.close(); return sb.tostring(); } public static void write(string filename, string text) throws ioexception { printwriter out = new printwriter( new bufferedwriter(new filewriter(filename))); out.print(text); out.close(); } public textfile(string filename) throws ioexception { super(arrays.aslist(read(filename).split("\n"))); } public void write(string filename) throws ioexception { printwriter out = new printwriter( new bufferedwriter(new filewriter(filename))); for(int i = 0; i < size(); i++) out.println(get(i)); out.close(); } // simple test: public static void main(string[] args) throws exception { string file = read("textfile.java"); write("test.txt", file); textfile text = new textfile("test.txt"); text.write("test2.txt"); }} ///:~

所有这些方法都会直接往外面抛ioexception。由于一行读出来之后,后面的换行符就没了,因此read( )会在每行的后面再加一个换行符,然后接到stringbuffer的后面(出于效率考虑)。最后它会返回一个"含有整个文件的内容"的string。write( )的任务是打开文件,然后往里面写东西。任务完成之后要记着把文件给close( )了。

(textfile)的构造函数用read( )方法将文件转化成string,然后用string.split( )和换行符转换成数组(如果你要经常使用这个类,或许应该重写一遍构造函数以提高性能)。此外,由于没有相应的"join"方法,非static的write( )方法只能手动地逐行打印文件。

为了确保它能正常工作,main( )作了一个基本测试。虽然它只是个小程序,但到后面你就会发觉,它却能帮你节省很多时间,同时让生活变得轻松一点。
标准i/o
"标准i/o"是unix的概念,它的意思是,一个程序只使用一个信息流(这种设计思想也以某种形式体现在windows及其它很多操作系统上)。所有输入都是从"标准输入"进来的,输出都从"标准输出"出去,错误消息都送到"标准错误"里。标准i/o的优点是,它可以很容易地把程序串连起来,并且把一个程序的输出当作另一个程序的输入。这是一种非常强大的功能。
读取标准输入
java遵循标准i/o的模型,提供了syetem.in,system.out,以及system.err。本书一直都在用system.out往标准输出上写,而它(system.out)是一个已经预先处理过的,被包装成printstream的对象。和system.out一样,system.err也是一个printstream,但是system.in就不对了,它是一个未经处理的inputstream。也就是说,虽然你可以直接往system.out和system.err上写,但是要想读system.in的话,就必须先做处理了。

通常情况下,你会用readline( )一行一行地读取输入,因此要把system.in包装成bufferedreader。但在这之前还得先用inputsteamreader把system.in转换成reader。
将system.out转换成printwriter
system.out是printstream,也就是说它是outputstream。不过printwriter有一个能将outputstream改造成printwriter的构造函数。有了这个构造函数,你就可以随时将system.out转化成printwriter了:

为了启动自动清空缓冲区的功能,一定要使用双参数版的构造函数,并且把第二个参数设成true。这点非常重要,否则就有可能会看不到输出了。
标准i/o的重定向
java的system类还提供了几个能让你重定向标准输入,标准输出和标准错误的静态方法:

setin(inputstream)setout(printstream)seterr(printstream)

如果程序在短时间内输出了大量的信息,使得翻屏的速度非常快,以致于你都没法读了,这时对输出进行重定向就会显得非常有用了 。对于那些要重复测试用户输入的命令行程序来说,对输入进行重定向也是非常重要的。

i/o重定向处理的不是character流,而是byte流,因此不能用reader和writer,要用inputstream和outputstream。

赞(0)
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com 特别注意:本站所有转载文章言论不代表本站观点! 本站所提供的图片等素材,版权归原作者所有,如需使用,请与原作者联系。未经允许不得转载:IDC资讯中心 » TIJ阅读笔记(第十二章)-JSP教程,Java技巧及代码
分享到: 更多 (0)

相关推荐

  • 暂无文章