NIO

来源:互联网 发布:在上海工作的感受 知乎 编辑:程序博客网 时间:2024/06/07 00:38

一、
1. NIO 和 IO 的区别
这里写图片描述

  1. Java NIO 的核心:通道(channel 负责传输) 和缓存区(buffer负责存储)
    a) 缓冲区

    i.缓存区(buffer) :在Java nio 中负责数据的存取,缓冲区就是数组,用于存储不同数据类型的数据
    根据数据类型不同提供了相应类型的缓冲区(Boolean类型除外)
    ByteBuffer
    CharBuffer
    ShorBuffer
    IntBuffer
    LongBuffer
    FloatBuffer
    DoubleBuffer

    ii.缓冲区存取数据的两个方法
    put():存入数据到缓冲区
    get():获取缓冲区中的数据

    iii.缓冲区中的四个核心属性
    mark:标记,表示记录当前 position 的位置,可以通过reset 恢复到mark 记录的位置
    position:位置,表示缓冲区中正在操作数据的位置
    limit:界限,表示缓冲区可以操作数据的大小(limit 后面的数据不能进行读写)
    capacity:容量,标书缓冲区中最大存储数据的容量,一旦申明不能改变
    (0 ≤ mark ≤ position ≤ limit ≤ capacity)

    iv.其他方法
    flip() :切换读取数据的模式
    rewind ():可重复读数据
    clear (): 清空缓冲区但是缓冲区的数据依然存在,但是处于被遗忘状态,不能正确的读取数据

    v.直接缓冲区和非直接缓冲区

    1.非直接缓冲区:通过allocate()方法分配缓冲区,将缓冲区建立在JVM的内存中(堆内存开辟工作区间,数组)
    这里写图片描述
    这里写图片描述

    2.直接缓冲区:通过allocateDirect() 方法分配直接缓冲区,将缓冲区建立在物理内存中,可以提高效率
    这里写图片描述
    缺点:物理内存映射文件中的内容不归我们管,要开辟物理内存,gc 回收
    这里写图片描述
    isDirect() 方法判断是否是直接缓冲区 isDirect()

b) 通道(channel):专门用IO操作

i. 通道与流的区别
通道(channel):用于源节点和目标节点的链接,在Java NIO 中负责缓冲区中数据的传输,通道本身不存储任何数据,需要配合缓冲区进行传输

ii. 通道的主要实现类:
java.nio.channels.Channel 接口
|–FileChannel(本地数据的传输)
|–SocketChannel(网络数据的传输)
|–ServerSocketChannel(网络数据的传输)
|–DatagramChannel(网络数据的传输)

iii. 获取通道的三种方式:
1. Java 针对支持通道的类提供了getChannel() 方法
本地IO:
FileInputStream/FileOutputStream
RandomAccessFile

网络IO:
Socket
ServerSocket (TCP)
DatagramSocket(UDP)

  1. JDK1.7 中的 NIO.2针对各个通道提供了一个静态方法 open()
  2. 在JDK1.7 中的NIO.2的 Files 工具类的newByteChannel()

iv. 通道之间的数据传输(直接缓冲区的方式)
transferFrom()
transferTo ()

v. 示例
1. 利用通道完成文件的复制

FileInputStream fis = null;FileOutputStream fos = null;FileChannel inChannel = null;FileChannel outChannel = null;try {    fis = new FileInputStream("1.jpg");    fos = new FileOutputStream("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 (FileNotFoundException e) {    e.printStackTrace();} catch (IOException e) {    e.printStackTrace();}finally {    if(outChannel !=null){        try {            outChannel.close();        } catch (IOException e) {            e.printStackTrace();        }    }    if(inChannel !=null){        try {            inChannel.close();        } catch (IOException e) {            e.printStackTrace();        }    }    if(fos !=null){        try {            fos.close();        } catch (IOException e) {            e.printStackTrace();        }    }    if(fis !=null){        try {            fis.close();        } catch (IOException e) {            e.printStackTrace();        }    }}
  1. 只用直接缓冲区完成文件的复制(内存映射文件方式)(直接缓冲区的模式只有 ByteBuffer 支持)
@Testpublic void test2() throws IOException{    Instant start = Instant.now();    FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);    FileChannel outChannel = FileChannel.open(Paths.get("2.jpg"), StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE);    // 内存映射文件    MappedByteBuffer inMappedBuf = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());    MappedByteBuffer outMappedBuf = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());    // 直接对缓冲区进行数据的读写操作    byte[] dst = new byte[inMappedBuf.limit()];    inMappedBuf.get(dst);    outMappedBuf.put(dst);    inChannel.close();    outChannel.close();    Instant end = Instant.now();    System.out.println(Duration.between(start, end).toMillis()); }
  1. 通道之间的数据传输
@Testpublic void test3() throws IOException{    FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);    FileChannel outChannel = FileChannel.open(Paths.get("2.jpg"), StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE);    // inChannel.transferTo(0, inChannel.size(), outChannel);    outChannel.transferFrom(inChannel, 0, inChannel.size());}

vi. 分散(scatter)与聚集(gather)
分散读取(scattering reads): 将通道中的数据分散到多个缓冲区中
聚集写入(gathering writes): 将多个缓冲区的数据聚集到通道中

//分散聚集@Testpublic void test4() throws IOException{    RandomAccessFile raf1 = new RandomAccessFile("test.txt", "rw");    // 获取通道    FileChannel channel = raf1.getChannel();    // 分配指定大小的缓冲区    ByteBuffer buf1 = ByteBuffer.allocate(1024);    ByteBuffer buf2 = ByteBuffer.allocate(1024);    // 分散读取    ByteBuffer[] bufs = {buf1,buf2};    channel.read(bufs);    for (ByteBuffer byteBuffer : bufs) {        byteBuffer.flip();    }    System.out.println(new String(bufs[0].array(),0,bufs[0].limit()) );    System.out.println("***********************************") ;    System.out.println(new String(bufs[1].array(),0,bufs[1].limit()) );    // 具体写入    RandomAccessFile raf2 = new RandomAccessFile("test2.txt", "rw");    FileChannel channel2 = raf2.getChannel();    channel2.write(bufs);}

vvi. 字符集 charset
编码:字符串—》字节数组
解码:字节数组—》字符串

@Testpublic void test6() throws IOException{    Charset cs1 = Charset.forName("GBK");    // 获取编码器    CharsetEncoder encoder = cs1.newEncoder();    // 获取解码器    CharsetDecoder decoder = cs1.newDecoder();    CharBuffer cbuf = CharBuffer.allocate(1024);    cbuf.put("my test!");    cbuf.flip();    //编码    ByteBuffer bBuf = encoder.encode(cbuf);    for (int i = 0; i <12; i++) {        System.out.println(bBuf.get());    }    //解码    bBuf.flip();    CharBuffer ccbuf = decoder.decode(bBuf);    System.out.println(ccbuf.toString());    bBuf.flip();    // GBK 编码 utf-8 解码出现乱码    Charset cs2 = Charset.forName("UTF-8");    CharBuffer cbbuf = cs2.decode(bBuf);    System.out.println(cbbuf.toString());}
  1. NIO 的非阻塞式网络通信
    a) 使用NIO 完成网络通信的三个核心
// 客户端@Testpublic void client() throws IOException{    // 1、获取通道    SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9803));    // 2、切换成非阻塞模式    socketChannel.configureBlocking(false);    // 3、分配指定大小的缓冲区    ByteBuffer buf = ByteBuffer.allocate(1024);    // 4、读取本地文件并发送到服务器    buf.put(new Date().toString().getBytes());    buf.flip();    socketChannel.write(buf);    buf.clear();    // 4、关闭通道    socketChannel.close();}
// 服务端    @Test    public void server() throws IOException{        // 1、获取通道        ServerSocketChannel ssChannel = ServerSocketChannel.open();        // 2、切换非阻塞模式        ssChannel.configureBlocking(false);        // 3、连接绑定        ssChannel.bind(new InetSocketAddress(9803));        // 4、获取选择器        Selector selector = Selector.open();        // 5、将通道注册到选择器上,并且指定监听事件        ssChannel.register(selector, SelectionKey.OP_ACCEPT);        // 6、轮询式的获取选择器上已经“准备就绪”的时间        while(selector.select() > 0){            // 7、获取当前选择器中多有注册的选择键(已注册的监听事件)            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();            while(iterator.hasNext()){                // 8、迭代获取准备就绪的事件                SelectionKey sk = iterator.next();                // 9、判断具体是什么事件准备就绪                if(sk.isAcceptable()){                    // 10、若"接收就绪" ,获取客户端链接                    SocketChannel socketChannel = ssChannel.accept();                    // 11、切换非阻塞模式                    socketChannel.configureBlocking(false);                    // 12、将该通道注册到选择器                    socketChannel.register(selector, SelectionKey.OP_ACCEPT);                }else if(sk.isReadable()){                    // 13、获取当前选择器上读就绪状态的通道                    SocketChannel channel = (SocketChannel) sk.channel();                    // 14、读取数据                    ByteBuffer buf = ByteBuffer.allocate(1024);                    int len = 0;                    while((len = channel.read(buf)) > 0){                        buf.flip();                        System.out.println(new String(buf.array(), 0, len));                        buf.clear();                    }                }                // 15、取消选择键                iterator.remove();            }        }    }
原创粉丝点击