Java NIO 学习总结(一)

Java NIO 学习总结(一)
主要内容:
    1.IO与NIO的区别
    2.NIO的缓冲区数据存取操作
    3.通道(Channel)的原理与获取
    4.通道的数据传输与内存映射文件
    5.分散读取与聚集写入
    6.字符集Charset

一、IO与NIO的区别
    1.NIO与原IO有同样的作用和目的,但使用方式完全不同,NIO支持面向缓冲区,基于通道的IO操作,NIO读写更加高效。其主要区别如下:
     
    带着问题:非阻塞如何理解? 非阻塞与选择器 主要是针对 网络IO通信的
    2.小结:
        IO:是单向传输数据管道,既负责连接,也负责数据传输;
        NIO:通道->是双向连接(铁路),但不负责数据的存取;缓冲区->书传输数据的载体(铁路上的火车),负责数据的存取,分工更细;
二、NIO的缓冲区数据存取操作
    1.缓冲区,在JAVA NIO中负责数据的存取,其底层实现就是数组,不同的数据类型,提供了不同的缓冲区,如:ByteBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DubbleBuffer、CharBuffer,而没有boolean类型的Buffer

package com.jupiter.test;

import java.lang.String;
import org.junit.Test;

import java.nio.Buffer;
import java.nio.ByteBuffer;

/**
 * @author Jupiter
 * @date 2019/3/2-11:15
 * @description 简单的buffer操作
 */
public class TestBuffer {

    /*
     * 1.缓冲区的两个核心方法:
     *   put() : 存入数据到缓冲区中
     *   get() : 从缓冲区中读数据
     * 2.缓冲区中的四个核心属性
     *   capacity : 表示缓冲区的大小,最大存储数据量,一旦申明不能改变
     *   limit : 缓冲区中可以操作数据的大小
     *   position : 表示正在操作缓冲区中数据的位置
     *   mark : 标记当前position的位置,可以通过reset方法恢复到mark的位置
     *   大小 : 0 <= mark <= position <= limit <=capacity
     */

    @Test
    public void test1(){

        String src = "abcde";
        //1. 创建一个指定大小的buffer
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        System.out.println("---------------allocate()-------------");
        System.out.println(buffer.position());
        System.out.println(buffer.limit());
        System.out.println(buffer.capacity());

        //2. 利用put将数据存入到缓冲区中
        buffer.put(src.getBytes());//capacity=1024,limit=1024,position=5

        System.out.println("---------------put()-------------");
        System.out.println(buffer.position());
        System.out.println(buffer.limit());
        System.out.println(buffer.capacity());

        //3. 切换buffer为读模式
        buffer.flip();//capacity=1024,limit=5,position=0

        System.out.println("---------------flip()-------------");
        System.out.println(buffer.position());
        System.out.println(buffer.limit());
        System.out.println(buffer.capacity());

        //4. 利用get读取缓冲区数据
        //新建目标字节数组
        byte[] target = new byte[buffer.limit()];
        //取数据放到目标字节数组中
        buffer.get(target, 0, buffer.limit());//capacity=1024,limit=5,position=5

        System.out.println("---------------get()-------------");
        System.out.println(buffer.position());
        System.out.println(buffer.limit());
        System.out.println(buffer.capacity());

        //5. 可重复读
        buffer.rewind();

        System.out.println("---------------rewind()-------------");
        System.out.println(buffer.position());
        System.out.println(buffer.limit());
        System.out.println(buffer.capacity());

        //6. 清空缓冲区,但是缓冲区的数据依然存在!只不过出于“被遗忘状态”
        buffer.clear();

        System.out.println("---------------clear()-------------");
        System.out.println(buffer.position());
        System.out.println(buffer.limit());
        System.out.println(buffer.capacity());

        //检验数据是否还存在
        System.out.println("---------------取出被“清空”后的buffer中的第一个char字符-------------");
        System.out.println((char)buffer.get());
    }
    
    @Test
    public void test2(){

        String src = "abcde";
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        byteBuffer.put(src.getBytes());

        byteBuffer.flip();//切换到读模式
        byte[] target = new byte[byteBuffer.limit()];
        byteBuffer.get(target, 0, 2);//读取buffer中的前两位

        System.out.println(byteBuffer.position());//当前position
        byteBuffer.mark();//标记

        byteBuffer.get(target, 2, 2);//读取buffer中的三四位
        System.out.println(byteBuffer.position());//当前position=4

        byteBuffer.reset();//跳回到标记的地方
        System.out.println(byteBuffer.position());//当前position=mark=2
    }
}

    2.NIO中的直接缓冲区与非直接缓冲区
        非直接缓冲区:通过allocate()方法,在JVM的堆内存中分配缓冲区
        直接缓冲区:通过allocate()方法,直接在操作系统物理内存中开辟缓冲区
        使用直接缓冲区,有些时候可以提高效率,原理:
            1)非直接缓冲区操作数据时,操作系统OS有一个缓冲区,JVM中也有一个缓冲区,这样程序与磁盘之间交互数据时,会有缓冲区与缓冲区的copy操作,而直接缓冲区是直接应用程序在物理内存中开辟一个缓冲区,省去了copy数据的操作,从而提高了效率。
            2)但是直接缓冲区有弊端:应用程序写数据到直接缓冲区后,物理内存中的直接缓冲区将又操作系统OS控制;物理内存中开辟缓冲区开销大,如果物理内存中的数据不能及时处理,操作系统会停机。
三、通道的原理与获取、通道的数据传输与内存映射文件、分散读取与聚集写入、字符集Charset

package com.jupiter.test;

import org.junit.Test;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

/**
 * @author Jupiter
 * @date 2019/3/4-22:20
 * @description 简单的Channel操作
 */
public class TestChannel {

    /*
     * 一.通道是连接。通道本身不负责传输,而是配合缓冲区传输数据
     * 二.通道的主要实现类:
     *   FileChannel
     *   SocketChannel
     *   ServerSocketChannel
     *   DatagramChannel
     * 三.获取通道
     *   1.Java针对支持通道的类,提供了getChannel()方法
     *      本地IO:
     *          FileInputStream/FileOutputStream
     *          RandomAccessFile
     *      网络IO:
     *          Socket
     *          ServerSocket
     *          DatagramSocket
     *   2.Java1.7之后的各通道实现类的open()静态方法
     *   3.Java1.7之后的Files工具类的newByteChannel()方法
     *
     * 四.通道之间的数据传输
     *   transferFrom()
     *   transferTo()
     * 五.分散(scatter)与聚集(Gather)
     *   1.分散读取:与通道读取多个加载数据的buffer
     *   2.聚集写入:将多个缓冲区的数据聚集到通道中
     * 六.字符集:Charset
     *   1.编码:字符串 -> 字节数组
     *   2.解码:字节数组 -> 字符串
     */

    /**
     * @description 利用通道+非直接缓冲区完成文件复制
     */
    @Test
    public void test1() throws Exception {

        //文件流对象相当于只指定了文件传输的起始与终点
        FileInputStream fis = new FileInputStream("o:/1.flv");
        FileOutputStream fos = new FileOutputStream("o:/2.flv");

        //获取通道
        FileChannel inChannel = fis.getChannel();
        FileChannel outChannel = fos.getChannel();

        //创建缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        //将通道中的数据写入缓冲区,数据装车
        while ((inChannel.read(buffer) != -1)) {
            //切换buffer为读数据模式
            buffer.flip();
            //数据下车
            outChannel.write(buffer);
            buffer.clear();//清空buffer
        }
        fis.close();
        inChannel.close();
        fos.close();
        outChannel.close();

    }

    /**
     * @description 利用 FileChannel+内存映射文件 复制文件
     */
    @Test
    public void test2() throws IOException {
        //创建两条通道:读数据通道、写数据通道
        FileChannel inChannel = FileChannel.open(Paths.get("o:/1.flv"), StandardOpenOption.READ);
        //CREATE_NEW:当对象存在就会报错;CREATE:当对象存在就不操作
        FileChannel outChannel = FileChannel.open(Paths.get("o:/2.flv"), StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE);

        //内存映射文件:建立内存映射冲区 读数据的一个缓冲区、写数据的一个缓冲区
        MappedByteBuffer inMappedBuf = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
        MappedByteBuffer outMappedBuf = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, outChannel.size());

        //直接对缓冲区进行数据的读写操作
        byte[] dst = new byte[inMappedBuf.limit()];//在JVM堆内存中创建与内存映射相等大小的字节数组对象:大对象开销很大
        inMappedBuf.get(dst);//将缓冲区的数据读到堆内存
        outMappedBuf.put(dst);//将堆内存的数据写到缓冲区

        inChannel.close();
        outChannel.close();
    }

    /**
     * @description 利用直接缓冲区+通道完成文件复制
     */
    @Test
    public void test3() throws IOException {
        FileChannel inChannel = FileChannel.open(Paths.get("o:/1.flv"), StandardOpenOption.READ);
        FileChannel outChannel = FileChannel.open(Paths.get("o:/2.flv"), StandardOpenOption.CREATE_NEW, StandardOpenOption.READ, StandardOpenOption.WRITE);

        //调用channel的transferFrom/To方法直接操作物理内存
        outChannel.transferFrom(inChannel, 0, inChannel.size());
        inChannel.close();
        outChannel.close();
    }

    /**
     * @description 分散读取与聚集写入
     */
    @Test
    public void test4() throws IOException {

        //1.txt内容:abcdefg
        RandomAccessFile file = new RandomAccessFile("o:/1.txt","rw" );

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

        //分配两个缓冲区
        ByteBuffer buffer1 = ByteBuffer.allocate(5);
        ByteBuffer buffer2 = ByteBuffer.allocate(1024);

        //分散读取
        ByteBuffer[] bufs = {buffer1, buffer2};
        channel.read(bufs);
        for (ByteBuffer buffer: bufs)
            buffer.flip(); // 每个buffer都切换到读模式

        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 outFile = new RandomAccessFile("o:/2.txt", "rw");
        //获取写出数据的channel
        FileChannel outChannel = outFile.getChannel();

        outChannel.write(bufs);

        file.close();
        channel.close();
        outFile.close();
        outChannel.close();
    }
    /**
     * @description 测试编码
     */
    @Test
    public void test5() throws CharacterCodingException {
        Charset cs = Charset.forName("UTF-8");

        //获取编码器
        CharsetEncoder charsetEncoder = cs.newEncoder();
        //获取解码器
        CharsetDecoder charsetDecoder = cs.newDecoder();

        CharBuffer cBuffer = CharBuffer.allocate(1024);
        cBuffer.put("你好");

        cBuffer.flip();

        //编码
        ByteBuffer buffer = charsetEncoder.encode(cBuffer);
        for (int i=0; i<"你好".getBytes().length; i++){
            System.out.println(buffer.get());
        }

        //解码
        buffer.flip();
        CharBuffer result = charsetDecoder.decode(buffer);
        System.out.println(result);

        buffer.rewind();
        Charset cs2 = Charset.forName("UTF-8");
        CharsetDecoder charsetDecoder1 = cs2.newDecoder();
        CharBuffer result2 = charsetDecoder1.decode(buffer);
        System.out.println(result2);//正常输出

        buffer.rewind();
        Charset cs3 = Charset.forName("GBK");
        CharsetDecoder charsetDecoder2 = cs3.newDecoder();
        CharBuffer result3 = charsetDecoder2.decode(buffer);
        System.out.println(result3);//乱码输出
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值