JavaNIO系列教程(⼆)Channel通道介绍及FileChannel详解⽬录:
《》
《》
Channel是⼀个通道,可以通过它读取和写⼊数据,它就像⾃来⽔管⼀样,⽹络数据通过Channel读取和写⼊。通道与流的不同之处在于通道是双向的,流只是在⼀个⽅向上移动(⼀个流必须是InputStream或者OutputStream的⼦类),⽽且通道可以⽤于读、写或者同事⽤于读写。因为Channel是全双⼯的,所以它可以⽐流更好地映射底层操作系统的API。特别是在UNIX⽹络编程模型中,底层操作系统的通道都是全双⼯的,同时⽀持读写操作。
NIO中通过channel封装了对数据源的操作,通过channel 我们可以操作数据源,但⼜不必关⼼数据源的具体物理结构。
这个数据源可能是多种的。⽐如,可以是⽂件,也可以是⽹络socket。在⼤多数应⽤中,channel与⽂件描述符或者socket是⼀⼀对应的。Channel⽤于在字节缓冲区和位于通道另⼀侧的实体(通常是⼀个⽂件或套接字)之间有效地传输数据。
channel接⼝源码:
package java.nio.channels;
public interface Channel;
{
public boolean isOpen();
public void close() throws IOException;
}
与缓冲区不同,通道API主要由接⼝指定。不同的操作系统上通道实现(Channel Implementation)会有根本性的差异,所以通道API仅仅描述了可以做什么。因此很⾃然地,通道实现经常使⽤操作系统的本地代码。通道接⼝允许您以⼀种受控且可移植的⽅式来访问底层的I/O服务。
Channel是⼀个对象,可以通过它读取和写⼊数据。拿 NIO 与原来的 I/O 做个⽐较,通道就像是流。所有数据都通过Buffer对象来处理。您永远不会将字节直接写⼊通道中,相反,您是将数据写⼊包含⼀个或者多个字节的缓冲区。同样,您不会直接从通道中读取字节,⽽是将数据从通道读⼊缓冲区,再从缓冲区获取这个字节。
Java NIO的通道类似流,但⼜有些不同:
既可以从通道中读取数据,⼜可以写数据到通道。但流的读写通常是单向的。
通道可以异步地读写。
通道中的数据总是要先读到⼀个Buffer,或者总是要从⼀个Buffer中写⼊。
正如上⾯所说,从通道读取数据到缓冲区,从缓冲区写⼊数据到通道。如下图所⽰:
Channel的实现
这些是Java NIO中最重要的通道的实现:
FileChannel:从⽂件中读写数据
DatagramChannel:通过UDP读写⽹络中的数据
SocketChannel:通过TCP读写⽹络中的数据
ServerSocketChannel:可以监听新进来的TCP连接,像Web服务器那样。对每⼀个新进来的连接都会创建⼀个SocketChannel。
正如你所看到的,这些通道涵盖了UDP 和 TCP ⽹络IO,以及⽂件IO。
FileChannel
FileChannel类可以实现常⽤的read,write以及scatter/gather操作,同时它也提供了很多专⽤于⽂件的新⽅法。这些⽅法中的许多都是我们所熟悉的⽂件操作。
FileChannel类的JDK源码:
package java.nio.channels;
public abstract class FileChannel extends AbstractChannel implements ByteChannel, GatheringByteChannel, ScatteringByteChannel
{
// This is a partial API listing
// All methods listed here can throw java.io.IOException
public abstract int read (ByteBuffer dst, long position);
public abstract int write (ByteBuffer src, long position);
public abstract long size();
public abstract long position();
public abstract void position (long newPosition);
public abstract void truncate (long size);
public abstract void force (boolean metaData);
public final FileLock lock();
public abstract FileLock lock (long position, long size, boolean shared);
public final FileLock tryLock();
public abstract FileLock tryLock (long position, long size, boolean shared);
public abstract MappedByteBuffer map (MapMode mode, long position, long size);
public static class MapMode;
public static final MapMode READ_ONLY;
public static final MapMode READ_WRITE;
public static final MapMode PRIVATE;
public abstract long transferTo (long position, long count, WritableByteChannel target);
public abstract long transferFrom (ReadableByteChannel src, long position, long count);
} 
⽂件通道总是阻塞式的,因此不能被置于⾮阻塞模式。现代操作系统都有复杂的缓存和预取机制,使得本地磁盘I/O操作延迟很少。⽹络⽂件系统⼀般⽽⾔延迟会多些,不过却也因该优化⽽受益。⾯向流的I/O的⾮阻塞范例对于⾯向⽂件的操作并⽆多⼤意义,这是由⽂件I/O本质上的不同性质造成的。对于⽂件I/O,最强⼤之处在于异步I/O(asynchronous I/O),它允许⼀个进程可以从操作系统请求⼀个或多个I/O操作⽽不必等待这些操作的完成。发起请求的进程之后会收到它请求的I/O操作已完成的通知。
  FileChannel对象是线程安全(thread-safe)的。多个进程可以在同⼀个实例上并发调⽤⽅法⽽不会引起任何问题,不过并⾮所有的操作都是多线程的(multithreaded)。影响通道位置或者影响⽂件⼤⼩的操作都是单线程的(single-threaded)。如果有⼀个线程已经在执⾏会影响通道位置或⽂件⼤⼩的操作,那么其他尝试进⾏此类操作之⼀的线程必须等待。并发⾏为也会受到底层的操作系统或⽂件系统影响。
  每个FileChannel对象都同⼀个⽂件描述符(file descriptor)有⼀对⼀的关系,所以上⾯列出的API⽅法与在您最喜欢的POSIX(可移植操作系统接⼝)兼容的操作系统上的常⽤⽂件I/O系统调⽤紧密对应也就不⾜为怪了。本质上讲,RandomAccessFile类提供的是同样的抽象内容。在通道出现之前,底层的⽂件操作都是通过RandomAccessFile类的⽅法来实现的。FileChannel模拟同样的I/O服务,因此它的API⾃然也是很相似的。
  三者之间的⽅法对⽐:
FILECHANNEL RANDOMACCESSFILE POSIX SYSTEM CALL
read( )read( )read( )
write( )write( )write( )
size( )length( )fstat( )
position( )getFilePointer( )lseek( )
position (long newPosition)seek( )lseek( )
truncate( )setLength( )ftruncate( )
force( )getFD().sync( )fsync( )
下⾯是⼀个使⽤FileChannel读取数据到Buffer中的⽰例:
package com.dxz.springsession.nio.demo1;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class FileChannelTest {
/**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
RandomAccessFile aFile = new RandomAccessFile("d:\\soft\\", "rw");
FileChannel inChannel = Channel();
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = ad(buf);
while (bytesRead != -1) {
System.out.println("Read " + bytesRead);
buf.flip();
while (buf.hasRemaining()) {
System.out.print((char) ());
}
buf.clear();
bytesRead = ad(buf);
}
aFile.close();
System.out.println("wan");
}
}
⽂件内容:
1234567qwertrewq
uytrewq
hgfdsa
nbvcxz
iop89
输出结果:
Read 48
1234567qwertrewq
uytrewq
hgfdsa
nbvcxz
iop89wan
注意 buf.flip() 的调⽤,⾸先读取数据到Buffer,然后反转Buffer,接着再从Buffer中读取数据。下⼀节会深⼊讲解Buffer的更多细节。
1、打开FileChannel
在使⽤FileChannel之前,必须先打开它。但是,我们⽆法直接打开⼀个FileChannel,需要通过使⽤⼀个InputStream、OutputStream或RandomAccessFile来获取⼀个FileChannel实例。下⾯是通过Random
AccessFile打开FileChannel的⽰例:
RandomAccessFile aFile = new RandomAccessFile("", "rw");
FileChannel inChannel = Channel();
2、从FileChannel读取数据
调⽤多个read()⽅法之⼀从FileChannel中读取数据。如:
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = ad(buf);
⾸先,分配⼀个Buffer。从FileChannel中读取的数据将被读到Buffer中。
然后,调⽤ad()⽅法。该⽅法将数据从FileChannel读取到Buffer中。read()⽅法返回的int值表⽰了有多少字节被读到了Buffer 中。如果返回-1,表⽰到了⽂件末尾。
3、向FileChannel写数据
使⽤FileChannel.write()⽅法向FileChannel写数据,该⽅法的参数是⼀个Buffer。如:
String newData = "New String to write " + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.Bytes());
buf.flip();
while(buf.hasRemaining()) {
channel.write(buf);
}
注意FileChannel.write()是在while循环中调⽤的。因为⽆法保证write()⽅法⼀次能向FileChannel写⼊多少字节,因此需要重复调⽤write()⽅法,直到Buffer中已经没有尚未写⼊通道的字节。
4、关闭FileChannel
⽤完FileChannel后必须将其关闭。如:
channel.close();
5、FileChannel的position⽅法
有时可能需要在FileChannel的某个特定位置进⾏数据的读/写操作。可以通过调⽤position()⽅法获取FileChannel的当前位置。
也可以通过调⽤position(long pos)⽅法设置FileChannel的当前位置。
这⾥有两个例⼦:
long pos = channel.position();
channel.position(pos +123);
如果将位置设置在⽂件结束符之后,然后试图从⽂件通道中读取数据,读⽅法将返回-1 —— ⽂件结束标志。
如果将位置设置在⽂件结束符之后,然后向通道中写数据,⽂件将撑⼤到当前位置并写⼊数据。这可能导致“⽂件空洞”,磁盘上物理⽂件中写⼊的数据间有空隙。
6、FileChannel的size⽅法
FileChannel实例的size()⽅法将返回该实例所关联⽂件的⼤⼩。如:
long fileSize = channel.size();
7、FileChannel的truncate⽅法
可以使⽤uncate()⽅法截取⼀个⽂件。截取⽂件时,⽂件将中指定长度后⾯的部分将被删除。如:
这个例⼦截取⽂件的前1024个字节。
8、FileChannel的force⽅法
FileChannel.force()⽅法将通道⾥尚未写⼊磁盘的数据强制写到磁盘上。出于性能⽅⾯的考虑,操作系统会将数据缓存在内存中,所以⽆法保证写⼊到FileChannel⾥的数据⼀定会即时写到磁盘上。要保证这⼀点,需要调⽤force()⽅法。
force()⽅法有⼀个boolean类型的参数,指明是否同时将⽂件元数据(权限信息等)写到磁盘上。
下⾯的例⼦同时将⽂件数据和元数据强制写到磁盘上:
channel.force(true);
⽰例:
package com.dxz.nio;
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class FileChannelRead {
static public void main(String args[]) throws Exception {
FileInputStream fin = new FileInputStream("e:\\logs\\");
/
/ 获取通道
FileChannel fc = Channel();
// 创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 读取数据到缓冲区
buffer.flip();
while (aining() > 0) {
byte b = ();
System.out.print(((char) b));
}
fin.close();
}
}
写⼊:
package com.dxz.nio;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class FileChannelWrite {
static private final byte message[] = { 83, 111, 109, 101, 32, 98, 121, 116, 101, 115, 46 };
static public void main(String args[]) throws Exception {
FileOutputStream fout = new FileOutputStream("e:\\logs\\");
FileChannel fc = Channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
for (int i = 0; i < message.length; ++i) {
buffer.put(message[i]);
}
buffer.flip();
fc.write(buffer);
fout.close();
}
}
9、FileChannel的transferTo和transferFrom⽅法--通道之间的数据传输
如果两个通道中有⼀个是FileChannel,那你可以直接将数据从⼀个channel(译者注:channel中⽂常译作通道)传输到另外⼀个channel。transferFrom()
FileChannel的transferFrom()⽅法可以将数据从源通道传输到FileChannel中(译者注:这个⽅法在JDK⽂档中的解释为将字节从给定的可读取字节通道传输到此通道的⽂件中)。下⾯是⼀个简单的例⼦:
通过FileChannel完成⽂件间的拷贝:
package com.dxz.springsession.nio.demo1;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
public class FileChannelTest2 {
public static void main(String[] args) throws IOException {
RandomAccessFile aFile = new RandomAccessFile("d:\\soft\\", "rw");
FileChannel fromChannel = Channel();
RandomAccessFile bFile = new RandomAccessFile("d:\\soft\\", "rw");
FileChannel toChannel = Channel();
truncate读
long position = 0;
long count = fromChannel.size();
aFile.close();
bFile.close();

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。