NIO

传统IO和普通IO的区别

传统IO:面向流,阻塞IO(Blocking), selector

NIO:面向缓冲区,非阻塞IO(Non Blocking)
通道(channel负责传输)和缓冲区(Buffer负责数据的存储)。通道表示打开到IO设备的链接(文件,套接字等)。使用NIO需要获取链接IO设备的通道以及容纳数据的缓冲区,然后操作缓冲区对数据进行处理。

缓冲区

缓冲区:

在javaNIO中负责数据的存取。底层就是数组,用于存储不同数据类型的数据。根据数据类型不同,提供了相应类型的缓冲区(Boolean除外),
ByteBuffer
shortBuffer
IntBUffer
LongBuffer
FolatBuffer
DoubleBuffer
上述缓冲区的管理方式基本相同,通过allocate()获取相应的缓冲区

缓冲区核心方法:
  • put:向缓冲区中存入数据
  • get:从缓冲区中取出数据
  • flip:切换模式(从写模式切换到读模式)
  • rewind:可重复读取数据,将position的值置为0
  • clear:清空缓冲区,回到最初的状态,其中的数据并没有被清空。数据处于被遗忘的状态。
  • hasRemaining:判断缓冲区中是否还有可以读取的数据
    *remaining:如果有可以读取的数据,看看还有多少个。
缓冲区核心属性:
  • capacity:缓冲区中最大存储数据的容量,一旦声明不能改变。
    *limit:表示缓冲区中可以操作数据的大小,limit后面的数据是不能够进行读写的。
  • position:表示换缓冲区中正在操作数据的位置。
  • mark:标记,可以记录position的位置,可以通过reset恢复position的状态。
直接缓冲区和非直接缓冲区:
  • 非直接缓冲区:通过allocate()方法获取缓冲区,将缓冲区建立在jvm的内存中。直接缓冲区只有ByteBuffer支持
  • 直接缓冲区:通过allocateDirect()分配直接缓冲区,将换缓冲区建立在操作系统的物理内存中。
非直接缓冲区.png
直接缓冲区.png
@Test
    public void fun1(){
        //创建指定大小缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        
        System.out.println("-------------------创建");
        printBuffer(buffer);
        
        System.out.println("-------------------存数据");
        buffer.put("zxvbnmasdw".getBytes());
        printBuffer(buffer);
        
        System.out.println("-------------------切换模式");
        buffer.flip();
        printBuffer(buffer);
        
        System.out.println("-------------------rewind");
        buffer.rewind();
        printBuffer(buffer);
    }
    
    private void printBuffer(Buffer buffer){
        System.out.println("capacity   :" + buffer.capacity());
        System.out.println("limit      :" + buffer.limit());
        System.out.println("position   :" + buffer.position());
    }

通道(Channel)

用于源节点和目标节点的链接在javaNIO中负责缓冲区中数据的传输,通道本身不存储任何数据,需要配合缓冲区进行使用。

通道的主要实现类:
java.nio.channel
FileChannel
SocketChannel
 ServerSocketChannel
DataGramChannel(UDP)
获取通道:
  • java针对支持通道的类提供了getChannel方法
    FileInputStrean FIleOutputStrean
    RandomAccessFile
    Socket
    ServerSocket
    DataGramSocket
  • jdk1.7中NIO2针对各个通道(FileChannel)提供了静态方法open()
  • jdk1.7中NIO2的Files工具类的newByteChannel()方法
通道.png
使用非直接缓冲区进行文件的复制
@Test
    public void fun3() throws Exception{
        FileInputStream in = new FileInputStream("src/file");
        FileOutputStream out = new FileOutputStream("src/file2");
        
        //获取相应的通道
        FileChannel inChannel = in.getChannel();
        FileChannel outCannel = out.getChannel();
        
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        while(inChannel.read(buffer) != -1){
            buffer.flip();
            outCannel.write(buffer);
            buffer.clear();
        }
        
    }

使用直接缓冲区完成文件的复制(内存映射文件的方式)

    @Test
    public void fun4() throws IOException{
        FileChannel inChannel = FileChannel.open(Paths.get("src/file"), StandardOpenOption.READ);
        //StandardOpenOption.CREATE_NEW:不存在就创建,存在就报错
        //StandardOpenOption.CREATE:不存在就创建,存在就覆盖
        FileChannel outChannel = FileChannel.open(Paths.get("src/file3"), StandardOpenOption.WRITE, StandardOpenOption.READ,StandardOpenOption.CREATE);
        //内存映射文件,和allocateDirect相同,只是获取方式不同。
        ByteBuffer readBuffer = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());
        ByteBuffer writeBuffer = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());
        //直接对缓冲区中对数据进行操作
        byte[] by = new byte[readBuffer.limit()];
        readBuffer.get(by);
        writeBuffer.put(by);
    }

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

  • transferTo
  • transferFrom
@Test
    public void fun5() throws Exception{
        FileChannel inChannel = FileChannel.open(Paths.get("src/file"), StandardOpenOption.READ);
        //StandardOpenOption.CREATE_NEW:不存在就创建,存在就报错
        //StandardOpenOption.CREATE:不存在就创建,存在就覆盖
        FileChannel outChannel = FileChannel.open(Paths.get("src/file4"), 
                StandardOpenOption.WRITE, StandardOpenOption.READ,StandardOpenOption.CREATE);
        inChannel.transferTo(0, inChannel.size(), outChannel);
    }

分散读取(scatter)和聚集写入(Gather)

  • 分散读取:将通道中的数据按照缓存的次序依次读取缓存中(将缓冲区依次填满)
  • 聚集写入:将缓存中的数据按照缓存的次序依次写入通道中


    分散读取.png

    聚集写入.png
@Test
    public void fun6() throws Exception{
        FileInputStream in = new FileInputStream("src/file");
        FileChannel inc = in.getChannel();
        FileOutputStream out = new FileOutputStream("src/file5");
        FileChannel outc = out.getChannel();
        
        ByteBuffer buffer1 = ByteBuffer.allocate(100);
        ByteBuffer buffer12 = ByteBuffer.allocate(100);
        inc.read(new ByteBuffer[]{buffer1, buffer12});
        
        outc.write(new ByteBuffer[]{buffer1, buffer12});
    }

字符集

  • 编码:将字符串装换成字节数组
  • 解码:将字节数组装换成字符串
@Test
    public void fun7() throws Exception{
        //获取编码方式
        Charset charset = Charset.forName("GBK");
        //获取编码器
        CharsetEncoder encoder = charset.newEncoder();
        CharBuffer charBuffer = CharBuffer.allocate(1024);
        CharBuffer buffer = charBuffer.put("哈哈你好");
        buffer.flip();
        ByteBuffer byteBuffer = encoder.encode(charBuffer);

        //创建解码器
        CharsetDecoder decoder = charset.newDecoder();
        CharBuffer charBuffer2 = decoder.decode(byteBuffer);
        System.out.println(charBuffer2.get());
    }

网络通信

Socket阻塞式

当服务端接收到用户的请求,开启线程,通过accept阻塞这个线程等待客户端成功建立链接(包括客户端的数据全部发送到服务器段),成功之后才对数据进行操作。

  • 通道(Channel),负责链接
    java.nio.channels.Channel接口
    抽象类selectorChannel(子类:SocketChannel, ServerSocketChannel,DataGramChannel。
    Pipe.SinkChannel, Pipe.SourceChannel)
  • 2、缓冲区(Buffer),负责数据的存取
  • 3、选择器(Selector),是selectorChannel的多路复用,用于检测selectorChannel的状况。
@Test
    public void client() throws Exception{
        //获取SocketChannel,通过此通道将客户端的数据写给服务端
        SocketChannel clientChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 3000));
        //创建Buffer
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //创建FIleChannel,用于将本地文件中的数据写入缓存中。
        FileChannel fileChannel = new FileInputStream("src/file").getChannel();
        
        //fileChannel.read(buffer):从文件中读取数据,往buffer中写入数据
        while(fileChannel.read(buffer) != -1){
            buffer.flip();
            //clientChannel.write(buffer):从buffer中读取数据,往channel中写入数据
            clientChannel.write(buffer);
            buffer.clear();
        }
        clientChannel.shutdownOutput();
        
        //接受服务端的反馈
        int len = 0;
        while((len = clientChannel.read(buffer)) != -1){
            buffer.flip();
            System.out.println(new String(buffer.array(), 0, len));
            buffer.clear();
        }
        
    }
@Test
    public void server() throws Exception{
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        //绑定链接端口号
        serverChannel.bind(new InetSocketAddress(3000));
        //获取客户端链接的通道
        SocketChannel socketChannel = serverChannel.accept();
        
        //接受客户端的数据保存在本地
        //1、创建FIleChannel(将socket中的数据写入本地文件中)
        FileChannel fileChannel = new FileOutputStream("src/file7").getChannel();
        //创建buffer,
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        while(socketChannel.read(buffer) != -1){
            buffer.flip();
            fileChannel.write(buffer);
            buffer.clear();
        }
        buffer.put("请求收到".getBytes());
        buffer.flip();
        socketChannel.write(buffer);
        
    }
Socket非阻塞
@Test
    public void client() throws Exception{
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 3000));
        socketChannel.configureBlocking(false);
        
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        Scanner scanner = new Scanner(System.in);
        while(scanner.hasNext()){
            String str = scanner.next();
            buffer.put(str.getBytes());
            buffer.flip();
            socketChannel.write(buffer);
            buffer.clear();
        }
    }
    @Test
    public void server() throws Exception {
        // 获取通道
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        // 设置为非阻塞
        serverChannel.configureBlocking(false);
        // 绑定端口
        serverChannel.bind(new InetSocketAddress(3000));

        // 创建连接器
        Selector selector = Selector.open();
        // 将连接器注册到channel,并设置监听事件(接受事件)
        // SelectionKey.OP_CONNECT:链接状态
        // SelectionKey.OP_READ:读状态
        // SelectionKey.OP_WRITE:写状态
        // SelectionKey.OP_ACCEPT:接受状态,当接受准备就绪,开始进行下一步操作
        // 通过 | 进行链接可以监听多个状态
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);

        // 轮寻获取选择器上已经准备就绪的状态
        while (selector.select() > 0) {
            // 获取所有的监听Key
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = keys.iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                if (key.isAcceptable()) {
                    // 若获取状态就绪,就获取客户端的链接
                    SocketChannel clientChannel = serverChannel.accept();
                    // 将客户端的链接设置为非阻塞状态
                    clientChannel.configureBlocking(false);
                    // 给该通道注册到选择器上,并设置状态为读就绪
                    clientChannel.register(selector, SelectionKey.OP_READ);
                } else if (key.isReadable()) {
                    SocketChannel channel = (SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    int len = 0;
                    while ((len = channel.read(buffer)) > 0) {
                        buffer.flip();
                        System.out.println(new String(buffer.array(), 0, len));
                        buffer.clear();
                    }
                }
                // 取消处理完了的选择建
                iterator.remove();
            }
        }
    }
dataGram非阻塞
@Test
    public void client() throws Exception{
        DatagramChannel channel = DatagramChannel.open();
        channel.configureBlocking(false);
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        Scanner scanner = new Scanner(System.in);
        while(scanner.hasNext()){
            buffer.put(scanner.next().getBytes());
            buffer.flip();
            channel.send(buffer, new InetSocketAddress("127.0.0.1", 3000));
            buffer.clear();
        }
    }
@Test
    public void server() throws Exception{
        DatagramChannel channel = DatagramChannel.open();
        channel.configureBlocking(false);
        channel.bind(new InetSocketAddress(3000));
        
        Selector selector = Selector.open();
        channel.register(selector, SelectionKey.OP_READ);
        while(selector.select() > 0){
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = keys.iterator();
            while(iterator.hasNext()){
                SelectionKey key = iterator.next();
                if(key.isReadable()){
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    channel.receive(buffer);
                    buffer.flip();
                    System.out.println(new String(buffer.array(), 0, buffer.limit()));
                }
                iterator.remove();
            }
        }
    }

管道
/**
     * 可以写入管道和读取管道放入到不同的线程中
     * @throws Exception
     */
    @Test
    public void client() throws Exception{
        
        //获取管道
        Pipe pipe = Pipe.open();
        //将缓冲区中的数据写入管道
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        buffer.put("发送数据".getBytes());
        buffer.flip();
        
        SinkChannel sink = pipe.sink();
        sink.write(buffer);
        
        buffer.clear();
        //读取缓冲区中的数据
        SourceChannel source = pipe.source();
        int len = source.read(buffer);
        System.out.println(new String(buffer.array(), 0, len));
        
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • 转自 http://www.ibm.com/developerworks/cn/education/java/j-...
    抓兔子的猫阅读 2,272评论 0 22
  • Java NIO(New IO)是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java I...
    JackChen1024阅读 7,533评论 1 143
  • 前言: 之前的文章《Java文件IO常用归纳》主要写了Java 标准IO要注意的细节和技巧,由于网上各种学习途径,...
    androidjp阅读 2,892评论 0 22
  • (转载说明:本文非原创,转载自http://ifeve.com/java-nio-all/) Java NIO: ...
    数独题阅读 799评论 0 3
  • (转载说明:本文非原创,转载自http://ifeve.com/java-nio-all/) Java NIO: ...
    柳岸阅读 814评论 0 3