NIO

nio是从jdk1.3版本1开始引入的一个新的io API可以代替标准java的io . nio与原来1的io有同样的作用和目的,但是使用方式完全不相同,NIO支持面向缓冲区的,基于通道的操作.NIO将以更加高效的方式进行文件的读写操作

IONIO
面向流面向缓冲区
阻塞IO非阻塞IO
(无)选择器

简介

通道和缓冲区

java NIO系统的核心在于(Channel)和缓冲区(Buffer)通道表示打开到IO设备的连接.若需要使用NIO系统,需要获取用于连接IO设备的通道以及用于容纳数据的缓冲区.然后操作缓冲区,对数据进行处理

简而言之,Channel负责传输,Buffer负责存储

缓冲区

缓冲区类型

缓冲区在java NIO中负责数据的存取,缓冲区就是数组.用于存储不同类型的数据

根据数据类型不同(boolean除外),提供了相应类型的缓冲区

  • ByteBuffer
  • CharBuffer
  • ShortBuffer
  • IntBuffer
  • LongBuffer
  • FloatBuffer
  • DoubleBuffer

上述缓冲区的管理方式几乎一致,通过allocate()获取缓冲区

核心方法

put():存入数据到缓冲区中

get():获取缓冲区中的数据

四个核心属性

capacity:容量,表示缓冲区最大存储数据的容量.一旦声明不能改变

limit:界限,表示缓冲区中可以操作数据的大小(limit后数据不能读写)

position:位置,表示缓冲区正在操作数据的位置

mark:标记,表示当前position的位置,可以通过reset()恢复到mark的位置

capacity<=limit<=capacity

package nio;

import java.nio.ByteBuffer;

//缓冲区:在java中负责存储
public class TestBuffer {
    public static void main(String[] args) {
        String str="abcd";
        //分配一个指定大小的缓冲区
        ByteBuffer buf=ByteBuffer.allocate(1024);
        System.out.println("执行了allocate之后 postion:"+buf.position()+",limit:"+buf.limit()+",capacity:"+buf.capacity());
        buf.put(str.getBytes());//将数据存入缓冲区
        System.out.println("getBytes postion:"+buf.position()+",limit:"+buf.limit()+",capacity:"+buf.capacity());
        buf.flip();//切换数据模式,切换之后position归零
        System.out.println("flip postion:"+buf.position()+",limit:"+buf.limit()+",capacity:"+buf.capacity());
        byte[]dst=new byte[buf.limit()];
        buf.get(dst);
        System.out.println("get postion:"+buf.position()+",limit:"+buf.limit()+",capacity:"+buf.capacity());
        System.out.println(new String(dst,0,dst.length));
        buf.rewind();//rewind可重复读数据
        System.out.println("rewind postion:"+buf.position()+",limit:"+buf.limit()+",capacity:"+buf.capacity());
        buf.clear();//清空缓冲区,但缓冲区的数据依然存在,但是处于被遗忘状态
        System.out.println("clear postion:"+buf.position()+",limit:"+buf.limit()+",capacity:"+buf.capacity());

    }
}

mark

package nio;

import java.nio.ByteBuffer;

//缓冲区:在java中负责存储
public class TestBuffer {
    public static void main(String[] args) {
        String str="abcd";
        ByteBuffer buf=ByteBuffer.allocate(1024);
        buf.put(str.getBytes());
        buf.flip();
        byte[]dst=new byte[buf.limit()];
        System.out.println("dist为:"+dst[0]);
        buf.get(dst,0,2);//从第0位后读俩位,执行之后def的值会改变
        System.out.println("dist为:"+dst[0]);
        System.out.println(new String(dst,0,2));

        buf.mark();
        buf.get(dst,2,2);
        System.out.println(new String(dst,2,2));

        buf.reset();//恢复到mark的位置
        System.out.println("position的位置在"+buf.position());
    }
}

直接缓冲区与非直接缓冲区

非直接缓冲区:通过allocate()方法分配缓冲区,将缓冲区建立在物理内存中(传统io也是用这个)

直接缓冲区:通过allocateDirect()方法直接分配缓冲区,将缓冲区建立在物理内存中,可以提高效率

优点效率高,缺点,性能消耗大,不安全

通道

用于源节点与目标节点的连接.在java NIO中负责缓冲区的传输.Channel本身不存储数据,因此需要配合缓冲区进行传输.

主要实现类

Fileannel

SocketChannel

ServerSocketChannel

获取通道

java针对通道的类提供了getChannel()方法

本地IO

FileInputStream/FileOutputStream

RandomAccessFile

网络IO

Socket

ServerSocket

DatagramSocket

在jdk1.7中的NIO.2针对各个通道提供了静态方法open()

在jdk1.7中的NIO.2的Files工具类的newByteChannel()

代码

利用通道完成文件的复制

package nio;

import java.nio.ByteBuffer;

//缓冲区:在java中负责存储
public class TestBuffer {


    public static void main(String[] args) {
        String str="abcd";
        ByteBuffer buf=ByteBuffer.allocate(1024);
        buf.put(str.getBytes());
        buf.flip();
        byte[]dst=new byte[buf.limit()];
        System.out.println("dist为:"+dst[0]);
        buf.get(dst,0,2);//从第0位后读俩位,执行之后def的值会改变
        System.out.println("dist为:"+dst[0]);
        System.out.println(new String(dst,0,2));

        buf.mark();
        buf.get(dst,2,2);
        System.out.println(new String(dst,2,2));

        buf.reset();//恢复到mark的位置
        System.out.println("position的位置在"+buf.position());
    }
}

直接使用缓冲区完成文件的复制(效率高)

package nio;

import java.io.IOException;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

//缓冲区:在java中负责存储
public class TestBuffer {

    //直接缓冲区完成复制文件
    public static void main(String[] args) throws IOException {
        //第一个参数是path,jdk1.7以后提供的文件路径类,第二个参数是文件操作类型
        FileChannel inChannel =
                FileChannel.open(Paths.get("E:\\code\\java\\test\\test\\src\\1.txt"), StandardOpenOption.READ);
        //create_new指的是不存在创建存在就报错.create是不存在创建,存在则覆盖
        FileChannel outChannel = FileChannel.open(Paths.get("E:\\code\\java\\test\\test\\src\\2.txt"),
                //下面的是读写模式,所以这里要加读和写,还有创建
                StandardOpenOption.WRITE, StandardOpenOption.CREATE,StandardOpenOption.READ);

        //map是内存映射文件 直接缓冲区只有bytebuffer支持
        MappedByteBuffer inMappedBuf = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
        MappedByteBuffer outMappedBuf = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());//读写模式,从0读到最后
//直接对缓冲区进行数据的读写操作
        byte[]dst=new byte[inMappedBuf.limit()];
        inMappedBuf.get(dst);//读取
        outMappedBuf.put(dst);//写入
        inChannel.close();
        outChannel.close();
    }
}

通道之间的数据传输

transferform

package nio;

import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

//缓冲区:在java中负责存储
public class TestBuffer {

    //直接缓冲区完成复制文件
    public static void main(String[] args) throws IOException {
        //第一个参数是path,jdk1.7以后提供的文件路径类,第二个参数是文件操作类型
        FileChannel inChannel =
                FileChannel.open(Paths.get("E:\\code\\java\\test\\test\\src\\1.txt"), StandardOpenOption.READ);
        //create_new指的是不存在创建存在就报错.create是不存在创建,存在则覆盖
        FileChannel outChannel = FileChannel.open(Paths.get("E:\\code\\java\\test\\test\\src\\2.txt"),
                //下面的是读写模式,所以这里要加读和写,还有创建
                StandardOpenOption.WRITE, StandardOpenOption.CREATE,StandardOpenOption.READ);
        outChannel.transferFrom(inChannel,0,inChannel.size());
        inChannel.close();
        outChannel.close();

    }
}

分散与聚集

分散读取:将通道中的数据分散到多个缓冲区当中

聚集写入:将多个缓冲区中的数据聚集到通道中

package nio;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

//缓冲区:在java中负责存储
public class TestBuffer {

    //直接缓冲区完成复制文件
    public static void main(String[] args) throws IOException {
        //分散读取
        RandomAccessFile raf1=new RandomAccessFile("E:\\code\\java\\test\\test\\src\\1.txt","rw");
        //获取通道
        FileChannel channel1=raf1.getChannel();

        //分配指定缓冲区大希奥
        ByteBuffer buf1 = ByteBuffer.allocate(100);
        ByteBuffer buf2 = ByteBuffer.allocate(100);
        //分散读取
        ByteBuffer[]bufs={buf1,buf2};
        channel1.read(bufs);

        for (ByteBuffer byteBuffer:bufs){
            byteBuffer.flip();
        }
        System.out.println(new String(bufs[0].array(),0,bufs[0].limit()));
        System.out.println(new String(bufs[1].array(),0,bufs[1].limit()));
        //聚集写入
        RandomAccessFile raf2 = new RandomAccessFile("E:\\code\\java\\test\\test\\src\\2.txt", "rw");
        FileChannel channel2 = raf2.getChannel();
        channel2.write(bufs);//写入
    }
}

字符集

ChartSet

编码:字符串->字节数组

节码:字节数组->字符串

package nio;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;

public class TestBuffer {

    public static void main(String[] args) throws CharacterCodingException, UnsupportedEncodingException {
        String str="hello world随意";
        Charset cs1 = Charset.forName("GBK");
        //获取编码器
        CharsetEncoder ce = cs1.newEncoder();
        //获取解码器
        CharsetDecoder cd = cs1.newDecoder();

        CharBuffer cBuf = CharBuffer.allocate(1024);
        cBuf.put(str);
        cBuf.flip();
        //编码
        ByteBuffer bBuf = ce.encode(cBuf);

        for (int i=0;i<str.getBytes("GBK").length;i++){
            System.out.println(bBuf.get());
        }
        //解码
        bBuf.flip();
        CharBuffer cBuf2=cd.decode(bBuf);
        System.out.println(cBuf2);
    }
}

NIO非阻塞式网络通信

阻塞传统IO都是阻塞式的,也就是说,当一个线程调用read()或者write()时,该线程被组塞,知道有一些数据被读取或者写入,该线程在此期间不能执行其他任务.因此,在完成网络IO操作时,由于线程会阻塞,所以服务器必须为每个客户端都提供一个独立的线程进行处理,当服务器端需要处理大量客户端时,性能急剧下降.

非阻塞java NIO是非阻塞模式的,当线程从某通道进行读写数据时,若没有数据可以使用,该线程可以进行1其他任务.线程通常将非阻塞IO的空闲的时间用于其他通道上执行IO操作,索引单独的线程可以管理多个输入和输出通道.因此NIO可以让服务器使用一个或几个线程来同时处理连接到服务器端的所有客户端

阻塞模式

使用NIO完成网络通信的三个核心

通道(Channel):负责连接

缓冲区(Buffer):负责存取数据

选择器(Selector):是SelectableChannel的多路复用器.用于监控SelectableChannel的IO状况

客户端

package nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

public class Client {
    public static void main(String[] args) throws IOException {
        //获取通道
        SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
        FileChannel inChannel=FileChannel.open(Paths.get("E:\\code\\java\\test\\test\\src\\1.txt"), StandardOpenOption.READ);
        //分配指定大小的缓冲区
        ByteBuffer buf=ByteBuffer.allocate(1024);
        //读取本地文件,并发送到服务器端
        while (inChannel.read(buf)!=-1){
            buf.flip();
            sChannel.write(buf);
            buf.clear();
        }
        //关闭通道
        inChannel.close();
        sChannel.close();
    }
}

服务端

package nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

public class Server {
    public static void main(String[] args) throws IOException {
        //获取通道
        ServerSocketChannel ssChannel=ServerSocketChannel.open();
        FileChannel outChannel=FileChannel.open(Paths.get("E:\\code\\java\\test\\test\\src\\2.txt"), StandardOpenOption.WRITE,StandardOpenOption.CREATE);
        //绑定连接
        ssChannel.bind(new InetSocketAddress(9898));
        //获取客户端的连接通道
        SocketChannel sChannel=ssChannel.accept();

        //分配指定大小的缓冲区
        ByteBuffer buf= ByteBuffer.allocate(1024);

        //接受客户端的数据,并保存到本地
        while (sChannel.read(buf)!=-1){
            buf.flip();
            outChannel.write(buf);
            buf.clear();
        }
        //关闭通道
        sChannel.close();
        outChannel.close();
        ssChannel.close();
    }
}

非阻塞式IO

选择器

当调用register将通道注册选择器时,选择器对通道监听事件,需要通过第二个参数ops指定.

可以监听的事件类型

  • 读OP_READ
  • 写OP_WRITE
  • 连接:OP_CONNECT
  • 接收OP_ACCEPT

若注册是不止监听一个事件,可以用位或运算符来连接

客户端

package nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Date;

public class Client {
    public static void main(String[] args) throws IOException {
       //获取通道
        SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.01", 9898));
        //切换非阻塞模式
        sChannel.configureBlocking(false);
        //分配指定大小的缓冲区
        ByteBuffer buf=ByteBuffer.allocate(1024);
        //发送数据给服务器
        buf.put(new Date().toString().getBytes());
        buf.flip();
        sChannel.write(buf);
        buf.clear();
        //关闭通道
        sChannel.close();

    }
}

服务端

package nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Iterator;

public class Server {
    public static void main(String[] args) throws IOException {
        //获取通道
        ServerSocketChannel ssChannel = ServerSocketChannel.open();
        //切换成非阻塞模式
        ssChannel.configureBlocking(false);
        //绑定连接
        ssChannel.bind(new InetSocketAddress(9898));
        //获取选择器
        Selector selector = Selector.open();
        //将通道注册到选择器上,并指定监听事件
        ssChannel.register(selector, SelectionKey.OP_ACCEPT);
        //轮询获取选择器上已经准备就绪的事件
        while (selector.select()>0){
            //获取当前选择器中所有的选择键(已就绪的监听事件)
            Iterator<SelectionKey> it = selector.selectedKeys().iterator();
            while (it.hasNext()){
                //获取准备就绪的事件
                SelectionKey sk=it.next();
                //判断具体是什么时事件准备就绪
                if (sk.isAcceptable()){
                    //若接受就绪,获取客户端连接
                    SocketChannel sChannel=ssChannel.accept();
                    //切换非阻塞模式
                    sChannel.configureBlocking(false);
                    //将该通道注册到选择器上
                    sChannel.register(selector,SelectionKey.OP_READ);
                }else if(sk.isReadable()){
                    //获取当前选择器上获取读就绪的状态通道
                    SocketChannel sChannel = (SocketChannel) sk.channel();
                    //读取数据
                    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();
                    }

                }
                //取消选择键
                it.remove();
            }
        }
    }
}

管道

java NIO管道是俩个线程之间的单项数据连接。pipe有一个source通道和一个sink通道。数据会被写道sink通道从source读取

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Pipe;

public class Main {

    public static void main(String[] args) throws IOException {
        //获取管道
        Pipe pipe =Pipe.open();
        //将缓冲区的数据写入管道
        ByteBuffer buf= ByteBuffer.allocate(1024);
        Pipe.SinkChannel sinkChannel = pipe.sink();
        buf.put("通过单项管道发送数据".getBytes());
        buf.flip();
        sinkChannel.write(buf);
        //读取缓冲区的数据
        Pipe.SourceChannel sourceChannel=pipe.source();
        int len=sourceChannel.read(buf);
        System.out.println(new String(buf.array(),0,len));
        sourceChannel.close();
        sinkChannel.close();
    }

}
Last modification:August 30, 2022
如果觉得我的文章对你有用,请随意赞赏