简介
- NIO与原来的IO有同样的作用和目的,但是使用方式完全不同,NIO支持面向缓存区的、基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作。
java NIO 与 IO的主要区别
IO | NIO |
---|---|
面向流 | 面向缓存区 |
阻塞IO(Blocking IO) | 非阻塞IO(Non Blocking IO) |
(无) | 选择器 |
- Channel 负责传输
- Buffer 负责存储
缓存区存取数据的核心方法
put() 写
get() 读
flip() 切换模式
缓存区的四个核心属性
private int mark = -1; //标记,表示当前position的位置。可以通过reset()恢复到mark的位置
private int position = 0; //位置,表示缓存区正在操作数据的位置。
private int limit; //界限,表示缓存区可以操作数据的大小。(limit后数据不能进行读写)
private int capacity; //容量,表示缓存区的最大存储数据的容量。一旦声明不能改变。
0 <= mark <= position <= limit <= capacity
public class test1 {
public static void main(String[] args) {
test2();
}
public static void test2(){
String str = "abcde";
//1. 分配一个指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
System.out.println("----------allocate()---------");
System.out.println(buf.position());//0
System.out.println(buf.limit());//1024
System.out.println(buf.capacity());//1024
//2. 利用put()存入数据到缓冲区中
buf.put(str.getBytes());
System.out.println("----------put()---------");
System.out.println(buf.position());//5
System.out.println(buf.limit());//1024
System.out.println(buf.capacity());//1024
//3.切换读取数据模式
buf.flip();
System.out.println("----------put()---------");
System.out.println(buf.position());//0
System.out.println(buf.limit());//5
System.out.println(buf.capacity());//1024
//4.利用get()
byte[] dst = new byte[buf.limit()];
buf.get(dst);
System.out.println(new String(dst,0,dst.length));
System.out.println("----------get()---------");
System.out.println(buf.position());//5
System.out.println(buf.limit());//5
System.out.println(buf.capacity());//1024
//5.可重复读
buf.rewind();
System.out.println("----------rewind()---------");
System.out.println(buf.position());//0
System.out.println(buf.limit());//5
System.out.println(buf.capacity());//1024
//6.clear():清空缓冲区,但是缓冲区中的数据依然存在,但是出于“被遗忘”状态
buf.clear();
System.out.println("----------clear()---------");
System.out.println(buf.position());//0
System.out.println(buf.limit());//1024
System.out.println(buf.capacity());//1024
//mark:标记,表示记录当前position的位置。可以通过reset()恢复到mark位置
}
}
直接缓冲区和非直接缓冲区
非直接缓存区:allocate()方法分配缓存区,将缓存区建立在JVM的内存中(堆,数组)
直接缓存区:allocateDirect()方法分配直接缓存区,将缓存区建立在物理内存中,可以提高效率
-
非直接缓冲区
-
直接缓冲区
Channel
- Channel表示IO源与目标打开的连接。Channel类似于传统的“流”。只不过Channel本身不能直接访问数据,Channel只能与Buffer进行交互。
- 分类:
FileChannel
-
SelectableChannel
- SocketChannel
- SockeServerChannel
- DatagramChannel
- Pipe.SinkChannel
- Pipe.SourceChannel
- 获取通道
- Java针对支持通道的类提供了getChannel()方法:
- 本地IO:
- FileInputStream/FileOutputStream
- RamdomAccessFile
- 网络IO:
- Socket
- ServerSocket
- DatagramSocket
- 在JDK 1.7中的NIO.2 针对各个通道提供了静态方法open()
- 在JDK 1.7中的NIO.2 的Files工具类的newByteChannel()
- 利用通道完成文件的复制(非直接缓冲区)
public class FileCopy {
public static void main(String[] args) {
test();
}
public static void test(){
FileInputStream fis = null;
FileOutputStream fos = null;
//获取通道
FileChannel inchannel = null;
FileChannel outChannel = null;
try {
fis = new FileInputStream("D:/1.jpg");
fos = new FileOutputStream("D:/2.jpg");
inchannel = fis.getChannel();
outChannel = fos.getChannel();
//分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
//将通道中的数据存入到缓冲区中
while (inchannel.read(buf) != -1){
buf.flip(); //切换到读数据的模式
//将缓冲区的数据写入到通道中
outChannel.write(buf);
buf.clear();//清空缓存区
}
}catch (IOException e){
e.printStackTrace();
}finally{
if(inchannel != null){
try {
inchannel.close();
}catch (IOException e){
e.printStackTrace();
}
}
if(outChannel != null){
try {
outChannel.close();
}catch (IOException e){
e.printStackTrace();
}
}
if(fis != null){
try {
fis.close();
}catch (IOException e){
e.printStackTrace();
}
}
if(fos != null){
try {
fos.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
}
}
- 使用直接缓冲区完成文件的复制(内存映射文件)
public class FileCopy2 {
public static void main(String[] args) {
test3();
}
private static void test3() {
try {
FileChannel inChannel = FileChannel.open(Paths.get("D:/1.jpg"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("D:/3.jpg"), StandardOpenOption.WRITE, StandardOpenOption.READ,
StandardOpenOption.CREATE_NEW);
//内存映射文件
MappedByteBuffer inMappedBuf = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
MappedByteBuffer outMappedBuf = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());
//直接对缓冲区进行数据的读写操作
byte[] dst = new byte[inMappedBuf.limit()];
inMappedBuf.get(dst);
outMappedBuf.put(dst);
} catch (IOException e) {
e.printStackTrace();
}
}
}
分散(Scatter)和聚集(Gather)
- 分散读取(Scattering Reads): 将通道中的数据分散到多个缓冲区中
- 聚集写入(Gathering Writes): 将多个缓冲区中的数据聚集到通道中
public class FileCopy3 {
public static void main(String[] args) {
test4();
}
private static void test4() {
try {
RandomAccessFile raf1 = new RandomAccessFile("D:/3.txt","rw");
//1.获取通道
FileChannel channel1 = raf1.getChannel();
//2.分配指定大小的缓冲区
ByteBuffer buf1 = ByteBuffer.allocate(1);
ByteBuffer buf2 = ByteBuffer.allocate(1024);
//3.分散读取
ByteBuffer[] bufs = {buf1,buf2};
channel1.read(bufs);
for (ByteBuffer byteBuffer : bufs) {
byteBuffer.flip();
}
//聚集写入
RandomAccessFile raf2 = new RandomAccessFile("D:/7.txt","rw");
FileChannel channel2 = raf2.getChannel();
channel2.write(bufs);
} catch (FileNotFoundException e) {
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}
}
}
Selector
- 是SelectableChannel的多路复用器,用于监控SelectableChannel的状态.它是实现NIO非阻塞的关键。
- fileChannel不能切换成非阻塞式模式。
阻塞式网络IO实现
- 客户端
public class FileTest {
public static void main(String[] args) {
client();
}
//客户端
private static void client() {
try {
//1.获取通道
SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("9.236.37.131", 9898));
FileChannel inChannel = FileChannel.open(Paths.get("D:/1.jpg"), StandardOpenOption.READ);
//2.分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
//3.读取本地文件,并发送到服务端
while (inChannel.read(buf) != -1){
buf.flip();
sChannel.write(buf);
buf.clear();
}
//单向关闭自己的输出流,并未关闭连接,不写这个服务器端并不知道传输的图片结束了。一直卡在ssChannel.accept()
sChannel.shutdownOutput();
//接收服务器的反馈
int len = 0;
while ((len = sChannel.read(buf))!=-1){
buf.flip();
System.out.println(new String(buf.array(),0,len));
buf.clear();
}
//4.关闭通道
inChannel.close();
sChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 服务端
public class FileTest2 {
public static void main(String[] args) {
server();
}
//服务端
private static void server() {
try {
//1.获取通道
ServerSocketChannel ssChannel = ServerSocketChannel.open();
FileChannel outChannel = FileChannel.open(Paths.get("D:/10.jpg"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
//2.绑定链接
ssChannel.bind(new InetSocketAddress(9898));
//3.获取客户端连接的通道
SocketChannel sChannel = ssChannel.accept();
//4.分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
//5.接收客户端的数据,并保存到本地
while (sChannel.read(buf)!= -1){
buf.flip();
outChannel.write(buf);
buf.clear();
}
//发送反馈给客户端
buf.put("服务器接收数据成功".getBytes());
buf.flip();
sChannel.write(buf);
//6.关闭通道
sChannel.close();
outChannel.close();
ssChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
非阻塞式网络IO实现
- 客户端
public class NonBlockClient {
public static void main(String[] args) {
client();
}
private static void client() {
try {
//1.获取通道
SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("9.236.37.131", 9898));
//2.切换非阻塞模式
sChannel.configureBlocking(false);
//3.分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
//4.发送数据给服务器
Scanner scan = new Scanner(System.in);
while (scan.hasNext()){
String str = scan.next();
buf.put((new Date().toString() + "\n"+str).getBytes());
buf.flip();
sChannel.write(buf);
buf.clear();
}
//5.关闭通道
sChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 服务端
public class NonBlockServer {
public static void main(String[] args) {
server();
}
private static void server() {
try {
//1.获取通道
ServerSocketChannel ssChannel = ServerSocketChannel.open();
//2.切换非阻塞模式
ssChannel.configureBlocking(false);
//3.绑定链接
ssChannel.bind(new InetSocketAddress(9898));
//4.获取选择器
Selector selector = Selector.open();
//5.将通道注册到选择器上,并且指定“监听接收事件”
//SelectionKey-----OP_ACCEPT,OP_CONNECT,OP_READ,OP_WRITE
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
//6.轮询式的获取选择器上已经“准备就绪”的事件
while (selector.select()>0){
//7.获取当前选择器中所有注册的“选择键(已就绪的监听事件)”
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()){
//8. 获取准备“就绪”的事件
SelectionKey sk = it.next();
//9.判断具体是什么事件准备就绪
if(sk.isAcceptable()){
//10. 若“接收就绪”,获取客户端连接
SocketChannel sChannel = ssChannel.accept();
//11.切换非阻塞模式
sChannel.configureBlocking(false);
//12.将该通道注册到选择器上
sChannel.register(selector,SelectionKey.OP_READ);
}else if(sk.isReadable()){
//13. 获取当前选择器上“读就绪”状态的通道
SocketChannel sChannel = (SocketChannel)sk.channel();
//14. 读取数据
ByteBuffer buf = ByteBuffer.allocate(1024);
int len = 0;
while ((len = sChannel.read(buf))>0){
buf.flip();
System.out.println(new String(buf.array(),0,len));
buf.clear();
}
}
//15.取消选择键SelectionKey
it.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
- DatagramChannel --UDP用法类似
-
Pipe