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读或者写数据一般需要四个步骤:
- 写数据到Buffer
- 调用flip方法
- 从Buffer中读数据
- 调用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写入的途径:
- 从Channel写入数据到Buffer
- 通过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:
- 打开一个SocketChannel,连接到服务器
- 当有连接到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
#### 非阻塞服务器的设计实现