天天看点

Java I/O 扩展 Java I/O 扩展

标签: Java基础

Java 的<code>NIO</code>(新IO)和传统的IO有着相同的目的: <code>输入</code> <code>输出</code> .但是NIO使用了不同的方式来处理IO,NIO利用<code>内存映射文件</code>(此处文件的含义可以参考Unix的名言<code>一切皆文件</code>)来处理IO, NIO将文件或文件的一段区域映射到内存中(类似于操作系统的虚拟内存),这样就可以像访问内存一样来访问文件了.

<code>Channel</code> 和 <code>Buffer</code>是NIO中的两个核心概念:

<code>Channel</code>是对传统的IO系统的模拟,在NIO系统中所有的数据都需要通过<code>Channel</code>传输;<code>Channel</code>与传统的<code>InputStream</code> <code>OutputStream</code> 最大的区别在于它提供了一个<code>map()</code>方法,可以直接将一块数据映射到内存中.如果说传统的IO系统是面向流的处理, 则NIO则是面向<code>块</code>的处理;

<code>Buffer</code>可以被理解成一个容器, 他的本质是一个数组; Buffer作为Channel与程序的中间层, 存入到<code>Channel</code>中的所有对象都必须首先放到<code>Buffer</code>中(<code>Buffer</code> -&gt; <code>Channel</code>), 而从<code>Channel</code>中读取的数据也必须先放到<code>Buffer</code>中(<code>Channel</code> -&gt; <code>Buffer</code>).

从原理来看, <code>java.nio.ByteBuffer</code>就像一个数组,他可以保存多个类型相同的数据.<code>Buffer</code>只是一个抽象类,对应每种基本数据类型(boolean除外)都有相应的Buffer类: <code>CharBuffer</code> <code>ShortBuffer</code> <code>ByteBuffer</code>等.

这些Buffer除了<code>ByteBuffer</code>之外, 都采用相同或相似的方法来管理数据, 只是各自管理的数据类型不同而已.这些Buffer类都没有提供构造器, 可以通过如下方法来得到一个Buffer对象.

其中<code>ByteBuffer</code>还有一个子类<code>MappedByteBuffer</code>,它表示<code>Channel</code>将磁盘文件全部映射到内存中后得到的结果, 通常<code>MappedByteBuffer</code>由<code>Channel</code>的<code>map()</code>方法返回.

Buffer中的几个概念:

capacity: 该Buffer的最大数据容量;

limit: 第一个不应该被读出/写入的缓冲区索引;

position: 指明下一个可以被读出/写入的缓冲区索引;

mark: Buffer允许直接将position定位到该mark处.

0 &lt;= mark &lt;= position &lt;= limit &lt;= capacity

Buffer中常用的方法:

方法

解释

<code>int capacity()</code>

Returns this buffer’s capacity.

<code>int remaining()</code>

Returns the number of elements between the current position and the limit.

<code>int limit()</code>

Returns this buffer’s limit.

<code>int position()</code>

Returns this buffer’s position.

<code>Buffer position(int newPosition)</code>

Sets this buffer’s position.

<code>Buffer reset()</code>

Resets this buffer’s position to the previously-marked position.

<code>Buffer clear()</code>

Clears this buffer.(并不是真的清空, 而是为下一次插入数据做好准备

<code>Buffer flip()</code>

Flips this buffer.(将数据<code>封存</code>,为读取数据做好准备)

除了这些在<code>Buffer</code>基类中存在的方法之外, Buffer的所有子类还提供了两个重要的方法:

<code>put()</code> : 向Buffer中放入数据

<code>get()</code> : 从Buffer中取数据

当使用put/get方法放入/取出数据时, Buffer既支持单个数据的访问, 也支持(以数组为参数)批量数据的访问.而且当使用put/get方法访问Buffer的数据时, 也可分为相对和绝对两种:

<code>相对</code> : 从Buffer的当前position处开始读取/写入数据, position按处理元素个数后移.

<code>绝对</code> : 直接根据索引读取/写入数据, position不变.

通过<code>allocate()</code>方法创建的Buffer对象是普通Buffer, <code>ByteBuffer</code>还提供了一个<code>allocateDirect()</code>方法来创建<code>DirectByteBuffer</code>. <code>DirectByteBuffer</code>的创建成本比普通Buffer要高, 但<code>DirectByteBuffer</code>的读取效率也会更高.所以<code>DirectByteBuffer</code>适用于生存期比较长的Buffer. 只有<code>ByteBuffer</code>才提供了<code>allocateDirect(int capacity)</code>方法, 所以只能在<code>ByteBuffer</code>级别上创建<code>DirectByteBuffer</code>, 如果希望使用其他类型, 则可以将Buffer转换成其他类型的Buffer.

像上面这样使用<code>Buffer</code>感觉是完全没有诱惑力的(就一个数组嘛,还整得这么麻烦⊙﹏⊙b).其实<code>Buffer</code>真正的强大之处在于与<code>Channel</code>的结合,从<code>Channel</code>中直接映射一块内存进来,而没有必要一一的get/put.

<code>java.nio.channels.Channel</code>类似于传统的流对象, 但与传统的流对象有以下两个区别:

<code>Channel</code>可以直接将指定文件的部分或者全部映射成<code>Buffer</code>

程序不能直接访问<code>Channel</code>中的数据, 必须要经过<code>Buffer</code>作为中间层.

Java为Channel接口提供了<code>FileChannel</code> <code>DatagramChannel</code> <code>Pipe.SinkChannel</code> <code>Pipe.SourceChannel</code> <code>SelectableChannel</code>

<code>SocketChannel</code> <code>ServerSocketChannel</code>. 所有的<code>Channel</code>都不应该通过构造器来直接创建, 而是通过传统的<code>InputStream</code> <code>OutputStream</code>的<code>getChannel()</code>方法来返回对应的<code>Channel</code>, 当然不同的节点流获得的<code>Channel</code>不一样. 例如, <code>FileInputStream</code> <code>FileOutputStream</code> 返回的是<code>FileChannel</code>, <code>PipedInputStream</code> <code>PipedOutputStream</code> 返回的是<code>Pipe.SourceChannel</code> <code>Pipe.SinkChannel</code>;

<code>Channel</code>中最常用的三个方法是<code>MappedByteBuffer map(FileChannel.MapMode mode, long position, long size)</code> <code>read()</code> <code>write()</code>, 其中<code>map()</code>用于将Channel对应的部分或全部数据映射成<code>ByteBuffer</code>, 而read/write有一系列的重载形式, 用于从Buffer中读写数据.

Java从1.4开始提供了<code>java.nio.charset.Charset</code>来处理字节序列和字符序列(字符串)之间的转换, 该类包含了用于创建解码器和编码器的方法, 需要注意的是, <code>Charset</code>类是不可变类.

<code>Charset</code>提供了<code>availableCharsets()</code>静态方法来获取当前JDK所支持的所有字符集.

执行上面代码可以看到每个字符集都有一些字符串别名(比如<code>UTF-8</code>还有<code>unicode-1-1-utf-8</code> <code>UTF8</code>的别名), 一旦知道了字符串的别名之后, 程序就可以调用Charset的<code>forName()</code>方法来创建对应的Charset对象:

在Java 1.7 之后, JDK又提供了一个工具类<code>StandardCharsets</code>, 里面提供了一些静态属性来表示标准的常用字符集:

获得了<code>Charset</code>对象之后,就可以使用<code>decode()</code>/<code>encode()</code>方法来对<code>ByteBuffer</code> <code>CharBuffer</code>进行编码/解码了

功能

<code>ByteBuffer encode(CharBuffer cb)</code>

Convenience method that encodes Unicode characters into bytes in this charset.

<code>ByteBuffer encode(String str)</code>

Convenience method that encodes a string into bytes in this charset.

<code>CharBuffer decode(ByteBuffer bb)</code>

Convenience method that decodes bytes in this charset into Unicode characters.

或者也可以通过<code>Charset</code>对象的<code>newDecoder()</code> <code>newEncoder()</code> 来获取<code>CharsetDecoder</code>解码器和<code>CharsetEncoder</code>编码器来完成更加灵活的编码/解码操作(他们肯定也提供了<code>encode</code>和<code>decode</code>方法).

String类里面也提供了一个<code>getBytes(String charset)</code>方法来使用指定的字符集将字符串转换成字节序列.

在以前的Java版本中,如果程序需要监控文件系统的变化,则可以考虑启动一条后台线程,这条后台线程每隔一段时间去遍历一次指定目录的文件,如果发现此次遍历的结果与上次不同,则认为文件发生了变化. 但在后来的NIO.2中,<code>Path</code>类提供了<code>register</code>方法来监听文件系统的变化.

其实是<code>Path</code>实现了<code>Watchable</code>接口, <code>register</code>是<code>Watchable</code>提供的方法.

<code>WatchService</code>代表一个文件系统监听服务, 它负责监听<code>Path</code>目录下的文件变化.而<code>WatchService</code>是一个接口, 需要由<code>FileSystem</code>的实例来创建, 我们往往这样获取一个<code>WatchService</code>

一旦<code>register</code>方法完成注册之后, 接下来就可调用<code>WatchService</code>的如下方法来获取被监听的目录的文件变化事件:

释义

<code>WatchKey poll()</code>

Retrieves and removes the next watch key, or null if none are present.

<code>WatchKey poll(long timeout, TimeUnit unit)</code>

Retrieves and removes the next watch key, waiting if necessary up to the specified wait time if none are yet present.

<code>WatchKey take()</code>

Retrieves and removes next watch key, waiting if none are yet present.

获取到<code>WatchKey</code>之后, 就可调用其方法来查看到底发生了什么事件, 得到<code>WatchEvent</code>

<code>List&lt;WatchEvent&lt;?&gt;&gt; pollEvents()</code>

Retrieves and removes all pending events for this watch key, returning a List of the events that were retrieved.

<code>boolean reset()</code>

Resets this watch key.

<code>WatchEvent</code>

<code>T context()</code>

Returns the context for the event.

<code>int count()</code>

Returns the event count.

<code>WatchEvent.Kind&lt;T&gt; kind()</code>

Returns the event kind.

通过使用<code>WatchService</code>, 可以非常优雅的监控指定目录下的文件变化, 至于文件发生变化后的处理, 就取决于业务需求了, 比如我们可以做一个日志分析器, 定时去扫描日志目录, 查看日志大小是否改变, 当发生改变时候, 就扫描发生改变的部分, 如果发现日志中有异常产生(比如有Exception/Timeout类似的关键字存在), 就把这段异常信息截取下来, 发邮件/短信给管理员.

平时开发中常用的IO框架有Apache的<code>commons-io</code>和Google <code>Guava</code>的IO模块; 不过Apache的<code>commons-io</code>包比较老,更新比较缓慢(最新的包还是2012年的); 而Guava则更新相对频繁, 最近刚刚发布了19.0版本, 因此在这儿仅介绍Guava对Java IO的扩展.

使用Guava需要在<code>pom.xml</code>中添加如下依赖:

代码清晰多了.

还可以使用<code>Resources</code>类的<code>readLines(URL url, Charset charset, LineProcessor&lt;T&gt; callback)</code>方法来实现只抓取特定的网页内容的功能:

而性能的话, 我记得有这么一句话来评论STL的

STL性能可能不是最高的, 但绝对不是最差的!

我认为这句话同样适用于Guava; 在Guava IO中, 有三类操作是比较常用的:

对Java传统的IO操作的简化;

Guava对<code>源</code>与<code>汇</code>的支持;

Guava <code>Files</code> <code>Resources</code>对文件/资源的支持;

在Guava中,用<code>InputStream/OutputStream</code> <code>Readable/Appendable</code>来对应Java中的字节流和字符流(<code>Writer</code>实现了<code>Appendable</code>接口,<code>Reader</code>实现了<code>Readable</code>接口).并用<code>com.google.common.io.ByteStreams</code>和<code>com.google.common.io.CharStreams</code>来提供对传统IO的支持.

这两个类中, 实现了很多static方法来简化Java IO操作,如:

<code>static long copy(Readable/InputStream from, Appendable/OutputStream to)</code>

<code>static byte[] toByteArray(InputStream in)</code>

<code>static int read(InputStream in, byte[] b, int off, int len)</code>

<code>static ByteArrayDataInput newDataInput(byte[] bytes, int start)</code>

<code>static String toString(Readable r)</code>

Guava提出源与汇的概念以避免总是直接跟流打交道.

源与汇是指某个你知道如何从中打开流的资源,如File或URL.

源是可读的,汇是可写的.

Guava的源有 <code>ByteSource</code> 和 <code>CharSource</code>; 汇有<code>ByteSink</code> <code>CharSink</code>

源与汇的好处是它们提供了一组通用的操作(如:一旦你把数据源包装成了ByteSource,无论它原先的类型是什么,你都得到了一组按字节操作的方法). 其实就源与汇就类似于Java IO中的<code>InputStream/OutputStream</code>, <code>Reader/Writer</code>. 只要能够获取到他们或者他们的子类, 就可以使用他们提供的操作, 不管底层实现如何.

获取字节源与汇的常用方法有:

字节源

字节汇

<code>Files.asByteSource(File)</code>

<code>Files.asByteSink(File file, FileWriteMode... modes)</code>

<code>Resources.asByteSource(URL url)</code>

-

<code>ByteSource.wrap(byte[] b)</code>

<code>ByteSource.concat(ByteSource... sources)</code>

获取字符源与汇的常用方法有:

字符源

字符汇

<code>Files.asCharSource(File file, Charset charset)</code>

<code>Files.asCharSink(File file, Charset charset, FileWriteMode... modes)</code>

<code>Resources.asCharSource(URL url, Charset charset)</code>

<code>CharSource.wrap(CharSequence charSequence)</code>

<code>CharSource.concat(CharSource... sources)</code>

<code>ByteSource.asCharSource(Charset charset)</code>

<code>ByteSink.asCharSink(Charset charset)</code>

这四个源与汇提供通用的方法进行读/写, 用法与Java IO类似,但比Java IO流会更加简单方便(如<code>CharSource</code>可以一次性将源中的数据全部读出<code>String read()</code>, 也可以将源中的数据一次拷贝到Writer或汇中<code>long copyTo(CharSink/Appendable to)</code>)

<code>Resources</code>常用方法

Resources 方法

<code>static void copy(URL from, OutputStream to)</code>

Copies all bytes from a URL to an output stream.

<code>static URL getResource(String resourceName)</code>

Returns a URL pointing to resourceName if the resource is found using the context class loader.

<code>static List&lt;String&gt; readLines(URL url, Charset charset)</code>

Reads all of the lines from a URL.

<code>static &lt;T&gt; T readLines(URL url, Charset charset, LineProcessor&lt;T&gt; callback)</code>

Streams lines from a URL, stopping when our callback returns false, or we have read all of the lines.

<code>static byte[] toByteArray(URL url)</code>

Reads all bytes from a URL into a byte array.

<code>static String toString(URL url, Charset charset)</code>

Reads all characters from a URL into a String, using the given character set.

<code>Files</code>常用方法

Files 方法

<code>static void append(CharSequence from, File to, Charset charset)</code>

Appends a character sequence (such as a string) to a file using the given character set.

<code>static void copy(File from, Charset charset, Appendable to)</code>

Copies all characters from a file to an appendable object, using the given character set.

<code>static void copy(File from, File to)</code>

Copies all the bytes from one file to another.

<code>static void copy(File from, OutputStream to)</code>

Copies all bytes from a file to an output stream.

<code>static File createTempDir()</code>

Atomically creates a new directory somewhere beneath the system’s temporary directory (as defined by the java.io.tmpdir system property), and returns its name.

<code>static MappedByteBuffer map(File file, FileChannel.MapMode mode, long size)</code>

Maps a file in to memory as per <code>FileChannel.map(java.nio.channels.FileChannel.MapMode, long, long)</code> using the requested FileChannel.MapMode.

<code>static void move(File from, File to)</code>

Moves a file from one path to another.

<code>static &lt;T&gt; T readBytes(File file, ByteProcessor&lt;T&gt; processor)</code>

Process the bytes of a file.

<code>static String readFirstLine(File file, Charset charset)</code>

Reads the first line from a file.

<code>static List&lt;String&gt; readLines(File file, Charset charset)</code>

Reads all of the lines from a file.

<code>static &lt;T&gt; T readLines(File file, Charset charset, LineProcessor&lt;T&gt; callback)</code>

Streams lines from a File, stopping when our callback returns false, or we have read all of the lines.

<code>static byte[] toByteArray(File file)</code>

Reads all bytes from a file into a byte array.

<code>static String toString(File file, Charset charset)</code>

Reads all characters from a file into a String, using the given character set.

<code>static void touch(File file)</code>

Creates an empty file or updates the last updated timestamp on the same as the unix command of the same name.

<code>static void write(byte[] from, File to)</code>

Overwrites a file with the contents of a byte array.

<code>static void write(CharSequence from, File to, Charset charset)</code>

Writes a character sequence (such as a string) to a file using the given character set.

<dl></dl>

<dt>参考:</dt>