首页 > 代码库 > java nio 通道(二)

java nio 通道(二)

本文章来源于我的个人博客: java nio 通道(二)


一,文件通道

    文件通道总是阻塞式的,因此不能被置于非阻塞模式。

    FileChannel对象是线程安全的。多个进程可以在同一个实例上并发调用方法而不会引起任何问题,不过非所有的操作都是多线程的。影响通道位置或者影响文件大小的操作都是单线程的。通过FileChannel实例看到的某个文件的视图同通过一个外部的非java进程看到的该文件的视图可能一致也可能不一致。

    创建文件通道:

RandomAccessFile randomAccessFile = new RandomAccessFile(filename,"r");
//设置读取文件的位置
randomAccessFile.seek(1000);
//创建文件通道
FileChannel fileChannel = randomAccessFile.getChannel();
System.out.println(fileChannel.position());  // print 1000
randomAccessFile.seek(500);
System.out.println(fileChannel.position());  //print 500
fileChannel.position(200);  
System.out.println(randomAccessFile.getFilePointer());  //print 200


二,文件锁定(FileLock)

    大家都知道,文件锁分为独占所和共享锁。独占锁是当有一个锁拥有了文件的操作权限之后,其它的访问者都必须等待当前的操作者完成才能操作文件;而共享锁是所有的访问者都可以同时使用该文件。独占锁最重要用于文件的写,而共享锁用于文件的读。

    java的文件锁只与文件关联,而不是与通道关联。文件锁旨在在进程级别上判优文件访问,比如在主要的程序组件之间或者在继承其他供应商的组件时。如果需要控制多个java线程的并发访问,就需要自己做锁定方案。

    再FileChannel类中有四个请求锁的方法,分别为:

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);

    两个带参数的请求所方法的意思是指定需要锁定文件的区域位置,并设置这个锁是否为共享锁,注意,如果要请求一个独占锁,则文件必须是以写模式打开的,锁区域的size值可以超出文件尾部,这样可以提前锁定需要待写入的文件数据区域,也可以锁定一个不包含任何内容的文件内容区域。不带参数的请求方法是请求整个文件的独占锁,也就是使用无参数的请求方法获取的锁,会阻塞后来的操作,只当这个锁定的访问者执行完它的操作为止。

    请一定要记住,在操作完成之后,一定要release锁,不然将可能导致程序奔溃或者死锁现象。


三,内存映射文件(MappedByteBuffer file):

    FileChannel类提供了一个名为map()的方法,该方法可以在一个打开的文件和一个特殊类型的ByteBuffer之间建立一个虚拟内存映射MappedByteBuffer。这个对象和基于内存的缓冲区类似,只不过该对象的数据元素存储在磁盘上的一个文件中。调用get()方法会从磁盘文件中获取数据,此数据反映该文件的当前内容,即使在映射建立之后文件已经被一个外部进程做了修改,也会同步到该对象所在的内存视图中;当然该对象的put()方法也会同步更新此文上的文件,即对文件的改变其他的访问者也是可见的。

    通过内存映射机制来访问一个文件会比使用常规方法读写高效得多,甚至比使用通道的效率都高。因为不需要做明确的系统调用,那会很消耗时间,更重要的是,操作系统的虚拟内存可以自动缓存内存页,这些内存页是系统内存来缓存的,所以不会消耗java虚拟机内存堆。(注:这段话因为涉及了操作系统的内存机制,博主也不怎么理解,博主的想法是只要知道使用这个对象会脱离JVM的内存堆,剔除了JVM这个中间桥梁)

    FileChannel类调用内存映射的方法如下:

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;
    //代表需要一个写时拷贝的映射。意味着通过put()方法所做的任何修改都会导致产生一个私有的数据拷贝并且
    //该拷贝中的数据只有MappedByteBuffer实例可以看到。该过程不会对地城文件做任何修改,而且一旦缓冲
    //区被拖到垃圾收集动作,那些修改的数据都会丢失。使用此模式时,其它的操作者对文件的修改,也都能反映
    //到这个内存映射区域,除非该缓冲区已经修改了文件上的同一区域。
    public static final MapMode PRIVATE;
}

    这个方法的第一个参数是设定这个内存映射区域是只读还是只写或者私有方式访问,第二个参数和第三个参数一起决定了内存映射的区域。要注意,这个内存映射的锁方式和文件锁的范围机制是不一样的。如果给这个内存映射对象的size的值设置为Integer.MAX_VALUE,则这个文件的大小会扩张到超过2.1GB,也就是说,这个内存映射文件的锁定区域如果超出了文件的大小,那么它会自动给这个设定的区域加入数据。

    MappedByteBuffer对象被创建之后,就不会受通道的关闭影响,也就是如果关闭相关联的FileChannel不会破坏映射,只有丢弃缓冲区对象本身才会破坏该映射。

    MappedByteBuffer类有以下的几个独有方法:

public final MappedByteBuffer load();
public final boolean isLoaded();
public final MappedByteBuffer force();

    当为一个文件建立虚拟内存映射之后,文件数据通常不会因此被从磁盘读取到内存(这取决于操作系统)。该过程类似于打开一个文件:文件先被定位,然后一个文件句柄会被创建,当准备好之后,就可以通过句柄来访问文件数据。对于映射缓冲区,虚拟内存系统将根据需要来把文件中相应区块的数据读进来。这个页验证或防错过程需要一定的时间,因为将文件数据读取到内存需要一次或多次磁盘访问。

    load()方法会加载整个文件以使它常驻内存。但是调用此方法是一个代价高昂的操作,因为它会导致大量的页调入。force()方法会强制将映射缓冲区上的更改应用到永久磁盘存储器上。

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;


public class TestMappedByte {

	/**
	 * @param args
	 * @throws IOException 
	 */
	public static void main(String[] args) throws IOException {
		 File file = new File("/home/lifeix/3.txt");
        if(!file.exists()) {
            file.createNewFile();
        }
        String str = "jiang fuqiang is cool
";
        //此类没有追加文件选项,如果需要追加,则需要调用fos.seek(int)方法
        RandomAccessFile fos = new RandomAccessFile(file,"rws"); 
        FileChannel fc = fos.getChannel();
        ByteBuffer bb = ByteBuffer.allocateDirect(str.length());
        bb.put(str.getBytes());
        bb.flip();
        fc.write(bb);
        bb.clear();
        
        //如果这里不使用fc.position(),则此处写入的数据会覆盖掉上面写入的数据,这是一种自己实现的文件追加方式,
        //此处不能用FileOutputStream,不然会报错
        MappedByteBuffer mbb = fc.map(MapMode.READ_WRITE, fc.position(), 1024);
        mbb.put(str.getBytes());
        mbb.force();
        mbb.clear();
        fc.close();
        fos.close();
        
        FileInputStream fis = new FileInputStream(file);
        FileChannel fc1 = fis.getChannel();
        MappedByteBuffer mbb1  = fc1.map(MapMode.READ_ONLY, 0, fc1.size());
        byte[] b = new byte[mbb1.remaining()];
        mbb1.get(b);
        System.out.println(new String(b));
        fc1.close();
        fis.close();
        
	}

}