第十二章 流与文件

12.1 流
在Java程序设计语言中,一个可以读取字节序列的对象被称为输入流(input stream)。一个可以写入字节序列的对象被称为输出流(output stream)。在抽象类InputStream和OutputStream中对它们进行了说明。由于以字节为单位的流处理存储为Unicode码的信息很不方便,所以有一个专门的类层次来处理Unicode字符,这些类继承与抽象类Reader和Writer。它们的读写操作都是基于双字节的Unicode代码单元,而不是基于单字节。
不论是read方法还是write方法都能阻塞(block)一个线程直到自己被真正地读取或者写入。
这意味着如果流不能立即被读取或者写入(通常是因为网络连接繁忙),Java就会挂起这个调用的线程。这样就能够给其他线程提供机会去做有用的工作,同时这个方法将一直等待,直到流再次可用为止。
available方法能够检查目前可以读取的字节数。
12.2 完整的流结构
12.2.1 流过滤器的分层
FileInputStream和FileOutputStream能够把输入和输出流与磁盘文件关联起来。要想达到这一目的,只需要在构造器中给出文件名或者文件的具体路径名即可。
提示:因为所有在java.io中的类都是将相对路径名解释为起始于用户的当前工作目录,所以应该清楚当前的目录。可以通过调用System.getProperty("user.dir")来获得。
Java使用了一种很聪明的策略来划分这两种职责。一些流(例如FileInputStrean以及URL类中利用openStream方法来返回的输入流)可以从文件以及其他地方接收字节。另一些流(例如DataInputStram和PrintWiter)可以将字节组合成更加有用的数据类型。Java程序员采用将一个已经存在的流传递给另一个流的构造器方法,将这两种流结合起来,结合后的流被称为过滤流(filetered stream)。
总而言之,处理需要使用怪异的构造器对流进行分层之外,能够将流进行混合与匹配时Java非常实用的特性之一!
12.2.2 数据流
数据流支持读取所有Java基本类型的方法。
注意:依赖于所使用的平台,在内存中有两种方法存储整数和浮点数。假设正在使用4字节的整数,他的十进制数值为1234或者用十六进制表示为4D2,那么能够使用这种方式来存储。一种是内存中的四个字节的第一个字节用来存储最高有效字节(MSB),值为00 00 04 D2。这也被称为高字节前存法;另一种是从最低字节(LSB) 开始,值为D2 04 00 00。这种更加自然的方法称为低字节前存法。例如,SPARC使用的是高字节前存方法,奔腾使用的是低字节前存方法。这样一来就会出问题。当一个C或C++文件存储时,数据保持方式和处理器存储它们的方式一样,这样一来,即使把一个最简单的数据文件从一个平台移动到另一个平台都会遇到挑战。在Java中,不管在什么处理器上,所有的值都是采用高字节前存的方法存储的。这就使得Java的数据文件具有平台独立性。
注意:二进制数据格式是紧凑的并与平台无关。除了UTF字符串以外,其他的都可以随机存取,其主要的缺点是这些二进制文件不适合人们阅读。
12.2.3 随机存取文件流
RandomAccessFile流泪能够在文件的任何位置查找或者写入数据。它同时实现了DataInput和DataOutput接口。磁盘文件可以随机存取,但是来自网络的数据流就不行了。打开一个随机存取文件,要么进行制度操作,要么进行读写操作。可以通过构造器的第二个参数带入“r”(读取)或“rw”(读写)指定相应的操作。
当打开一个向右文件作为RandomAccessFile时,原来的文件不会被删除。
随进存取文件同时提供了一个文件指针(file pointer)。文件指针总是指向下一条要进行读取操作记录的位置。Seek方法将文件指针设定在文件内部的任意字节位置。Seek使用的参数是一个在0到文件长度(以字节计)之间的long整数。
12.2.4 文本流
Java提供了一套六过滤器作为Unicode字符码和本地操作系统使用的字符码间的桥梁。所有这些类都从抽象类Reader和Writer类派生出来,而名字会令人联想到用于二进制数据的名字。
12.2.5 字符集
字符集给出了双字节Unicode码序列与在本地字符编码中采用的字节序列间的映射。
字符集的名称不分大小写。
可以用官方名称或者任何一个别名调用静态方法forName来获得Charset:
Charset cset=Charset.forName("ISO-8859-1");
12.2.6 文本输出
进行文本输出时,应该使用PrintWriter。print writer可以以文本格式打印字符串和数值。与DataOutputStream类似,尽管提供了一些很有用的输出方法,但却没有定义目的地,一个PrintWriter必须与一个目标writer相结合。
12.2.7 文本输入
当以二进制格式写入数据时,应当使用DataOutputStream。
当以文本格式写入数据时,应当使用Printriter。
提示:Java的StringReader和StringWriter类可以像对待一个数据流一样对待字符串。这对于想要使用相同的代码从流中分析字符串和数据来说十分有用。
12.3 ZIP文件流
注意:处理ZIP文件的类在java.util.zip中而不在java.io中,所以请记住要添加必要的import语句。尽管不是java.io的一部分,但GZIP和ZIP类还是java.io.FilterInputStream和java.io.FilterOutPutStream的子类。java.util.zip包同样也包含了拥有计算循环冗余码校验(Cyclic Readundancy Check,CRC)的类。(CRC是用来生成类似散列码的方法,文件接收者可以使用这些来检验数据的完整性。)
12.4 流的使用
12.4.1 分隔符输出
12.4.2 字符串记号处理器和带分隔符的文本
java.util.StringTokenizer
12.4.3 读取带分隔符的输入
12.4.4 StringBuilder类
StringBuilder的工作过程更像一个ArrayList。它管理着一个可以根据需求增长或缩短的字符数组char[]。可以追加、插入或者删除代码单元,直到StringBuilder中含有的所希望的字符为止,然后使用toString方法将内同转换为一个真正的String对象。
注意:StringBuilder类是JDK5.0新引入的。它的前身StringBuffrt的效率稍稍低一点,但是却可以允许多线程进行增加或移除字符操作。如果所有的字符串编辑出现在一个线程中,应该使用StringBuilder。这两个类的API都是相同的。
12.4.5 随机存取流
12.5 对象流
12.5.1 存储可变类型的对象
12.5.2 理解对象序列化文件格式
对象序列化使用的是一种特殊的文件格式来存储对象。
Java从以下途径获得指纹:
首先用标准的方式对类、超类、接口、域类型和方法签名的描述进行排序。
然后对那些数据应用一种所谓的“安全散列算法”(Secure Hash Algorithm,SHA)
SHA是一种可以从大块的信息块中获取“指纹”的快速算法。不管原始数据多大,这种方法产生的指纹必然是一个长度为20字节的数据包。
注意:从技术上说,只要类的数据布局没有改变,都回一个对象就是安全的。但是,Java相对的保守,所以还会检查方法是不是被改变。(毕竟,方法描述了所保存的数据的意义。)当然,实际上,类是不断衍化的,所以成像读取对象的老版本也是必要的。
12.5 保存对象引用问题的解决
Java使用序列化(serialization)方法。这就是所谓的对象序列化(object serialization)机制。下面是具体的算法:
所有保存到磁盘的对象都有一个序列号。
当向磁盘存储一个对象时,先检查相同的对象是否已经保存。
如果已经存储过,只要写入“与已经存储的对象具有序列号X”。如果没有,保存所有的数据。
12.5.4 理解对象引用的输出格式
所有的对象(包括数组和字符串)以及类描述符在保持到输出文件时,都会获得一个序列号。因为每个保存的对象都会分配到一个唯一的序列号(从00 7E 00 00开始),这个处理流程被称为序列化(serralization).
通常,不必知道准确的文件格式(除非想尝试通过改变数据创造一个遗憾的效果。)应该记住下面这些:
对象流的输出包含所有对象的类型和数据域。
每个对象都会分配一个序列号。
重复出现的同一个对象会存储为其序列号的引用。
12.5.5 修改默认的序列化机制
Java有一个简单的机制用来避免这些域被序列化,即使用关键字transient标注他们。另外,还需要将那些属于非序列化中的域标注为transient。当对象被序列化时,标注为transient的域是会被跳过的。
提示:序列化有些慢,因为虚拟机必须弄清每个对象的结构,如果关心性能,并且需要读写大量的特定类对象,就应该考虑使用Externalizable接口。
警告:不同于readObject和writeObject方法,他们是私有的并且只能被序列化机制调用,readExternal和writeExternal方法是公有的。特别是,readExternal方法潜在地运行对一个已经存在的对象状态进行修改。
12.5.6 单元素与类型安全枚举的序列化
12.5.7 版本
类可以指出自己兼容于其早期版本。为了实现这一点,首先必须获得该类的早期版本的指纹,可以使用一个独立的程序serialver,它是JDK的一部分来获得这个数值。
12.5.8 使用序列化进行克隆
序列化机制提供了一种很简单的方法来克隆一个对象,只要被提供的类是序列化的。
要克隆一个序列化的对象,只需要就爱你过对象序列化到输出流中,然后读回即可。其结果是一个新的对已存在对象进行了深度拷贝的对象。不需要将对象写入文件——可以使用一个ByteArrayOutputStream来存储数据到一个字节数组中。
12.6 文件管理
注意:就像Java中经常使用的方法那样,File类也仅仅有最少的公共操作方法。比如在Windows中,可以查询(或设置)文件的只读标志,但尽管可以查询一个文件是否隐藏,却不能将它设置为隐藏,除非使用本地方法。
提示:如果处理文件或者目录名,就应该使用File对象,而不是字符串。
警告:如果在Windows中构造File对象时,使用正斜杠作为目录分隔符,那么getAbsolutePath方法将但会一个包括正斜杠的文件名,Windows用户会感觉有点奇怪。实际上,应该使用geCanonicalPath方法。它将使用反斜杠代替正斜杠。
12.7 新的I/O
JDK4.0包含了一些改进输入、输出处理的新特性,它们包含在java.nio包中,被称为“新的I/O”。
此包支持下列特性:
内存映射文件
文件锁定
字符集编码和解码
非阻塞I/O
12.7.1
使用java.nio包建立内存映射很简单。下面是需要的步骤:
首先,为文件获取通道(channel)。通道时对于磁盘文件的抽象,允许访问操作系统的特性,如文件间的内存映射、文件锁定和快速数据传输。通过调用增加到FileInputStream、FileOutputStream和RandomAccessFile类中的getChannel方法就可以获得通道。
FileInputStream in=new FileInputStream(...);
FileChannel channel=in.getChannel();
通过调用FileChannel类的map方法可以获得MappedByteBuffer。可以指定需要进行映射的文件区域和映射模式。有三种模式:
FileChannel.MapMode.READ_ONLY:结果缓存区是只读的。任何试图写入缓存区的举动都将抛出ReadOnlyBufferException。
FileChannel.MapMode.READ_WRITE:结果缓存区可写,这些改变会及时写会文件中。注意,如果其他程序也映射了同一个文件,它并不会立即观察到这些变化。多个程序同时对同一文件进行映射的具体行为依赖于操作系统。
FileChannel.MapMode.PRIVATE:结果缓存区可写,但是任何改变都是私有的,仅仅对该缓存区有效,并不会写入文件中。
一旦获取了缓冲区,可以用ByteBuffer类和Buffer超类读写数据。
缓冲区支持顺序和随机数据访问。缓冲区有一个被get和put操作使用的位置。
12.7.2 缓存区数据结构
当使用内存映射时,制作了一个横跨整个文件的缓冲区,或者是文件中的一个感兴趣的区域。也可以使用缓冲区来读写更多的普通信息块。
一个缓冲区就是一个相同类型的值的数组。Buffer类是一个抽象类,它包含以下子类:ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer和ShortBuffer。在实际应用中,使用最多的是ByteBuffer和CharBuffer。
一个缓存区具有:
一个绝不会改变的容量(capacity)。
一个下一数值读取或写入的位置(position)。
一个限制(limit),超出这个限制的读写时无意义的。
可选地,一个重复进行读写操作的标志(mark)。
12.7.3 文件锁定
要锁住一个文件,调用FileChannel类的lock或tryLock方法:
File lock=channel.lock();
或者
File lock=channel.tryLock();
第一个调用一直处于阻塞状态直到该锁可用。第二个调用立刻返回,返回该锁或者是null(如果该锁不可用)。第一个文件保持封锁状态知道通道关闭,或者对锁调用release方法。
注意:如果锁住文件的尾部,而文件后来增加的内容超过了锁住的位置,则多出的部分并没有锁住。要锁住全部字节,使用大小为Long.MAX_VALUE。
记住文件锁是与系统相关的。要注意以下几点:
在一些系统中,文件锁仅仅是建议性的(advisory)。因此,如果一个应用程序未能获得锁,它也可以对文件进行读写操作,尽管其他应用程序已经锁住文件。
在一些系统中,不能同步地锁住一个文件并把它映射到内存中。
文件锁是整个Java虚拟机持有的。如果两个程序使用同一个虚拟机执行(例如,一个applet或者应用程序启动器),则它们不能对同一个文件获得锁。如果虚拟机已经拥有了对同一个文件的重叠锁,则lock或者truLock方法会抛出OverlappingFileLockException异常。
在某些系统上,关闭通道会释放Java虚拟机拥有的文件上全部的锁。因此应该避免对同一个被锁定的文件打开多个通道。
对网络文件系统中的文件锁定是和系统高度相关的,因此应该避免这么做。
12.8 正则表达式
正则表达式是用来指定字符串模式的。任何时候只要需要定位一个匹配特定模式的字符串,都可以使用正则表达式。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值