欢迎访问悦橙教程(wld5.com),关注java教程。悦橙教程  java问答|  每日更新
页面导航 : > > 文章正文

Java NIO,

来源: javaer 分享于  点击 22155 次 点评:126

Java NIO,


一.简介

  JDK1.4的java.nio.*包中引入了新的Java I/O类库。其目的在于提高速度。速度的提高来自于所使用的的I/O和网络更接近于操作系统执行I/O的方式:通道和缓冲区。我们并不直接和通道交互,我们只是和缓冲器交互,并把缓冲器派送到通道。通道要么从缓冲器获得数据,要么向缓冲器发送数据。唯一直接与通道交互的缓冲器是ByteBuffer——-可以存储未加工字节的缓冲器。ByteBuffer是一个相当基础的类:通过告知分配多少存储空间来创建一个ByteBuffer对象。

  旧的I/O类库中有三个类被修改了,用以产生FileChannel。这三个被修改的类是FileInputStreamFileOutputStream以及用于既读又写的RandomAccesFile。注意,这些是字节操纵流,与底层的nio性质一致。Reader和Writer这种字符模式的类不能用于产生通道;但是java.nio.channels.Channels类提供了实用的方法,用以在通道中产生ReaderWriter

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class GetChannel {

    private static final int BSIZE = 1024; 

    public static void main(String[] args) throws IOException {
        FileChannel fc = new FileOutputStream("data.txt").getChannel();
        fc.write(ByteBuffer.wrap("This is a nio test".getBytes()));
        fc.close();

        fc = new RandomAccessFile("data.txt","rw").getChannel();
        fc.position(fc.size());
        fc.write(ByteBuffer.wrap("This is a nio test".getBytes()));

        fc = new FileInputStream("data.txt").getChannel();
        ByteBuffer buff = ByteBuffer.allocate(BSIZE);
        fc.read(buff);
        buff.flip();
        while(buff.hasRemaining()) {
            System.out.println((char)buff.get());
        }
    }
}

对于这里所展示的任何流类,getChannel()将会产生一个FileChannel。通道是一种相当基础的东西:可以向它传送用于读写的ByteBuffer,并且可以锁定文件的某些区域用于独占式访问。

  将字节存放于ByteBuffer的方法之一是:使用一种“put”方法直接对它们进行填充,填入一个或多个字节,或基本数据类型的值。不过,正于所见,也可以将已存在的字节数组“包装”到ByteBuffer中。一旦如此,就不再复制底层的数组,而是把它作为所产生的ByteBuffer的存储器,我们称之为数组支持的ByteBuffer

  一旦调用read()来告知FileChannel()ByteBuffer存储字节,就必须调用缓冲器上的flip()方法,让ByteBuffer做好让别人读取字节的准备,这适用于获取更大的速度。如果我们打算使用缓冲器执行进一步的read()操作,我们必须调用clear()方法为每个read()做好准备。

public class ChannelCopy {

    private static final int BSIZE = 1024;

    public static void main(String[] args) throws IOException {
        FileChannel fcIn = new FileInputStream("data.txt").getChannel();
        FileChannel fcOut = new FileOutputStream("data1.txt").getChannel();

        ByteBuffer bf = ByteBuffer.allocate(BSIZE);

        while(fcIn.read(bf) != -1) {
            bf.flip();       // prepare for writing
            fcOut.write(bf);
            bf.clear();       // prepare for reading
        }
    }
}

  可以看到,打开一个FileChannel以用于读,而打开另一个用于写。ByteBuffer被分配了空间,当FileChannel的read()方法返回-1时(一个分界符,毋庸置疑,它源于Unix和C),表示我们已经到达输入的末尾。每次read()操作之后,就会将数据输入到缓冲器中,flip()则是准备缓冲器以便它的信息可以由write()提取。write()操作之后,信息仍在缓冲器中。接着clear()操作则对所有的内部指针重新安排,以便缓冲器在另一个read()操作期间能够做好接收数据的准备。
  然而,上面那个程序并不是处理此类操作的理想方式。特殊方法transferTo()transferFrom()则允许我们将一个通道和另一个通道直接相连:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;

public class TransferTo {

    public static void main(String[] args) throws IOException {

        FileChannel fcIn = new FileInputStream("data.txt").getChannel();
        FileChannel fcOut = new FileOutputStream("data1.txt").getChannel();

        fcIn.transferTo(0, fcIn.size(), fcOut);

        /*or :
         * fcOut.transferFrom(fcIn, 0, fcIn.size());
         */
    }
}

1.转换数据

  回过头看GetChannel .java这个程序就会发现,为了输出文件中的信息,我们必须每次只读取一个字节的数据,然后将每个byte类型强转成char类型。这种方法似乎有点原始——-如果我们查看一下java.nio.CharBuffer这个类,将会发现它有个toString()方法是这样定义的:“返回一个包含缓冲器中所有字符的字符串”。既然ByteBuffer可以看作是具有asCharBuffer()方法的CharBuffer,那么为什么不用呢?正如下面输出语句汇总的第一行所见,这种方法并不能解决问题:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;

public class BufferToText {

    private static final int BSIZE = 1024;

    public static void main(String[] args) throws IOException {
        FileChannel fc = new FileOutputStream("data.txt").getChannel();
        fc.write(ByteBuffer.wrap("This is a nio test".getBytes()));
        fc.close();

        fc = new FileInputStream("data.txt").getChannel();
        ByteBuffer buff = ByteBuffer.allocate(BSIZE);
        fc.read(buff);
        buff.flip();

        // Does not work
        System.out.println(buff.asCharBuffer());

        // Decode using system default Charset
        buff.rewind();

        String encoding = System.getProperty("file.encoding");
        System.out.println("Decoding using " + encoding + " : " + Charset.forName(encoding).decode(buff)); 

        // or we could encode with something that will print:
        fc = new FileOutputStream("data.txt").getChannel();
        fc.write(ByteBuffer.wrap("This is a nio test".getBytes("UTF-16BE")));
        fc.close(); 

        // Now try reading again
        fc = new FileInputStream("data.txt").getChannel();
        buff.clear();
        fc.read(buff);
        buff.flip();
        System.out.println(buff.asCharBuffer());

        // use a CharBuffer to write through:
        fc = new FileOutputStream("data.txt").getChannel();
        buff = ByteBuffer.allocate(24); // 分配了24个字节
        buff.asCharBuffer().put("some text");
        fc.write(buff);
        fc.close();

        //Read and Display
        fc = new FileInputStream("data.txt").getChannel();
        buff.clear();
        fc.read(buff);
        buff.flip();
        System.out.println(buff.asCharBuffer());
    }
}

  缓冲器容纳的是普通的字节,为了把他们转换成字符,我们需要在输入他们的时候对其进行编码(这样,他们输出时才有意义),要么在将其从缓冲器输出时对他们进行解码。可以使用java.nio.charset.Charset类实现这些功能,该类提供了把数据编码转换成多种不同类型的字符集的工具。

  让我们返回到ByteBufferToText.java,如果我们想对缓冲器调用rewind()方法(调用该方法是为了返回到数据开始部分),接着使用平台的默认字符集对数据进行decode(),那么作为结果的CharBuffer可以很好的输出打印到控制台。

2.获取基本类型
  尽管ByteBuffer只能保存字节类型的数据,但是它具有可以从其所容纳的字节中产生各种不同基本类型值的方法。下面这个例子展示了怎样使用这些方法来插入和抽取各种数值:

public class GetData {

     private static final int BSIZE = 1024;
      public static void main(String[] args) {
        ByteBuffer bb = ByteBuffer.allocate(BSIZE);
        // Allocation automatically zeroes the ByteBuffer:
        int i = 0;
        while(i++ < bb.limit())
          if(bb.get() != 0)
            System.out.println("nonzero");
        System.out.println("i = " + i);
        bb.rewind();
        // Store and read a char array:
        bb.asCharBuffer().put("Howdy!");
        char c;
        while((c = bb.getChar()) != 0)
            System.out.println(c + " ");
        System.out.println();
        bb.rewind();
        // Store and read a short:
        bb.asShortBuffer().put((short)471142);
        System.out.println(bb.getShort());
        bb.rewind();
        // Store and read an int:
        bb.asIntBuffer().put(99471142);
        System.out.println(bb.getInt());
        bb.rewind();
        // Store and read a long:
        bb.asLongBuffer().put(99471142);
        System.out.println(bb.getLong());
        bb.rewind();
        // Store and read a float:
        bb.asFloatBuffer().put(99471142);
        System.out.println(bb.getFloat());
        bb.rewind();
        // Store and read a double:
        bb.asDoubleBuffer().put(99471142);
        System.out.println(bb.getDouble());
        bb.rewind();
      }
}

  在分配一个ByteBuffer之后,可以通过检测它的值来查看缓冲器的分配方式是否将其内容自动置零——它确实是这样做了。这里一共检测了1024个值(由缓冲器的limit()决定),并且所有的值都是零。向ByteBuffer插入基本数据类型最简单的方法是:利用asCharBuffer()asShortBuffer()等获得该缓冲器上的视图,然后使用视图的put()方法。我们会发现此方法适用于所有基本数据类型。仅有一个小小的例外,即,使用asShortBuffer()put()方法时,需要进行类型转换(注意类型转换会截取或改变结果)。而其他所有的视图缓冲器在使用put()方法时,不需要进行类型转换。

3.视图缓冲器

  视图缓冲器(view buffer)可以让我们通过某个特定的基本数据类型的视图视窗查看其底层的ByteBufferByteBuffer依然是实际存储数据的地方,“支持”着前面的视图,因此,对视图的任何修改都会映射成为对ByteBuffer中数据的修改。

public class IntBufferDemo {

    private static final int BSIZE = 1024;
      public static void main(String[] args) {
        ByteBuffer bb = ByteBuffer.allocate(BSIZE);
        IntBuffer ib = bb.asIntBuffer();
        // Store an array of int:
        ib.put(new int[]{ 11, 42, 47, 99, 143, 811, 1016 });
        // Absolute location read and write:
        System.out.println(ib.get(3));
        ib.put(3, 1811);
        // Setting a new limit before rewinding the buffer.
        ib.flip();
        while(ib.hasRemaining()) {
          int i = ib.get();
          System.out.println(i);
        }
      }
}

   先用重载后的put()方法存储一个整数数组。接着get()put()方法调用直接访问底层ByteBuffer中的某个整数位置。注意,这些通过直接与ByteBuffer对话访问绝对位置的方式也同样适用于基本数据类型。

  一旦底层的ByteBuffer通过视图缓冲器填满了整数或其他基本类型时,就可以直接被写到通道中了。正像从通道中读取那样容易,然后使用视图转换器可以把任何数据都转化成某一特定的基本类型。在下面的例子中,通过在同一个ByteBuffer上建立不同的视图缓冲器,将同一字节序列翻译成了shortintfloatlongdouble类型的数据。

//: io/ViewBuffers.java
import java.nio.*;
import static net.mindview.util.Print.*;

public class ViewBuffers {
  public static void main(String[] args) {
    ByteBuffer bb = ByteBuffer.wrap(
      new byte[]{ 0, 0, 0, 0, 0, 0, 0, 'a' });
    bb.rewind();
    printnb("Byte Buffer ");
    while(bb.hasRemaining())
      printnb(bb.position()+ " -> " + bb.get() + ", ");
    print();
    CharBuffer cb =
      ((ByteBuffer)bb.rewind()).asCharBuffer();
    printnb("Char Buffer ");
    while(cb.hasRemaining())
      printnb(cb.position() + " -> " + cb.get() + ", ");
    print();
    FloatBuffer fb =
      ((ByteBuffer)bb.rewind()).asFloatBuffer();
    printnb("Float Buffer ");
    while(fb.hasRemaining())
      printnb(fb.position()+ " -> " + fb.get() + ", ");
    print();
    IntBuffer ib =
      ((ByteBuffer)bb.rewind()).asIntBuffer();
    printnb("Int Buffer ");
    while(ib.hasRemaining())
      printnb(ib.position()+ " -> " + ib.get() + ", ");
    print();
    LongBuffer lb =
      ((ByteBuffer)bb.rewind()).asLongBuffer();
    printnb("Long Buffer ");
    while(lb.hasRemaining())
      printnb(lb.position()+ " -> " + lb.get() + ", ");
    print();
    ShortBuffer sb =
      ((ByteBuffer)bb.rewind()).asShortBuffer();
    printnb("Short Buffer ");
    while(sb.hasRemaining())
      printnb(sb.position()+ " -> " + sb.get() + ", ");
    print();
    DoubleBuffer db =
      ((ByteBuffer)bb.rewind()).asDoubleBuffer();
    printnb("Double Buffer ");
    while(db.hasRemaining())
      printnb(db.position()+ " -> " + db.get() + ", ");
  }
} /* Output:
Byte Buffer 0 -> 0, 1 -> 0, 2 -> 0, 3 -> 0, 4 -> 0, 5 -> 0, 6 -> 0, 7 -> 97,
Char Buffer 0 ->  , 1 ->  , 2 ->  , 3 -> a,
Float Buffer 0 -> 0.0, 1 -> 1.36E-43,
Int Buffer 0 -> 0, 1 -> 97,
Long Buffer 0 -> 97,
Short Buffer 0 -> 0, 1 -> 0, 2 -> 0, 3 -> 97,
Double Buffer 0 -> 4.8E-322,
*///:~

  ByteBuffer通过一个被“包装”过的8字节数组产生,然后通过各种不同的基本类型的视图缓冲器显示了出来。

字节存放次序

  不同的机器可能会使用不同的字节排序方法来存储数据。“big ending”(高位优先)将重要的字节存放在地址最低的存储单元。而“little ending”(地位优先)则是将最重要的字节存放在地址最高的存储单元。当存储量大于一个字节时,像int、float等。就要考虑字节的顺序问题了。ByteBuffer是以高位优先的形式存储数据的,并且数据在网上传送时也常常使用高位优先的形式。我们可以使用带有参数ByteOrder.BIG_ENDINGByteOrder.LITTLE_ENDINGorder()方法改变ByteBuffer的字节排序方式。

  考虑包含下面两个字节的ByteBuffer:

   00000000 01100001

   如果我们以short(ByteBuffer.asShortBuffer())形式读取数据,得到的数字是97(二进制形式为 00000000 01100001);但如果将ByteBuffer更改成低位优先形式,仍以short形式读取数据;得到的数字却是24832(而进制形式为01100001 00000000 )。

  这个例子展示了怎样通过字节存放模式设置来改变字符总的字节次序:

//: io/Endians.java
// Endian differences and data storage.
import java.nio.*;
import java.util.*;
import static net.mindview.util.Print.*;

public class Endians {
  public static void main(String[] args) {
    ByteBuffer bb = ByteBuffer.wrap(new byte[12]);
    bb.asCharBuffer().put("abcdef");
    print(Arrays.toString(bb.array()));
    bb.rewind();
    bb.order(ByteOrder.BIG_ENDIAN);
    bb.asCharBuffer().put("abcdef");
    print(Arrays.toString(bb.array()));
    bb.rewind();
    bb.order(ByteOrder.LITTLE_ENDIAN);
    bb.asCharBuffer().put("abcdef");
    print(Arrays.toString(bb.array()));
  }
} /* Output:
[0, 97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102]
[0, 97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102]
[97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102, 0]
*///:~

4.用缓冲器操纵数据

  ByteBuffer是将数据移进移出通道的唯一方式,并且我们只能创建一个独立的基本类型缓冲器,或者用“as”方法从ByteBuffer中获得。也就是说,我们不能把基本类型的缓冲器转换成ByteBuffer。然而,由于我们可以经由视图缓冲器将基本类型数据移进移出ByteBuffer(bb.asCharBuffer().put(“abcdef”)),所以这也就不是什么真正的限制了。

5. 缓冲器的细节

  Buffer由数据和可以高效地访问及操纵这些数据的四个索引组成,这四个索引是:mark(标记)、position(位置)、limit(界限)、capacity(容量)。下面是用于设置和复位索引及查询它们的方法。

方法 描述
capacity 返回缓冲区容量
clear() 清空缓冲区,将position设置为0,limit设置为容量。我们可以调用此方法覆写缓冲区
flip() 将limit设置为position,position设置为0,。此方法用于准备从缓冲区读取已经写入的数据
limit() 返回limit值
limit(int lim) 设置limit的值
mark() 将mark设置为position
position() 返回position值
position(int pos) 设置position的值
remaining() 返回limit-position
hasRemaining() 若有介于position和limit之间的元素,则返回true
reset() 把position的值设为mark的值
rewind() 把position设置到缓冲器开始的位置

在缓冲器中插入和提取数据的方法会更新这些索引,用于反应所产生的变化。

import java.nio.ByteBuffer;
import java.nio.CharBuffer;

public class UsingBuffers {

    private static void symmetricScramble(CharBuffer buffer) {
        while (buffer.hasRemaining()) {
            buffer.mark();
            char c1 = buffer.get();
            char c2 = buffer.get();

            buffer.reset();
            buffer.put(c2).put(c1);
        }
    }

    public static void main(String[] args) {
        char[] data = "UsingBuffers".toCharArray();
        ByteBuffer bb = ByteBuffer.allocate(data.length * 2);
        CharBuffer cb = bb.asCharBuffer();
        cb.put(data);
        /*
        * or:
        * CharBuffer cb = CharBuffer.wrap("UsingBuffers".toCharArray());
        */
        System.out.println(cb.rewind());
        symmetricScramble(cb);
        System.out.println(cb.rewind());
        symmetricScramble(cb);
        System.out.println(cb.rewind());
    }
}

注意:一旦调用缓冲器上相对的get()和put()函数,position指针就会随之相应改变。

6.内存映射文件

   前提:内存的访问速度比磁盘高几个数量级,但是基本的IO操作是直接调用native方法获得驱动和磁盘交互的,IO速度限制在磁盘速度上

  由此,就有了缓存的思想,将磁盘内容预先缓存在内存上,这样当供大于求的时候IO速度基本就是以内存的访问速度为主,例如BufferedInput/OutputStream等

  而我们知道大多数OS都可以利用虚拟内存实现将一个文件或者文件的一部分映射到内存中,然后,这个文件就可以当作是内存数组一样地访问,我们可以把它看成一种“永久的缓存

  内存映射文件:内存映射文件允许我们创建和修改那些因为太大而不能放入内存的文件,此时就可以假定整个文件都放在内存中,而且可以完全把它当成非常大的数组来访问(随机访问)

import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

public class LargeMappedFiles {

    static int length = 0x8FFFFFF; // 128 MB

    public static void main(String[] args) throws Exception {

        MappedByteBuffer out = new RandomAccessFile("test.dat", "rw").getChannel()
            .map(FileChannel.MapMode.READ_WRITE, 0, length);
        for (int i = 0; i < length; i++)
            out.put((byte) 'x');
        System.out.println("Finished writing");
        for (int i = length / 2; i < length / 2 + 6; i++)
            System.out.println((char) out.get(i));
    }
}

  为了既能写又能读,我们先由RandomAccessFile开始,获得该文件上的通道,然后调用map()产生MappedByteBuffer,这是一种特殊类型的直接缓冲器。注意我们必须指定映射文件的初始位置和映射区域的长度,这意味着我们可以映射某个区域的长度,这意味着我们可以映射某个大文件较小的部分。

  MappedByteBufferByteBuffer继承而来,因此它具有ByteBuffer的所有方法。这里,我们仅仅展示了非常简单的put()get(),但是我们同样可以使用像asCharBuffer()等这样的用法。

  前面那个程序创建的文件为128MB,这可能比操作系统所允许一次载入内存的空间大。但似乎我们可以一次访问到整个文件,因为只有一部分文件放入了内存,文件的其他部分被交换了出去。用这种方式,很大的文件(可达2GB)也可以很容易的修改。注意底层操作系统的文件映射工具是用来最大化地提高性能。

//: io/MappedIO.java
import java.nio.*;
import java.nio.channels.*;
import java.io.*;

public class MappedIO {
  private static int numOfInts = 4000000;
  private static int numOfUbuffInts = 200000;
  private abstract static class Tester {
    private String name;
    public Tester(String name) { this.name = name; }
    public void runTest() {
      System.out.print(name + ": ");
      try {
        long start = System.nanoTime();
        test();
        double duration = System.nanoTime() - start;
        System.out.format("%.2f\n", duration/1.0e9);
      } catch(IOException e) {
        throw new RuntimeException(e);
      }
    }
    public abstract void test() throws IOException;
  }
  private static Tester[] tests = {
    new Tester("Stream Write") {
      public void test() throws IOException {
        DataOutputStream dos = new DataOutputStream(
          new BufferedOutputStream(
            new FileOutputStream(new File("temp.tmp"))));
        for(int i = 0; i < numOfInts; i++)
          dos.writeInt(i);
        dos.close();
      }
    },
    new Tester("Mapped Write") {
      public void test() throws IOException {
        FileChannel fc =
          new RandomAccessFile("temp.tmp", "rw")
          .getChannel();
        IntBuffer ib = fc.map(
          FileChannel.MapMode.READ_WRITE, 0, fc.size())
          .asIntBuffer();
        for(int i = 0; i < numOfInts; i++)
          ib.put(i);
        fc.close();
      }
    },
    new Tester("Stream Read") {
      public void test() throws IOException {
        DataInputStream dis = new DataInputStream(
          new BufferedInputStream(
            new FileInputStream("temp.tmp")));
        for(int i = 0; i < numOfInts; i++)
          dis.readInt();
        dis.close();
      }
    },
    new Tester("Mapped Read") {
      public void test() throws IOException {
        FileChannel fc = new FileInputStream(
          new File("temp.tmp")).getChannel();
        IntBuffer ib = fc.map(
          FileChannel.MapMode.READ_ONLY, 0, fc.size())
          .asIntBuffer();
        while(ib.hasRemaining())
          ib.get();
        fc.close();
      }
    },
    new Tester("Stream Read/Write") {
      public void test() throws IOException {
        RandomAccessFile raf = new RandomAccessFile(
          new File("temp.tmp"), "rw");
        raf.writeInt(1);
        for(int i = 0; i < numOfUbuffInts; i++) {
          raf.seek(raf.length() - 4);
          raf.writeInt(raf.readInt());
        }
        raf.close();
      }
    },
    new Tester("Mapped Read/Write") {
      public void test() throws IOException {
        FileChannel fc = new RandomAccessFile(
          new File("temp.tmp"), "rw").getChannel();
        IntBuffer ib = fc.map(
          FileChannel.MapMode.READ_WRITE, 0, fc.size())
          .asIntBuffer();
        ib.put(0);
        for(int i = 1; i < numOfUbuffInts; i++)
          ib.put(ib.get(i - 1));
        fc.close();
      }
    }
  };
  public static void main(String[] args) {
    for(Tester test : tests)
      test.runTest();
  }
} /* Output: (90% match)
Stream Write: 0.56
Mapped Write: 0.12
Stream Read: 0.80
Mapped Read: 0.07
Stream Read/Write: 5.32
Mapped Read/Write: 0.02
*///:~

  尽管“映射写”似乎要用到FIleOutputStream,但是映射文件中的所有输出必须使用RandomAccessFile

7.文件加锁

  JDK1.4引入了文件加锁机制,它允许我们同步访问某个作为共享资源的文件。不过,竞争同一文件的两个线程可能在不同的Java虚拟机上,或者一个是Java线程,另一个是操作系统中其他的某个本地线程。文件加锁对其他的操作系统进程是可见的,因为Java的文件加锁直接映射到了本地操作系统的加锁工具。

//: io/FileLocking.java
import java.nio.channels.*;
import java.util.concurrent.*;
import java.io.*;

public class FileLocking {
  public static void main(String[] args) throws Exception {
    FileOutputStream fos= new FileOutputStream("file.txt");
    FileLock fl = fos.getChannel().tryLock();
    if(fl != null) {
      System.out.println("Locked File");
      TimeUnit.MILLISECONDS.sleep(100);
      fl.release();
      System.out.println("Released Lock");
    }
    fos.close();
  }
} /* Output:
Locked File
Released Lock
*///:~

  通过FileChannel调用tryLock()lock(),就可以获得整个文件的FileLockSocketChannelDatagramChannelServerSocketChannel不需要加锁,因为他们是从单进程实体继承而来的,我们通常不在两个进程之间共享网络socket)。tryLock是非阻塞式的,它设法获取锁,但是如果不能获得(当其他一些进程已经持有相同的锁,并且不共享时),它将直接从方法调用返回。lock()则是阻塞式的,它要阻塞进程直至锁可以获得,或调用lock()的线程中断,或调用lock()的通道关闭。使用FileChannel.release()可以释放锁。也可以使用如下方法对文件的一部分上锁:

tryLock(long position, long size, boolean shared)

或者

lock(long position, long size, boolean shared)

其中,加锁的区域由size-position决定,第三个参数指定是否是共享锁。
对独占锁或者共享锁的支持必须由底层操作系统提供。如果操纵系统不支持共享锁并为每一个请求都创建一个锁,那么它就会使用独占锁。锁的类型可以通过FileLock.isShared()进行查询。

对映射文件部分加锁

  如前所述,文件映射通常应用于极大的文件。我们可能需要对这种巨大的文件进行部分加锁,以便其他进程可以修改文件中未被加锁的部分。例如,数据库库就是这样,因此多个用户可以同时访问它。

//: io/LockingMappedFiles.java
// Locking portions of a mapped file.
// {RunByHand}
import java.nio.*;
import java.nio.channels.*;
import java.io.*;

public class LockingMappedFiles {
  static final int LENGTH = 0x8FFFFFF; // 128 MB
  static FileChannel fc;
  public static void main(String[] args) throws Exception {
    fc =
      new RandomAccessFile("test.dat", "rw").getChannel();
    MappedByteBuffer out =
      fc.map(FileChannel.MapMode.READ_WRITE, 0, LENGTH);
    for(int i = 0; i < LENGTH; i++)
      out.put((byte)'x');
    new LockAndModify(out, 0, 0 + LENGTH/3);
    new LockAndModify(out, LENGTH/2, LENGTH/2 + LENGTH/4);
  }
  private static class LockAndModify extends Thread {
    private ByteBuffer buff;
    private int start, end;
    LockAndModify(ByteBuffer mbb, int start, int end) {
      this.start = start;
      this.end = end;
      mbb.limit(end);
      mbb.position(start);
      buff = mbb.slice();
      start();
    }
    public void run() {
      try {
        // Exclusive lock with no overlap:
        FileLock fl = fc.lock(start, end, false);
        System.out.println("Locked: "+ start +" to "+ end);
        // Perform modification:
        while(buff.position() < buff.limit() - 1)
          buff.put((byte)(buff.get() + 1));
        fl.release();
        System.out.println("Released: "+start+" to "+ end);
      } catch(IOException e) {
        throw new RuntimeException(e);
      }
    }
  }
} ///:~

   线程类LockAndModify创建了缓冲区和用于修改的slice(),然后在run()中,获得文件通道上的锁(我们不能获得缓冲器上的锁,只能是通道上的)。lock()调用类似于获得一个对象上的线程锁——我们现在处在“临界区”,既对该部分的文件具有独占访问权。

相关文章

    暂无相关文章
相关栏目:

用户点评