java_nio

nio简介

Java NIO(New IO)是从Java 1.4版本开始引入的 一个新的IO API,可以替代标准的Java IO API。 
NIO与原来的IO有同样的作用和目的,但是使用 的方式完全不同,NIO支持面向缓冲区的、基于 通道的IO操
作。NIO将以更加高效的方式进行文件的读写操作。

javaNIO与IO的区别:
传统方式的IO:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
通道和缓冲区
javaNIO系统的核心在于:

通道(Channel)和缓冲区 (Buffer)。通道表示打开到 IO 设备(例如:文件、 套接字)的连接。若需要使用 NIO 系统,需要获取 用于连接 IO 设备的通道以及用于容纳数据的缓冲 区。然后操作缓冲区,对数据进行处理。 Channel 负责传输, Buffer 负责存储。

缓冲区:
一个用于特定基本数据类型的容器。由java.nio包定义,所有缓冲区都是buffer抽象类的子类

java NIO中的buffer主要用于与NIO通道进行交互,数据是同通道读入缓冲区,从缓冲区写入通道中去。

Buffer的重要概念:

  容量 (capacity) :表示 Buffer 最大数据容量,缓冲区容量不能为负,并且创 
建后不能更改。 
 限制 (limit):第一个不应该读取或写入的数据的索引,即位于 limit 后的数据 
不可读写。缓冲区的限制不能为负,并且不能大于其容量。 
 位置 (position):下一个要读取或写入的数据的索引。缓冲区的位置不能为 
负,并且不能大于其限制 
 标记 (mark)与重置 (reset):标记是一个索引,通过 Buffer 中的 mark() 方法 
指定 Buffer 中一个特定的 position,之后可以通过调用 reset() 方法恢复到这 
个 position。

标记、位置、限制、容量遵守以下不变式: 0 <= mark <= position <= limit <= capacity

缓冲区的基本属性
在这里插入图片描述
Buffer的常用方法
在这里插入图片描述
缓冲区的数据操作
Buffer 所有子类提供了两个用于数据操作的方法:get()
与 put() 方法

在这里插入图片描述
在这里插入图片描述

/**
 * 缓冲区  在java NIO中负责数据的存取。  缓冲区就是数组,用于存储不同数据类型的数据
 * 根据数据类型不同(Boolean除外),提供相应的缓冲区
 * ByteBuffer   CharBuffer  ShortBuffer IntBuffer   LongBuffer  FloatBuffer DoubleBuffer
 *
 * 上述缓冲区的管理方式几乎一致,allocate()
 * 缓冲区存取数据的两个核心方法       put():放入数据到缓冲区      get():获取缓冲区的数据
 * 缓冲区的四个核心属性
 * capacity 容量      一旦声明,不能改变。
 * limit 界限:表示缓冲区可以操作数据的大小      limit后面的数据不能进行读写
 * position 位置,表示缓冲区中正在操作数据的位置
 * position <=limit<=capacity
 *
 * 直接缓冲区  非直接缓冲区   非直接缓冲区:通过allocate()方法分配缓冲区,将缓冲区建立在jvm的内存中
 * 缓冲区:通过allocateDirect()方法分配直接缓冲区,将缓冲区建立在物理内存中。可以提高效率
 */ 
public class testBuffer {
    @Test
    public void Test2(){
        ByteBuffer buf = ByteBuffer.allocate(1024);
        String str="abcde";
        buf.put(str.getBytes());
        buf.flip();
        byte[] dst = new byte[buf.limit()];
        buf.get(dst,0,2);
        System.out.println(new String(dst,0,2));

        System.out.println(buf.position());
        //mark() 标记一下
        buf.mark();
        buf.get(dst,2,2);
        System.out.println(new String(dst,2,2));
        System.out.println(buf.position());

        buf.reset();//回到mark的位置
        System.out.println(buf.position());
        if(buf.hasRemaining()){
            //剩余的可以操作的数据个数   刚才position回到了2,所以还剩下3个
            System.out.println(buf.remaining());
        }
    }
    @Test
    public void test1(){
        ByteBuffer buf = ByteBuffer.allocate(1024);
        String str="abcde";
        System.out.println("`````````````````allocate");
        System.out.println(buf.position());
        System.out.println(buf.capacity());
        System.out.println(buf.limit());

        //利用put()存入数据到缓冲区
        buf.put(str.getBytes());
        System.out.println("`````````````````put");
        System.out.println(buf.position());
        System.out.println(buf.capacity());
        System.out.println(buf.limit());
        //切换为读取数据模式
        buf.flip();
        System.out.println("````````````````flip");
        System.out.println(buf.position());
        System.out.println(buf.capacity());
        System.out.println(buf.limit());

        //利用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());//获取的时候position的位置往后移动
        System.out.println(buf.capacity());
        System.out.println(buf.limit());

        //rewind():可重复读数据
        buf.rewind();
        System.out.println("``````````````rewind");
        System.out.println(buf.position());
        System.out.println(buf.capacity());
        System.out.println(buf.limit());

        //清空缓冲区  clear方法    回到最初状态      但是缓冲区的数据还在,只是处于被"遗忘状态"
        System.out.println("`````````````clear");
        System.out.println(buf.position());
        System.out.println(buf.capacity());
        System.out.println(buf.limit());
    }
}

在这里插入图片描述
在这里插入图片描述

/**
 * 缓冲区  在java NIO中负责数据的存取。  缓冲区就是数组,用于存储不同数据类型的数据
 *
 * 根据数据类型不同(Boolean除外),提供相应的缓冲区
 * ByteBuffer   CharBuffer  ShortBuffer IntBuffer   LongBuffer  FloatBuffer DoubleBuffer
 *
 * 上述缓冲区的管理方式几乎一致,allocate()
 * 缓冲区存取数据的两个核心方法       put():放入数据到缓冲区      get():获取缓冲区的数据
 * 缓冲区的四个核心属性
 * capacity 容量      一旦声明,不能改变。
 * limit 界限:表示缓冲区可以操作数据的大小      limit后面的数据不能进行读写
 * position 位置,表示缓冲区中正在操作数据的位置
 * position <=limit<=capacity
 *
 * 直接缓冲区  非直接缓冲区   非直接缓冲区:通过allocate()方法分配缓冲区,将缓冲区建立在jvm的内存中
 * 缓冲区:通过allocateDirect()方法分配直接缓冲区,将缓冲区建立在物理内存中。可以提高效率
 *
 *
 */
public class testBuffer {
    @Test
    public void Test2(){
        ByteBuffer buf = ByteBuffer.allocate(1024);
        String str="abcde";
        buf.put(str.getBytes());
        buf.flip();
        byte[] dst = new byte[buf.limit()];
        buf.get(dst,0,2);
        System.out.println(new String(dst,0,2));

        System.out.println(buf.position());
        //mark() 标记一下
        buf.mark();
        buf.get(dst,2,2);
        System.out.println(new String(dst,2,2));
        System.out.println(buf.position());

        buf.reset();//回到mark的位置
        System.out.println(buf.position());
        if(buf.hasRemaining()){
            //剩余的可以操作的数据个数   刚才position回到了2,所以还剩下3个
            System.out.println(buf.remaining());
        }
    }
    @Test
    public void test1(){
        ByteBuffer buf = ByteBuffer.allocate(1024);
        String str="abcde";
        System.out.println("`````````````````allocate");
        System.out.println(buf.position());
        System.out.println(buf.capacity());
        System.out.println(buf.limit());

        //利用put()存入数据到缓冲区
        buf.put(str.getBytes());
        System.out.println("`````````````````put");
        System.out.println(buf.position());
        System.out.println(buf.capacity());
        System.out.println(buf.limit());
        //切换为读取数据模式
        buf.flip();
        System.out.println("````````````````flip");
        System.out.println(buf.position());
        System.out.println(buf.capacity());
        System.out.println(buf.limit());

        //利用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());//获取的时候position的位置往后移动
        System.out.println(buf.capacity());
        System.out.println(buf.limit());

        //rewind():可重复读数据
        buf.rewind();
        System.out.println("``````````````rewind");
        System.out.println(buf.position());
        System.out.println(buf.capacity());
        System.out.println(buf.limit());

        //清空缓冲区  clear方法    回到最初状态      但是缓冲区的数据还在,只是处于被"遗忘状态"
        System.out.println("`````````````clear");
        System.out.println(buf.position());
        System.out.println(buf.capacity());
        System.out.println(buf.limit());
    }
    @Test
    public void Test3(){
        //分配直接缓冲区
        ByteBuffer buf = ByteBuffer.allocateDirect(1024);
        System.out.println(buf.isDirect());
    }
}
/**
 * 通道:用于源节点与目标节点的连接,在java nio负责缓冲区的数据的传输。channel本身是不存储数据的,因此需要配合缓冲区进行传输。
 * 通道的一些主要实现类  java.nio,channel接口
 * fileChannel   socketChannel    serverSocketChannel   网络io   DatagramChannel  UDP
 *
 * 1.如何获取通道
 * java针对支持通道的类提供了getChannel()方法
 * 本地IO
 * FileInputStream/FileOutPutStream/RandomAccessStream
 *
 * 网络IO
 * socket
 * ServerAccessFile
 * DatagramSocket
 *
 * 获取通道的方法
 * 2.在JDK1.7中的NIO.2针对各个通道提供了静态方法  open()
 *
 *3.在JDK1.7中的NIO.2的Files工具类的newByteChannel()
 *
 */
public class testChannel {
    //利用通道完成文件的复制

    @Test
    public void test1() {
        FileInputStream fis= null;
        FileOutputStream fos= null;
        FileChannel inChannel = null;
        FileChannel outChannel = null;
        try {
            fis = new FileInputStream("src\\1.jpg");
            fos = new FileOutputStream("src\\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 {
            try {
                if(outChannel!=null) outChannel.close();
                if(inChannel!=null) inChannel.close();
                if(fos!=null) fos.close();
                if(fis!=null) fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}
@Test
//使用内存映射文件 完成复制
public void test2() throws Exception{
    FileChannel inChannel = FileChannel.open(Paths.get("src\\1.png"), StandardOpenOption.READ);//读的模式
    FileChannel outChannel=FileChannel.open(Paths.get("src\\2.png"),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);
    inChannel.close();
    outChannel.close();
}
 //四、通道之间的数据传输   transferFrom()  transformTo()
    @Test
    public void test3() throws Exception{
        FileChannel inChannel = FileChannel.open(Paths.get("src\\1.png"), StandardOpenOption.READ);//读的模式
        FileChannel outChannel=FileChannel.open(Paths.get("src\\2.png"),StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE_NEW);

//        inChannel.transferTo(0,inChannel.size(),outChannel);
        outChannel.transferFrom(inChannel,0,inChannel.size());
        inChannel.close();
        outChannel.close();
    }
}
//五、分散与聚集
// 分散读取:将通道中的数据读取的数据分散到多个通道buffer中
//聚集写入:将多个buffer缓冲区聚集到channel
@Test
public void test4() throws Exception{
    RandomAccessFile file=new RandomAccessFile("src\\1.txt","rw");//设置为读写模式

    //1、获取通道
    FileChannel channel = file.getChannel();

    //2.获取指定大小的缓冲区
    ByteBuffer btf1 = ByteBuffer.allocate(100);//缓冲区1
    ByteBuffer btf2 = ByteBuffer.allocate(200);//缓冲区2

    //3.分散读取
    ByteBuffer[] buf={btf1,btf2};
    channel.read(buf);

    for (ByteBuffer temp:buf){
        temp.flip();
    }
    System.out.println(new String(buf[0].array(),0,buf[0].limit()));//String方法的三个参数  数组   起始坐标   读取的长度
    System.out.println(new String(buf[1].array(),0,buf[1].limit()));//String方法的三个参数  数组   起始坐标   读取的长度
    //聚集写入   写入到2.txt
    RandomAccessFile file2=new RandomAccessFile("src\\3.txt","rw");
    FileChannel channel1 = file2.getChannel();
    channel1.write(buf);
    channel1.close();
}
//字符集  CharSet
//编码:字符串--->字节数组
//解码:字节数组--->字符集
@Test
public void test5(){
    SortedMap<String, Charset> map = Charset.availableCharsets();
    Set<Map.Entry<String, Charset>> set = map.entrySet();
    for (Map.Entry<String,Charset> entry:set){
        System.out.println(entry.getKey()+"="+entry.getValue());
    }
}
//字符集
@Test
public void test6() throws Exception{
    Charset gbk = Charset.forName("GBK");
    //获取编码器
    CharsetEncoder encoder = gbk.newEncoder();
    //获取解码器
    CharsetDecoder decoder = gbk.newDecoder();

    CharBuffer buf = CharBuffer.allocate(1024);
    buf.put("xxx");
    buf.flip();

    //编码
    ByteBuffer encode1 = encoder.encode(buf);
    for (int i = 0; i < encode1.limit(); i++) {
        System.out.println(encode1.get());
    }

    //对编码的内容进行解码
    encode1.flip();
    CharBuffer decode = decoder.decode(encode1);
    System.out.println(decode.toString());

    System.out.println("-----------");//换utf-8进行解码
    Charset charset = Charset.forName("utf-8");
    encode1.flip();
    CharBuffer decode1 = charset.decode(encode1);
    System.out.println(decode1.toString());

}

NIO的非阻塞网络通信
阻塞与非阻塞
在这里插入图片描述
缺点:即使加入多线程,让其去访问服务器,但是CPU的利用率还是不够高。
NIO的形式 非阻塞式
在这里插入图片描述
阻塞式示例代码

/**
 * 使用nio完成完成网络通信的三个核心
 * 1.通道:负责连接
 *
 * --java.nio.channels.channel接口
 *      --SelectableChannel
 *          --SocketChannel             TCP
 *          --serverSocketChannel       TCP
 *          --DatagramChannel       UDP
 *
 *          --Pipe.SinkChannel
 *          --Pipe.SourceChannel
 *
 * 2.缓冲区:负责数据的存取
 * 3.选择器:是selectableChannel的多路复用器。用于监控selectableChannel的IO状况
 */

//阻塞示例
public class TestBlockNIO {
    //客户端
    @Test
    public void client() throws Exception{
        //1.获取通道
        SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",9898));//服务端通道

        FileChannel inChannel=FileChannel.open(Paths.get("src\\1.png"), StandardOpenOption.READ);

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

        //3.读取本地文件,并发送到服务器
        while(inChannel.read(buf)!=-1){
            buf.flip();
            sChannel.write(buf);
            buf.clear();
        }
        //关闭通道
        inChannel.close();
        sChannel.close();

    }
    //服务端
    @Test
    public void server()throws Exception{
        //1、获取通道
        ServerSocketChannel ssChannel = ServerSocketChannel.open();//ssChannel   serverSocketChannel

        FileChannel outChannel=FileChannel.open(Paths.get("src\\2.png"), StandardOpenOption.WRITE,StandardOpenOption.CREATE_NEW);
        //2.绑定连接
        ssChannel.bind(new InetSocketAddress(9898));

        //3.客户端连接的通道
        SocketChannel sChannel = ssChannel.accept();

        //4.接受客户端的数据,并保存到本地

        //缓冲区
        ByteBuffer dst = ByteBuffer.allocate(1024);
        while (sChannel.read(dst)!=-1){
            dst.flip();
            outChannel.write(dst);
            dst.clear();//必须放在while循环的内部,为什么呢?我想应该是终止while,也就是读取完数据跳出循环的条件
        }
        outChannel.close();
        sChannel.close();
        ssChannel.close();
    }
}

思路:如果出现错误,可以使用try//catch方法看看哪里出现了问题
注意:先启动服务器,之后再其中客户端。如果先启动服务器,那和谁连呢吗,所以,需要先让服务器处于就绪状态。

非阻塞示例代码

public class testBlockNIO {
    //客户端
    @Test
    public void client() throws Exception{
        SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
        FileChannel file=FileChannel.open(Paths.get("src\\1.png"), StandardOpenOption.READ);

        ByteBuffer buf = ByteBuffer.allocate(1024);

        while(file.read(buf)!=-1){
            buf.flip();
            sChannel.write(buf);
            buf.clear();
        }

        sChannel.shutdownOutput();//告诉服务器已经传输完毕
        //接受服务端的反馈
        int len=0;
        while((len=sChannel.read(buf))!=-1){
            buf.flip();
            System.out.println(new String(buf.array(),0,len));
            buf.clear();
        }
        file.close();
        sChannel.close();
    }

    //服务端
    @Test
    public void server()throws Exception{
        ServerSocketChannel ssChannel = ServerSocketChannel.open();
        ssChannel.bind(new InetSocketAddress(9898));
        FileChannel outChannel = FileChannel.open(Paths.get("src\\2.png"), StandardOpenOption.WRITE,StandardOpenOption.CREATE_NEW);
        SocketChannel sChannel = ssChannel.accept();

        ByteBuffer buf = ByteBuffer.allocate(1024);
        while (sChannel.read(buf)!=-1){
            buf.flip();
            outChannel.write(buf);
            buf.clear();
        }
        //发送返回给客户端
        buf.put("服务端接受数据成功".getBytes());
        buf.flip();
        sChannel.write(buf);
        sChannel.close();
        outChannel.close();
        ssChannel.close();
    }
}

非阻塞式NIO

public class testNotBlockingNIO {

    //客户端
    @Test
    public void client()throws Exception{
        //1.获取通道
        SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
        //2.切换为非阻塞模式
        sChannel.configureBlocking(false);

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

        //4.发送数据给服务端
        buf.put(new Date().toString().getBytes());
        buf.flip();
        buf.clear();
        //关闭通道
        sChannel.close();
    }

    //服务端
    @Test
    public void server() throws Exception{
        //获取通道
        ServerSocketChannel ssChannel = ServerSocketChannel.open();
        ssChannel.configureBlocking(false);//切换为非阻塞模式
        FileChannel outChannel = FileChannel.open(Paths.get("2.png"), StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW);
        //绑定连接
        ssChannel.bind(new InetSocketAddress(9898));

        //获取选择器
        Selector selector = Selector.open();

        //将通道注册到选择器上,指定“监听接受事件”
        SelectionKey register = ssChannel.register(selector, SelectionKey.OP_ACCEPT);

        //轮询的获取选择器上已经准备就绪的事件
        while(selector.select()>0){
            //获取当前选择器中所有注册的选择键(已就绪的监听事件)
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();

            //迭代器迭代
            while (iterator.hasNext()){
                //获取准备就绪的事件
                SelectionKey sk = iterator.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();
                    }
                }
                //取消SelectionKey
                iterator.remove();
            }
        }
    }
}
Java NIO中的DatagramChannel是一个能收发UDP包的通道。
操作步骤:
打开DatagramChannel---------------------------接受/发送数据

Datagram示例代码:

public class testNonblockingNIO {
    @Test
    public void send() throws IOException {
        DatagramChannel dc = DatagramChannel.open();
        dc.configureBlocking(false);

        ByteBuffer buf = ByteBuffer.allocate(1024);

        Scanner scanner = new Scanner(System.in);
        while(scanner.hasNext()){
            String str = scanner.next();
            buf.put((new Date().toString()+":\n"+str).getBytes());
            buf.flip();
            dc.send(buf,new InetSocketAddress("127.0.0.1",9898));
            buf.clear();
        }
        dc.close();
    }

    @Test
    public void receive() throws IOException{
        DatagramChannel dc = DatagramChannel.open();
        dc.configureBlocking(false);

        dc.bind(new InetSocketAddress(9898));
        Selector selector=Selector.open();

        dc.register(selector, SelectionKey.OP_READ);
        while (selector.select()>0){
            Iterator<SelectionKey> it = selector.selectedKeys().iterator();

            while (it.hasNext()){
                SelectionKey sk = it.next();
                if(sk.isReadable()){
                    ByteBuffer buf = ByteBuffer.allocate(1024);

                    dc.receive(buf);
                    buf.flip();
                    System.out.println(new String(buf.array(),0,buf.limit()));
                    buf.clear();
                }
            }
            it.remove();
        }
    }
}

idea出现控制台无法输入的问题:

help---edit custom vm options...

粘贴下面这句话就可以解决。

-Deditable.java.test.console=true

管道:Java NIO 管道是2个线程之间的单向数据连接。 Pipe有一个source通道和一个sink通道。数据会被写到sink通道,从source通道读取。
在这里插入图片描述
示例代码:

public class TestPipe {
    @Test
    public void test1()throws Exception{
        //1.获取管道
        Pipe pipe = Pipe.open();

        //2.将缓冲区的数据写入管道
        ByteBuffer buf = ByteBuffer.allocate(1024);
        //向管道写输入
        Pipe.SinkChannel sinkChannel = pipe.sink();

        buf.put("通过单向管道发送数据".getBytes());
        buf.flip();
        //向管道中写入数据
        sinkChannel.write(buf);

        //读取缓冲区的数据,访问source通道
        Pipe.SourceChannel sourceChannel = pipe.source();
        //切换为读取数据的模式
        buf.flip();

        int len = sourceChannel.read(buf);
        System.out.println(new String(buf.array(),0,len));
        //关闭资源
        	  sourceChannel.close();
        sinkChannel.close();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值