Java NIO Tutorial简记

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/guanhang89/article/details/80644620

Java NIO

这篇博客内容源自Java NIO Tutorial,貌似有对应的中文翻译。

Tutorial

Java NIO是除了JAVA IO 和 Java网络编程之外的另一种可选的IO API,它有着与众不同的工作方式。

Channels、Buffers

在传统IO中,要不使用字节流,要不使用字符流,在NIO中是通过channel和buffer工作的。数据总是从channel读取到buffer中,或者从buffer写到channel中。

非阻塞

NIO能够工作在非阻塞的方式。例如,一个线程可以从一个channel读取数据到buffer中,同时这个channel可以从buffer中读取数据以及该线程可以干其他的事情,一旦数据读入了buffer,线程可以进行后续的处理。对于写数据到channel中也是一样

Selectors

selector能够管理多个事件的channel,比如连接的打开和到达。因此,用一个线程就能管理多个channel。

概述

Java NIO主要包括这个三个部分:

Channels

Buffers

Selectors

Channel和Buffer

一般来说,NIO中的IO都是从channel开始,channel和流类似。可以从channel中读取数据到buffer中,或者从buffer写入数据到channel中。Channel的主要实现类有:

FileChannel:file IO

DatagramChannel :UDP

SocketChannel:TCP

ServerSocketChannel:TCP

Buffer的主要实现类有:

ByteBuffer

CharBuffer

DoubleBuffer

FloatBuffer

IntBuffer

LongBuffer

ShortBuffer

基本涵盖了所有的基本类型

Selectors

一个Selector可以通过一个线程来管理多个channel。如果你的应用有多个打开的连接,但是每个连接的流量又很低,使用selector就很方便。使用时,要注册Channel到Selector,然后调用select方法,这个方法会阻塞直到有一个注册过的事件准备好了,一旦这个方法返回,线程可以继续处理这些事件。

Channel

NIO的channel和流的区别

既可以从Channel中读,也可以写入Channel,但是流一般是单向的

Channel支持异步读和写

Channel总是读取数据到Buffer,或者从Buffer写入流

案例

public class FileChannleDemo {
    public static void main(String[] args) throws IOException {
        RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
        //后去channel
        FileChannel inChannel = aFile.getChannel();
        ByteBuffer buf = ByteBuffer.allocate(49);
        int bytesRead = inChannel.read(buf);
        while (bytesRead != -1) {
            System.out.println("Read " + bytesRead);
            //使buffer准备被读入
            buf.flip();
            while (buf.hasRemaining()) {
                System.out.println((char) buf.get());
            }
            //使buffer准备被写入
            buf.clear();
            bytesRead = inChannel.read(buf);
        }
        aFile.close();
    }
}

Buffer

Buffer是用来和Channel交互的。使用Buffer读或者写数据一般需要四个步骤:

  1. 写数据到Buffer
  2. 调用flip方法
  3. 从Buffer中读数据
  4. 调用clear()或者compact

当往buffer写入数据时,buffer会记录你写入的数据量,一旦你需要读取数据时,调用flip将buffer从写入模式转为读取模式。在读取模式中,buffer会让已经写入到buffer中的数据被读取。

一旦读取了所有的数据后,需要clear buffer,让它能够再被写入,可以通过两个方法实现:clear()和compact()。clear 会清除所有的buffer,compact只会清除已经读取的数据,任何没有被读取的数据会被移动到buffer的开头,然后数据继续在未读取的数据后写入。

Buffer结构

buffer实际上是一个内存块,在Buffer的数据结构中,有三个重要的属性:

capacity

position

limit

position和limit的含义在读写模式中是不同的,但是capacity一直都是值buffer的大小

Position

position表示写入数据的位置,一开始的position处于0,当写入一个数据后,position就会增加,position最大能够到capacity-1。当你从buffer中读取数据时其实也是读position,但调用flip进入读取模式后,position会重设为0,每读取一个字节,position就会增加。

Limit

在写模式中,buffer的limit表示最多能够写入多少数据,因此limit和capacity是相等的

在读模式中,limit表示能够从buffer中读取多少数据

所以当调用flip后,limit会设置成写模式时的写入位置,也就是说尽可能多的读取之前缩写的内容

分配Buffer的空间

使用Buffer对象之前,必须先分配空间:

ByteBuffer buf = ByteBuffer.allocate(48);

Buffer写入

有两种Buffer写入的途径:

  1. 从Channel写入数据到Buffer
  2. 通过put方法写入数据到Buffer

从channel写入:

int bytesWritten = inChannel.write(buf);

从buffer中读取数据:

byte aByte = buf.get()

当然也有其他形式的get方法,比如能够从指定位置读取buffer

rewind()

当调用Buffer.rewind()后,position会置为0,这时候又能重新读取数据,但是limit不会变,也就是能读取的最大的数据量是不变的

clear()和compact()

当从buffer读完数据后,需要让buffer准备再写入,这是可以调用clear和compact方法。

调用clear,position会置为0,limit会置为capacity,这样相当于清空了buffer,但是实际上buffer中的数据没有清除。不过由于相关的标记都已经清除,想继续读取没有读取过的数据是不行的。

解决办法就是调用compact,和clear不同,compact会从头复制这些没有读取的数据,然后position会指向最后一个没有读取的元素后面,因此这些未读的数据不会丢失。

mark和reset

标记一个给定的position可以用mark,然后调用reset让position重新到mark标记的位置上:

buffer.mark();
buffer.reset();

equals()和compareTo()

两个buffer如果equal,则:

buffer的类型相同

buffer剩下的(没有被读取的)字节、字符等数量一样多

所有剩下的字节、字符都是一致的

compareTo的比较规则:一个buffer比另一个buffer“小”是指:

buffer出现第一个能够相对应的元素比另一个小

所有能比较的元素都相等,但是第一个buffer的元素个数更少

Scatter/Gather

scatter以及gather的概念分别用在从channel中读取数据和往channel里写入数据。

分散的读(scatter)是指将数据读到多个buffer,集中的写(gather)是指将多个buffer中的数据写入一个channel中。

分散的读

案例代码:

ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
//构造ByteBuffer数组
ByteBuffer[] bufferArray = {header,body};
//调用read会往buffer中写数据,按ByteBuffer数组的顺序一个一个写
channel.read(bufferArray);

集中的写

ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
ByteBuffer[] bufferArray = {header, body};
//从buffer中读取数据写入channel,写入的内容是position和limit之间的数据
channel.write(bufferArray);

Channel到Channel Transfers

NIO中一个channel的数据可以从另一个channel中转换过来,主要使用两个方法:

transferFrom()

transfer()

TransferFrom

transferFrom是从源channel的数据转为目的channel的数据

RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt","rw");
FileChannel fromChannel = formFile.getChannel();

RandomAccessFile toFile = new RandomAccessFile("toFile.txt","rw");
FileChannel toChannel = toFile.getChannel();

long position = 0;
long count = fromChannel.size();
//position表示toChannel中写入的其实位置,count表示写入的数量
toChannel.transferFrom(fromChannel,position,count);

TransferTo

RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
FileChannel      fromChannel = fromFile.getChannel();

RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
FileChannel      toChannel = toFile.getChannel();

long position = 0;
long count    = fromChannel.size();
//pistion和count是指fromChannel的
fromChannel.transferTo(position, count, toChannel);

Selector

使用Selector能够通过一个线程来管理多个channel

创建一个Selector

Selector selector = Selector.open();

注册channel到selector

//设置为非阻塞模式,这意味着不能使用Selector来管理FileChannel,因为FileChannel不能转为非阻塞模式
//SocketChannel可以
channel.configureBlocking(false);

SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

register的第二个参数表示对channel的什么事件感兴趣,有四种事件类型:

Connect:表示connect ready

Accept:表示accept ready

Read:表示read ready

Write:表示write ready

如果不止对一个事件感兴趣可以用这种写法:

int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;   

SelectionKeys

register方法返回一个SelectionKey,该对象主要有下面几个属性:

interest set

ready set

The Channel

The Selector

An attached object

Interest set

通过interest set能够找出特定事件:

int interestSet = selectionKey.interestOps();

boolean isInterestedInAccept  = (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
boolean isInterestedInConnect = (interestSet & SelectionKey.OP_CONNECT) == SelectionKey.OP_CONNECT;
boolean isInterestedInRead = (interestSet & SelectionKey.OP_READ) == SelectionKey.OP_READ;
boolean isInterestedInWrite = (interestSet & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE;

Ready Set

表示channel已经ready的操作,一般用在selection之后。

int readySet = selectionKey.readyOps();

也可以和interest set一样来判断某个事件或者操作是否准备好,也可以调用现成的方法:

selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();

Channel+Selector

获取Channel和Selctor

Channel  channel  = selectionKey.channel();

Selector selector = selectionKey.selector();

Attaching Objects

selectionKey.attach(theObject);

Object attachedObj = selectionKey.attachment(); 

也可以在register的时候attach

SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);

使用Selector选择Channel

当注册多个Selector时,可以调用select方法返回感兴趣的已经ready的channel,有三个select方法的变形:

select():会阻塞知道有一个channel准备好了

select(long timeout):也会阻塞但是会有一个超时时间

selectNow():不会阻塞,无论是否有时间准备好都返回

上面三个方法的返回值都是int,表示已经准备好的channel的个数(自从上一次调用select

方法以来)

selectedKeys()

通过selectedKeys可以访问已经准备好的channel

Set<SelectionKey> selectedKeys = selector.selectedKeys();  

当我们调用register的时候会返回一个SelectionKey,它表示已经注册到selector的channel。可以通过selectedKeys来遍历:

Set<SelectionKey> selectedKeys = selector.selectedKeys();

Iterator<SelectionKey> keyIterator = selectedKeys.iterator();

while(keyIterator.hasNext()) {

    SelectionKey key = keyIterator.next();

    if(key.isAcceptable()) {
        // a connection was accepted by a ServerSocketChannel.

    } else if (key.isConnectable()) {
        // a connection was established with a remote server.

    } else if (key.isReadable()) {
        // a channel is ready for reading

    } else if (key.isWritable()) {
        // a channel is ready for writing
    }
    //注意,需要手动remove
    keyIterator.remove();
}

WakeUp

在调用select方法出现阻塞后,我们可以调用WakeUp来唤醒并让select立即返回。

close

当使用完Selector后,可以调用close方法,这将会关闭Selector以及是注册的SelectionKey失效

完整的案例:

Selector selector = Selector.open();
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
while(true) {
  int readyChannels = selector.select();
  if(readyChannels == 0) continue;
  Set<SelectionKey> selectedKeys = selector.selectedKeys();
  Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
  while(keyIterator.hasNext()) {
    SelectionKey key = keyIterator.next();
    if(key.isAcceptable()) {
        // a connection was accepted by a ServerSocketChannel.
    } else if (key.isConnectable()) {
        // a connection was established with a remote server.
    } else if (key.isReadable()) {
        // a channel is ready for reading
    } else if (key.isWritable()) {
        // a channel is ready for writing
    }
    keyIterator.remove();
  }
}

FileChannel

FileChannel是和File关联的,使用FileChannel能够从文件中读取数据以及写数据到文件。注意FileChannel只能工作在阻塞模式,不能工作在非阻塞模式。

打开一个FileChannel

不能直接打开FileChannel需要通过InputStream、OutputStream、或者RandomAccessFile来打开。

RandomAccessFile aFile     = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel      inChannel = aFile.getChannel();

读数据

ByteBuffer buf = ByteBuffer.allocate(48);
//返回读取的字节数,-1表示到了文件末尾
int bytesRead = inChannel.read(buf);

写数据

String newData = "New String to write to file..." + System.currentTimeMillis();

ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
//写文件对于buffer来说是读
buf.flip();

while(buf.hasRemaining()) {
    channel.write(buf);
}

关闭FileChannel

使用FileChannel后必须要关闭

channel.close();

FileChannel的Position

写数据和读数据也是有Position的,通过调用position可以获取当前的position,也可以调用position(long pos)设置当前的position:

long pos channel.position();

channel.position(pos +123);

如果设置的position超过了文件的末尾,在读数据的时候会返回-1,写数据的时候会扩展channel,从position开始写,这会造成”文件空洞“,也就是磁盘上的文件中间有字节”间隙“。

FileChannel size

long fileSize = channel.size();   

FileChannel Truncate

可以调用FileChannel.truncate()删除一个文件,并且可以定义截取的文件的大小:

channel.truncate(1024);

FileChannel Force

调用force会在写channel的时候,强制刷新缓存在内存中的数据到磁盘中

channel.force(true);

SocketChannel

SocketChannel是连接到TCP的channel,有两种创建SocketChannel:

  1. 打开一个SocketChannel,连接到服务器
  2. 当有连接到SeverSocketChannel时,可以获取到SocketChannel

打开一个SocketChannel

SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));

关闭一个SocketChannel

使用完需要关闭

socketChannel.close();

从SocketChannel中读数据:

ByteBuffer buf = ByteBuffer.allocate(48);

int bytesRead = socketChannel.read(buf);

向SocketChannel中写数据:

String newData = "New String to write to file..." + System.currentTimeMillis();

ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());

buf.flip();

while(buf.hasRemaining()) {
    channel.write(buf);
}

非阻塞模式

非阻塞的connect

socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));

while(! socketChannel.finishConnect() ){
    //轮询 
}

非阻塞的write、read

和connect一样,非阻塞的write和read会立即返回,但是可能并没有写数据或读数据,对于写,可以通过hasRemaining()来判断是否写完,对于读可以通过返回的int型值来判断是否读取到数据。

ServerSocketChannel

ServerSocketChannel可以监听TCP连接,例如:

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

serverSocketChannel.socket().bind(new InetSocketAddress(9999));
//监听连接
while(true){
    SocketChannel socketChannel =
            serverSocketChannel.accept();

    //do something with socketChannel...
}

打开一个ServerSocketChannel

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

关闭

serverSocketChannel.close();

非阻塞模式

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

serverSocketChannel.socket().bind(new InetSocketAddress(9999));
serverSocketChannel.configureBlocking(false);

while(true){
    SocketChannel socketChannel =
            serverSocketChannel.accept();
    //非阻塞模式会立即返回,可能返回为null
    if(socketChannel != null){
        //do something with socketChannel...
        }
}

DatagramChannel

DatagramChannel能够收发UDP数据包。

打开

DatagramChannel channel = DatagramChannel.open();
channel.socket().bind(new InetSocketAddress(9999));

接收数据

ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
//从接收的数据包中拷贝数据到buf,如果数据量比buf大,剩余的数据将会被丢失
channel.receive(buf);

发送数据

String newData = "New String to write to file..."
                    + System.currentTimeMillis();

ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
//即使该服务器没有监听80端口,也不影响发送是否成功,因为UDP是无连接的
int bytesSent = channel.send(buf, new InetSocketAddress("jenkov.com", 80));

连接到特定的地址

由于UDP是无连接的,因此实际不能连接特定的网络地址,因此connect实际不创建连接,而是锁定DatagramChannel,只能和该地址交互

channel.connect(new InetSocketAddress("jenkov.com", 80)); 
int bytesRead = channel.read(buf); 
int bytesWritten = channel.write(buf);

Java NIO Pipe

NIO pipe是两个线程之间单向的数据连接,一个Pipe有一个源channel和一个宿channel,可以写入数据到宿channel,然后源channel可以从宿channel读取,示意如下:

Tread A——>Sink Channel—>Source Channel—>Thread B

创建

Pipe pipe = Pipe.open();

写入pipe

需要先获取sink channel

Pipe.SinkChannel sinkChannel = pipe.sink();

然后再写入:

String newData = "New String to write to file..." + System.currentTimeMillis();

ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());

buf.flip();

while(buf.hasRemaining()) {
    sinkChannel.write(buf);
}

读取Pipe

Pipe.SourceChannel sourceChannel = pipe.source();
ByteBuffer buf = ByteBuffer.allocate(48);

int bytesRead = inChannel.read(buf);

NIO Path

NIO Path是一个接口,是java7新增的,他表示系统中的一个路径,能够指向文件或者目录,可以是相对的,也可以是绝对的路径

创建一个Path实例

public class PathExample {
    public static void main(String[] args) {
        Path path = Paths.get("c:\\data\\myfile.txt");
    }
}

创建绝对路径

Path path = Paths.get("c:\\data\\myfile.txt");

创建相对路径

Path projects = Paths.get("d:\\data", "projects");
//第一个参数是基础路径,第二个参数是相对路径
Path file     = Paths.get("d:\\data", "projects\\a-project\\myfile.txt");
//也可以用.和..
Path currentDir = Paths.get(".");
Path parentDir = Paths.get("..");
System.out.println(currentDir.toAbsolutePath());
//.放在中间就表示当前.指的目录,实际没有意义
Path currentDir = Paths.get("d:\\data\\projects\.\a-project");
//..表示是当前目录的上一级
String path = "d:\\data\\projects\\a-project\\..\\another-project";
Path parentDir2 = Paths.get(path);

Path path1 = Paths.get("d:\\data\\projects", ".\\a-project");
Path path2 = Paths.get("d:\\data\\projects\\a-project",
                       "..\\another-project");

Path.normalize()

normalize能够标准化path,也就是能够转义.和..,并返回实际的路径

String originalPath =
        "d:\\data\\projects\\a-project\\..\\another-project";
//打印原始的路径
Path path1 = Paths.get(originalPath);
System.out.println("path1 = " + path1);
//打印归一化后的路径
Path path2 = path1.normalize();
System.out.println("path2 = " + path2);

NIO Files

常见的方法:

Path path = Paths.get("data/logging.properties");
//File.exists判断File是否存在,第二个参数表示不包括软连接
boolean pathExists =Files.exists(path,new LinkOption[]{ LinkOption.NOFOLLOW_LINKS});

//创建目录
Path path = Paths.get("data/subdir");
try {
    Path newDir = Files.createDirectory(path);
} catch(FileAlreadyExistsException e){
    // the directory already exists.
} catch (IOException e) {
    //something else went wrong
    e.printStackTrace();
}

//复制文件
Path sourcePath      = Paths.get("data/logging.properties");
Path destinationPath = Paths.get("data/logging-copy.properties");

try {
    Files.copy(sourcePath, destinationPath);
} catch(FileAlreadyExistsException e) {
    //destination file already exists
} catch (IOException e) {
    //something else went wrong
    e.printStackTrace();
}

//复制并覆盖文件
Path sourcePath      = Paths.get("data/logging.properties");
Path destinationPath = Paths.get("data/logging-copy.properties");

try {
    Files.copy(sourcePath, destinationPath,
            StandardCopyOption.REPLACE_EXISTING);
} catch(FileAlreadyExistsException e) {
    //destination file already exists
} catch (IOException e) {
    //something else went wrong
    e.printStackTrace();
}

//移动一个文件
Path sourcePath      = Paths.get("data/logging-copy.properties");
Path destinationPath = Paths.get("data/subdir/logging-moved.properties");

try {
    Files.move(sourcePath, destinationPath,
            StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
    //moving file failed.
    e.printStackTrace();
}

//删除文件
Path path = Paths.get("data/subdir/logging-moved.properties");

try {
    Files.delete(path);
} catch (IOException e) {
    //deleting file failed
    e.printStackTrace();
}

//遍历文件的路径,第二个参数要继承FileVisitor接口,他有一个简单的实现类:SimpleFileVisitor
/**
    FileVisitResult是一个枚举值:
    CONTINUE
    TERMINATE
    SKIP_SIBLINGS
    SKIP_SUBTREE
**/
Files.walkFileTree(path, new FileVisitor<Path>() {
  @Override
  public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
    //访问目录之前会调用
    System.out.println("pre visit dir:" + dir);
    return FileVisitResult.CONTINUE;
  }

  @Override
  public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
    //访问文件的时候调用
    System.out.println("visit file: " + file);
    return FileVisitResult.CONTINUE;
  }

  @Override
  public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
    System.out.println("visit file failed: " + file);
    return FileVisitResult.CONTINUE;
  }

  @Override
  public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
    //访问目录之后会调用
    System.out.println("post visit directory: " + dir);
    return FileVisitResult.CONTINUE;
  }
});

//使用walkFileTree寻找一个名为README.txt的文件
Path rootPath = Paths.get("data");
String fileToFind = File.separator + "README.txt";

try {
  Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() {

    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
      String fileString = file.toAbsolutePath().toString();
      //System.out.println("pathString = " + fileString);

      if(fileString.endsWith(fileToFind)){
        System.out.println("file found at path: " + file.toAbsolutePath());
        return FileVisitResult.TERMINATE;
      }
      return FileVisitResult.CONTINUE;
    }
  });
} catch(IOException e){
    e.printStackTrace();
}

//使用walkFileTree递归删除一个文件
Path rootPath = Paths.get("data/to-delete");

try {
  Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() {
    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
      System.out.println("delete file: " + file.toString());
      Files.delete(file);
      return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
      Files.delete(dir);
      System.out.println("delete dir: " + dir.toString());
      return FileVisitResult.CONTINUE;
    }
  });
} catch(IOException e){
  e.printStackTrace();
}

AsynchronousFileChannel

异步的文件channel允许异步的读和写channel

创建

Path path = Paths.get("data/test.xml");

AsynchronousFileChannel fileChannel =
    AsynchronousFileChannel.open(path, StandardOpenOption.READ);

读Channel

有两种读Channel的方法,一种是返回Future

Future<Integer> operation = fileChannel.read(buffer, 0);

案例:

AsynchronousFileChannel fileChannel = 
    AsynchronousFileChannel.open(path, StandardOpenOption.READ);

ByteBuffer buffer = ByteBuffer.allocate(1024);
long position = 0;

Future<Integer> operation = fileChannel.read(buffer, position);
//这种方式使用CPU效率不高
while(!operation.isDone());

buffer.flip();
byte[] data = new byte[buffer.limit()];
buffer.get(data);
System.out.println(new String(data));
buffer.clear();

还有一种是传递一个回调方法,也就是继承CompletionHandler接口

fileChannel.read(buffer, position, buffer, new CompletionHandler<Integer, ByteBuffer>() {
    //result表示借字节个数,attachment是绑定的那个buffer
    @Override
    public void completed(Integer result, ByteBuffer attachment) {
        System.out.println("result = " + result);

        attachment.flip();
        byte[] data = new byte[attachment.limit()];
        attachment.get(data);
        System.out.println(new String(data));
        attachment.clear();
    }
    //失败的时候会调用
    @Override
    public void failed(Throwable exc, ByteBuffer attachment) {

    }
});

写Channel

和读一样,写也有两种方法

第一种同样是通过Future模式:

Path path = Paths.get("data/test-write.txt");
AsynchronousFileChannel fileChannel = 
    AsynchronousFileChannel.open(path, StandardOpenOption.WRITE);

ByteBuffer buffer = ByteBuffer.allocate(1024);
long position = 0;

buffer.put("test data".getBytes());
buffer.flip();

Future<Integer> operation = fileChannel.write(buffer, position);
buffer.clear();

while(!operation.isDone());

System.out.println("Write done");

第二种也是通过ComplettionHandler接口

Path path = Paths.get("data/test-write.txt");
if(!Files.exists(path)){
    Files.createFile(path);
}
AsynchronousFileChannel fileChannel = 
    AsynchronousFileChannel.open(path, StandardOpenOption.WRITE);

ByteBuffer buffer = ByteBuffer.allocate(1024);
long position = 0;

buffer.put("test data".getBytes());
buffer.flip();

fileChannel.write(buffer, position, buffer, new CompletionHandler<Integer, ByteBuffer>() {

    @Override
    public void completed(Integer result, ByteBuffer attachment) {
        System.out.println("bytes written: " + result);
    }

    @Override
    public void failed(Throwable exc, ByteBuffer attachment) {
        System.out.println("Write failed");
        exc.printStackTrace();
    }
});

Java NIO和IO的比较

NIO和普通IO的主要几个区别:

面向Buffer和面向流

普通的Java IO是面向流的,它没有缓存,并且不能够改变读流的位置。NIO是面向buffer的,我们可以调整读Buffer的位置,因此更加灵活。

阻塞和非阻塞

Java IO是阻塞的,这意味着,在调用read或者write时,如果没有数据或者还没有完成写入,线程会进行阻塞,不能做其他的事情。NIO是非阻塞的,比如读channel,只会获取当前能够得到的,如果没有数据能够读取也不会阻塞,但是由于这个问题NIO需要不停的轮询判断。

Selectors

NIO有Selectors,允许一个线程管理多个channel

#### 非阻塞服务器的设计实现

猜你喜欢

转载自blog.csdn.net/guanhang89/article/details/80644620