NIO学习系列:文件锁定和字符集
在前面五名已经学习了四篇关于NIO系列的文章:
核心概念及基本读写 、缓冲区内部实现机制 、连网和异步IO 、缓冲区更多特性及分散/聚集IO ,
这里我们继续探讨和学习有关文件锁定和字符集相关的内容。
9. 文件锁定
1) 概述:
文件锁定初看起来可能让人迷惑。它似乎指的是防止程序或者用户访问特定文件。事实上,文件锁就像常规的Java对象锁,它们是“劝告式”的(advisory)锁。它们不阻止任何形式的数据访问,相反,它们通过锁的共享和获取赖允许系统的不同部分相互协调。
您可以锁定整个文件或者文件的一部分。如果您获取一个排它锁,那么其他人就不能获得同一个文件或者文件的一部分上的锁。如果您获得一个共享锁,那么其他人可以获得同一个文件或者文件一部分上的共享锁,但是不能获得排它锁。文件锁定并不总是出于保护数据的目的。例如,您可能临时锁定一个文件以保证特定 的写操作成为原子的,而不会有其他程序的干扰。
大多数操作系统提供了文件系统锁,但是它们并不都是采用同样的方式。有些实现提供了共享锁,而另一些仅提供了排它锁。事实上,有些实现使得文件的锁 定部分不可访问,尽管大多数实现不是这样的。
在这里,我们将学习如何在 NIO 中执行简单的文件锁过程,还将探讨一些保证被锁定的文件尽可能可移植的方法。
2) 锁定文件:
要获取文件的一部分上的锁,您要调用一个打开的FileChannel上的lock()方法。注意,如果要获取一个排它锁,您必须以写方式打开文件。
RandomAccessFile raf = new RandomAccessFile( "usefilelocks.txt", "rw" );
FileChannel fc = raf.getChannel();
FileLock lock = fc.lock( start, end, false );
在拥有锁之后,您可以执行需要的任何敏感操作,然后再释放锁:
lock.release();
在释放锁后,尝试获得锁的其他任何程序都有机会获得它。
本附件的例子程序UseFileLocks.java必须与它自己并行运行。这个程序获取一个文件上的锁,持有三秒钟,然后释放它。如果同时运行这个程序的多个实例,您会看到每个实例依次获得锁。
3) 文件锁定和可移植性:
文件锁定可能是一个复杂的操作,特别是考虑到 不同的操作系统是以不同的方式实现锁这一事实。下面的指导原则将帮助您尽可能保持代码的可移植性:
i 只使用排它锁。
ii将所有的锁视为劝告式的(advisory)。
10. 字符集
1) 概述:
根据Sun的文档,一个Charset是“十六位Unicode字符序列与字节序列之间的一个命名的映射”。实际上,一个Charset允许您以尽可能最具可移植性的方式读写字符序列。
Java语言被定义为基于Unicode。然而在实际上,许多人编写代码时都假设一个字符在磁盘上或者在网络流中用一个字节表示。这种假设在许多情况下成立,但是并不是在所有情况下都成立,而且随着计算机变得对Unicode越来越友好,这个假设就日益变得不能成立了。
在这里,我们将看一下如何使用Charsets以适合现代文本格式的方式处理文本数据。这里将使用的示例程序相当简单,不过,它触及了使用Charset的所有关键方面:为给定的字符编码创建Charset,以及使用该Charset解码和编码文本数据。
2) 编码/解码:
要读和写文本,我们要分别使用CharsetDecoder和CharsetEncoder。将它们称为“编码器”和“解码器” 是有道理的。一个字符不再表示一个特定的位模式,而是表示字符系统中的一个实体。因此,由某个实际的位模式表示的字符必须以某种特定的编码来表示。
CharsetDecoder用于将逐位表示的一串字符转换为具体的char值。同样,一个CharsetEncoder用于将字符转换回位。
3) 处理文本的正确方式:
现在我们将分析这个例子程序UseCharsets.java。这个程序非常简单:它从一个文件中读取一些文本,并将该文本写入另一个文件。但是它把该数据当作文本数据,并使用CharBuffer来将该数句读入一个CharsetDecoder中。同样,它使用CharsetEncoder来写回该数据。
我们将假设字符以ISO-8859-1(Latin1)字符集(这是ASCII的标准扩展)的形式储存在磁盘上。尽管我们必须为使用Unicode做好准备,但是也必须认识到不同的文件是以不同的格式储存的,而ASCII无疑是非常普遍的一种格式。事实上,每种Java实现都要求对以下字符编码提供完全的支持:
US-ASCII
ISO-8859-1
UTF-8
UTF-16BE
UTF-16LE
UTF-16
4) 示例程序:
在打开相应的文件、将输入数据读入名为inputData的ByteBuffer之后,我们的程序必须创建ISO-8859-1字符集的一个实例:
Charset latin1 = Charset.forName( "ISO-8859-1" );
然后,创建一个解码器(用于读取)和一个编码器 (用于写入):
CharsetDecoder decoder = latin1.newDecoder();
CharsetEncoder encoder = latin1.newEncoder();
为了将字节数据解码为一组字符,我们把ByteBuffer传递给CharsetDecoder, 结果得到一个CharBuffer:
CharBuffer cb = decoder.decode( inputData );
如果想要处理字符,我们可以在程序的此处进行。但是我们只想无改变地将它写回,所以没有什么要做的。
要写回数据,我们必须使用CharsetEncoder将它转换回字节:
ByteBuffer outputData = encoder.encode( cb );
在转换完成之后,我们就可以将数据写到文件中了。
11. 结束语和参考资料
正如您所看到的,NIO库有大量的特性。在一些新特性(例如文件锁定和字符集)提供新功能的同时,许多特性在优化方面也非常优秀。
在基础层次上,通道和缓冲区可以做的事情几乎都可以用原来的面向流的类来完成。但是通道和缓冲区允许以快得多的方式完成这些相同的旧操作,事实上接近系统所允许的最大速度。
不过NIO最强大的长度之一在于,它提供了一种在Java语言中执行进行输入/输出的新的(也是迫切需要的)结构化方式。随诸如缓冲区、通道和异步I/O这些概念性(且可实现的)实体而来的,是我们重新思考Java程序中的I/O过程的机会。这样,NIO甚至为我们最熟悉的I/O过程也带来了新的活力,同时赋予我们通过和以前不同并且更好的方式执行它们的机会。
本系列教程的整理参考了IBM developerworks 中国社区关于NIO的一些学习资料。
推荐阅读