Channel是java NIO中的通道,他类似于流,但是又有一些不同:
- 既可以从通道中读取数据,又可以写数据到通道。但流的读写通常是单向的。
- 通道可以异步地读写。
- 通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入。
Channel的实现
这些是Java NIO中最重要的通道的实现:
- FileChannel:FileChannel 从文件中读写数据。
- DatagramChannel :DatagramChannel 能通过UDP读写网络中的数据。
- SocketChannel:SocketChannel 能通过TCP读写网络中的数据。
- ServerSocketChannel:ServerSocketChannel可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。
下面是一个基本的Channel实例:
package pack01;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class ChannelDemo01 {
public static void main(String[] args) throws Exception {
RandomAccessFile file = new RandomAccessFile("a.txt","rw");
//定义一个文件通道
FileChannel inChannel = file.getChannel();
//分配一个新的字节缓冲区
ByteBuffer buffer = ByteBuffer.allocate(52);
//将字节序列从此通道读入给定的缓冲区,返回读取的字节数,如果该通道已到达流的末尾,那么返回-1
int bytesRead = inChannel.read(buffer);
while(bytesRead != -1){
System.out.println("Read "+bytesRead);
buffer.flip();
//如果在当前位置和限制之间还有元素
while(buffer.hasRemaining()) {
System.out.print((char)buffer.get());
}
//清除缓冲区
buffer.clear();
//将数据从通道读入到缓冲区
bytesRead = inChannel.read(buffer);
}
file.close();
}
}
这里在获取Channel的时候通过RandomAccessFile, 为了以可读可写的方式打开文件,这里使用RandomAccessFile来创建文件。
RandomAccessFile是用来访问那些保存数据记录的文件的,你就可以用seek()方法来访问记录,并进行读写了,这些记录的大小不必相同;但是其大小与位置是必须可知的,但是该类仅限于操作文件。
基本上,RandomAccessFile的工作方式是,把DataInputStream和DataOutputStream结合起来,再加上它自己的一些方法,比如定位用的getFilePointer( ),在文件里移动用的seek( ),以及判断文件大小的length( )、skipBytes()跳过多少字节数。此外,它的构造函数还要一个表示以只读方式("r"),还是以读写方式("rw")打开文件的参数 (和C的fopen( )一模一样)。它不支持只写文件。
RandomAccessFile的绝大多数功能,但不是全部,已经被JDK 1.4的nio的"内存映射文件(memory-mapped files)"给取代了,使用这个类的时候该考虑一下是不是用"内存映射文件"来代替RandomAccessFile了。
下面的实例简单的演示一下使用RandomAccessFile:
package pack02;
import java.io.IOException;
import java.io.RandomAccessFile;
public class TestRandonAccessFile {
public static void main(String[] args) throws IOException{
RandomAccessFile rs = new RandomAccessFile("desc.txt","rw");
for(int i=0;i<10;i++) {
//写入基本的Double类型
rs.writeDouble(i*1.411);
}
rs.close();
rs = new RandomAccessFile("desc.txt","rw");
//将文件指针移到第五个double数据后面
rs.seek(8*5);
//覆盖第六个double数据
rs.writeDouble(43.234);
rs.close();
rs = new RandomAccessFile("desc.txt","r");
for(int i=0;i<10;i++) {
System.out.println("Value"+i+":"+rs.readDouble());
}
rs.close();
}
}
内存映射文件
内存映射文件能让你创建和修改那些因为太大而无法放入内存的文件。有了内存映射文件,你就可以认为文件已经全部读进了内存,然后把它当成一个非常大的数组来访问。
这种解决办法能大大简化修改文件的代码。
fileChannel.map(FileChannel.MapMode mode, long position, long size)将此通道的文件区域直接映射到内存中。
注意,你必须指明,它是从文件的哪个位置开始映射的,映射的范围又有多大;也就是说,它还可以映射一个大文件的某个小片断。
MappedByteBuffer是ByteBuffer的子类,因此它具备了ByteBuffer的所有方法,但新添了force()将缓冲区的内容强制刷新到存储设备中去、
load()将存储设备中的数据加载到内存中、isLoaded()位置内存中的数据是否与存储设置上同步。这里只简单地演示了一下put()和get()方法
,除此之外,你还可以使用asCharBuffer( )之类的方法得到相应基本类型数据的缓冲视图后,可以方便的读写基本类型数据。
该程序创建了一个128Mb的文件,如果一次性读到内存可能导致内存溢出,但这里访问好像只是一瞬间的事,
这是因为,真正调入内存的只是其中的一小部分,其余部分则被放在交换文件上。这样你就可以很方便地修改超大型的文件了(最大可以到2 GB)。
注意,Java是调用操作系统的"文件映射机制"来提升性能的。
package pack02;
import java.io.FileNotFoundException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
public class LargeMappedFiles{
static int length=0x8_000_000;
public static void main(String[] args) throws Exception {
//为了以可读可写的方式打开文件
FileChannel fc = new RandomAccessFile("desc.txt","rw").getChannel();
//文件通道的可读可写要建立在文件流可读可写的基础上
MappedByteBuffer out = fc.map(FileChannel.MapMode.READ_WRITE, 0, length);
//向文件中写入128M内容
for(int i=0;i<length;i++){
out.put((byte) 'x');
}
System.out.println("Finished writing");
//读取文件中间6个字节内容
for(int i=length/2;i<length/2+6;i++){
System.out.println(out.getChar(i));
}
fc.close();
}
}