Java--NIO之通道(文件通道)

  上篇博文http://blog.xiaoxiaomo.com/2016/04/23/Java-NIO之通道/只是泛泛地讨论通道,现在我们将具体讨论文件通道(socket 通道将在下篇博客讨论)。FileChannel 类可以实现常用的 read,write 以及 scatter/gather 操作,同时它也提供了很多专用于文件的新方法。这些方法中的许多都是我们所熟悉的文件操作。

FileChannel 类层次结构

文件通道FileChannel

上篇博文知道,一个FileChannel对象却只能通过在一个打开的RandomAccessFile、FileInputStream或FileOutputStream对象上调用getChannel()方法来获取,开发者不能直接创建一个FileChannel。下面来看一下 FileChannel 接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package java.nio.channels;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.spi.AbstractInterruptibleChannel;
import java.nio.file.*;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.spi.*;
import java.util.Set;
import java.util.HashSet;
import java.util.Collections;

public abstract class FileChannel
extends AbstractInterruptibleChannel
implements SeekableByteChannel, GatheringByteChannel, ScatteringByteChannel{

protected FileChannel() {}
public static FileChannel open(Path path,Set<? extends OpenOption> options,FileAttribute<?>... attrs)
throws IOException{

FileSystemProvider provider = path.getFileSystem().provider();
return provider.newFileChannel(path, options, attrs);
}
private static final FileAttribute<?>[] NO_ATTRIBUTES = new FileAttribute[0];
public static FileChannel open(Path path, OpenOption... options)throws IOException{
Set<OpenOption> set = new HashSet<OpenOption>(options.length);
Collections.addAll(set, options);
return open(path, set, NO_ATTRIBUTES);
}

// -- Channel operations --
public abstract int read(ByteBuffer dst) throws IOException;
public abstract long read(ByteBuffer[] dsts, int offset, int length) throws IOException;
public final long read(ByteBuffer[] dsts) throws IOException {
return read(dsts, 0, dsts.length);
}
public abstract int write(ByteBuffer src) throws IOException;
public abstract long write(ByteBuffer[] srcs, int offset, int length) throws IOException;
public final long write(ByteBuffer[] srcs) throws IOException {
return write(srcs, 0, srcs.length);
}

// -- Other operations --
public abstract long position() throws IOException;
public abstract FileChannel position(long newPosition) throws IOException;
public abstract long size() throws IOException;
public abstract FileChannel truncate(long size) throws IOException;
public abstract void force(boolean metaData) throws IOException;
public abstract long transferTo(
long position, long count,WritableByteChannel target)throws IOException
;

public abstract long transferFrom(
ReadableByteChannel src,long position, long count) throws IOException
;

public abstract int read(ByteBuffer dst, long position) throws IOException;
public abstract int write(ByteBuffer src, long position) throws IOException;

// -- Memory-mapped buffers --
public static class MapMode {
public static final MapMode READ_ONLY = new MapMode("READ_ONLY");
public static final MapMode READ_WRITE = new MapMode("READ_WRITE");
public static final MapMode PRIVATE = new MapMode("PRIVATE");
private final String name;
//....
}
public abstract MappedByteBuffer map(MapMode mode,long position, long size)throws IOException;

// -- Locks --
public abstract FileLock lock(long position, long size, boolean shared)throws IOException;
public final FileLock lock() throws IOException {
return lock(0L, Long.MAX_VALUE, false);
}
public abstract FileLock tryLock(long position, long size, boolean shared)throws IOException;
public final FileLock tryLock() throws IOException {
return tryLock(0L, Long.MAX_VALUE, false);
}
}

  1. 文件通道总是阻塞式的,因此不能被置于非阻塞模式下。
  2. FileChannel对象是线程安全的(thread-safe),多个进程可以在同一个实例上并发调用方法而不会引起任何问题,不过并非所有的操作都是多线程的(multithreaded)。影响通道位置或者影响文件的操作都是单线程的(single-threaded),如果有一个线程已经在执行会影响通道位置或文件大小的操作,那么其他尝试进行此类操作之一的线程必须等待,并发行为也会受到底层操作系统或文件系统的影响。

读/写数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.xxo.momo.nio;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
* 读/写数据
* Created by xiaoxiaomo on 2016/4/14.
*/

public class Channel01 {

public static void main(String[] args) throws IOException {
//1. 定义通道,并打开
FileChannel inChannel = new RandomAccessFile("abc.txt", "rw").getChannel();

//2. 缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);

//3. 读写数据
// 从inChannel读取的数据会读到buf中
int bytesRead ;
while ( (bytesRead = inChannel.read(buf)) != -1) {
System.out.println("=====Read " + bytesRead);

//读模式下,可以读取之前写入到buffer的所有数据
buf.flip(); //反转此缓冲区。将Buffer从写模式切换到读模式

while(buf.hasRemaining()){ //告知在当前位置和限制之间是否有元素。
System.out.print((char) buf.get());
}

buf.clear(); //清除此缓冲区。
}

//4. 关闭流
inChannel.close();
}
}

文件空洞

  1. 当磁盘上一个文件的分配空间小于它的文件大小时会出现“文件空洞”
  2. 对于内容稀疏的文件,大多数现代文件系统只为实际写入的数据分配磁盘空间(更准确地说,只为那些写入数据的文件系统页分配空间)。假如数据被写入到文件中非连续的位置上,这将导致文件出现在逻辑上不包含数据的区域(即“空洞”)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    package com.xxo.momo.nio;

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

    /**
    * Created by xiaoxiaomo on 2016/4/25.
    */

    public class Channel04 {
    public static void main (String [] argv) throws IOException {
    //1. 创建一个临时文件和获取一个通道
    File temp = File.createTempFile("xiaoxiaomo", null);
    FileChannel channel = new RandomAccessFile(temp, "rw").getChannel();

    //2. 创建一个缓冲区并存入数据
    ByteBuffer byteBuffer = ByteBuffer.allocateDirect (100);
    putData (0, byteBuffer, channel);//position=0
    putData (10, byteBuffer, channel);//position=10
    putData (20, byteBuffer, channel);//position=20

    // 文件中有两个洞,大小为26
    System.out.println ("temp file '" + temp.getPath( )
    + "', size=" + channel.size( ));
    channel.close();
    }

    //写入数据
    private static void putData(
    int position, ByteBuffer buffer, FileChannel channel)throws IOException
    {

    String string = "xiao" + position;//待写入字符串
    buffer.clear();
    buffer.put (string.getBytes ("US-ASCII"));
    buffer.flip();

    channel.position(position);//设置position
    channel.write(buffer);//写入数据
    }
    }
    //输出结果
    //temp file 'C:\Users\Jason\AppData\Local\Temp\xiaoxiaomo*******.tmp', size=26

FileChannel空洞1

  • 说明

所有空洞都会被“0”填充。该文件是26个字节,有些以“0”表示。FileChannel 位置(position)是从底层的文件描述符获得的,该 position 同时被作为通道引用获取来源的文件对象共享。这也就意味着一个对象对该 position 的更新可以被另一个对象看到。position()获取,position(long pos)方法设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package com.xxo.momo.nio;

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

/**
* Created by xiaoxiaomo on 2016/4/25.
*/

public class Channel03 {
public static void main (String [] argv) throws IOException {
//创建通道
FileChannel channel = new RandomAccessFile("momo.txt", "rw").getChannel();
System.out.println("====="+channel.size());
ByteBuffer buffer = ByteBuffer.allocate(10);

long position = channel.position();
System.out.println("当前position: " + position);

//重新设定位置
channel.position(position+5);//形成文件空洞
buffer.clear();
buffer.put("xiaoxiaomo".getBytes());
buffer.flip();

while(buffer.hasRemaining()) {//写文件
channel.write(buffer);
}

//读取数据
System.out.println("当前position: " + channel.position());
readFile();//读取数据
channel.close();
}

public static void readFile() throws IOException {
FileChannel channel = new RandomAccessFile("momo.txt", "rw").getChannel();
ByteBuffer buffer = ByteBuffer.allocate(10);
//读文件
while(channel.read(buffer) != -1 ) {
buffer.flip();
while(buffer.hasRemaining()) {
System.out.print((char)buffer.get());
}
buffer.clear();
}
channel.close();
}
}
//执行结果:
//=====0
// 当前position: 0
// 当前position: 15
// xiaoxiaomo

FileChannel空洞2

文件锁

  1. 文件锁针对的是文件,所以和通道无关。分为共享锁(shared)独占锁(exclusive)

  2. 如果一个进程下的另一个线程访问一个加独占锁的文件,是可以的。因为这是底层操作系统的实现。只是针对不同的进程。所以,也有可能在某些操作系统上,独占锁只是一个建议锁,非强制性的。关于管道上的锁,有如下操作。

    1
    2
    3
    4
    5
    6
    7
    8
    public abstract class FileChannel extends AbstractChannel 
    implements ByteChannel, GatheringByteChannel, ScatteringByteChannel {

    // This is a partial API listing
    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)
    }

  3. lock可以锁住一个文件的某个区域。而且这个区域可以使比文件还大。如超出文件尾的,新写入的内容也被锁住。lock可能会阻塞,等待前一个lock被释放。

  4. tryLock和lock一样。只是如果当时没有可用锁,就立即返回一个null,而不是阻塞。

  • FileLock 类如下:
1
2
3
4
5
6
7
8
9
public abstract class FileLock {
public final FileChannel channel()
public final long position()
public final long size()
public final boolean isShared()
public final boolean overlaps(long position, long size)
public abstract boolean isValid();

public abstract void release() throws IOException;
}

一旦FileLock对象创建就生效。注意,创建以后,position,是否独占,大小就不能改变了。
FileLock是线程安全的,可以多个线程同时访问一个锁。注意,在你不确定操作系统是否支持独占性时,使用isShared()来判断是否该锁支持共享。
如果你要查看一个感兴趣的区域是否与当前的锁有冲突,可以使用 overlaps,这个函数返回的是当前进程上的,即使是返回为false,也不一定可以获取到FileLock。

最后,使用文件锁,一定要释放。使用try…catch…finally

1
2
3
4
5
6
7
8
FileLock lock = fileChannel.lock()
try {
//...
} catch (IOException) {
//...
} finally {
lock.release()
}

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器