• NIO详解


    NIO

    前言

    NIO即New IO,这个库是在JDK1.4中才引入的。NIO和IO有相同的作用和目的,但实现方式不同,NIO主要用到的是块,所以NIO的效率要比IO高很多。在Java API中提供了两套NIO,一套是针对标准输入输出NIO,另一套就是网络编程NIO。

    IO与NIO的区别

    • IO流是面向流的,属于阻塞IO,没有选择器。
    • NIO流是面向块(缓冲区),属于非阻塞IO有选择器。
      其中最大的区别就是一个面向流,一个面向缓冲区。
      • 面向流:IO流每次读取一个字节或者多个,直到读完为止。不能对流中的数据进行操作。
      • 面向缓冲区:NIO流是将数据放到一个缓冲区,需要时可以对缓冲区中的数据进行操作,这样就可以更加灵活的操作数据了。(就好像IO流是一根水管,而NIO是一个运水车)

    Buffer(缓冲区)

    Buffer是一个抽象类;
    子类有:ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer
    核心类(常用类):ByteBufferCharBuffer 其中ByteBuffer有一个子类MappedByteBufferMappedByteBuffer类能够将文件直接映射到内存中,那么这样我们就可以像访问内存一样访问文件,非常方便)

    创建Buffer

    因为Buffer都是抽象类,无法直接实例化。创建缓冲区要调用XxxBuffer allocate(int capacity),XxxBuffer allocateDirect(int capacity),参数是缓冲区容量。

    eg:获取ByteBuffer
    static ByteBuffer allocate(int capacity)
    分配一个新的字节缓冲区(普通Buffer)
    static ByteBuffer allocateDirect(int capacity)
    分配新的直接字节缓冲区(直接Buffer)

    二者的区别:

    1. 创建普通Buffer成本低,读写的效率不高
    2. 因为创建直接Buffer成本高,所以我们一般用在Buffer生存周期较长的时候使用
    3. 只有ByteBuffer才能够创建直接Buffer,其他的Buffer对象是不能够创建
    4. 如果创建了直接Buffer但是我又想要使用其他Buffer的功能,可以将ByteBuffer转换成其他Buffer
    Buffer参数
    • capacity(容量):缓冲区的容量,不可以为负数,一旦创建了就不能够改变
    • limit(界限):是缓冲区读写数据的终止点,limit之后的区域无法访问
    • position(起始指针):是缓冲区读写数据的起始点,初始值为0。position随着数据的加入而改变,例如读取2个数据到Buffer中,则position = 2
    • mark(标记):该索引能够用于下次读取或者写入,mark在0~position之间,设置该值就会把position移动到mark处

    Buffer方法
    • flip():读取模式;确定缓冲区数据的起始点和终止点,为输出数据做准备(即写入通道), 将limit的值改为postion的值,同时将postion归0
      • 特点: 就是为下一次数据的读取做好准备
    • clear():写入模式;缓冲区初始化,准备再次接收新数据到缓冲区,将limit改为capacity的值,同时将postion归0
      • 特点: 就是为下一次数据的写入做好准备
    • get()和put():获取元素和存放元素。使用clear()之后,无法直接使用get()获取元素,需要使用get(int index)根据索引值来获取相应元素
    • hasRemaining():判断postion到limit之间是否还有元素。
    	public static  void main(String[] args) {
    		CharBuffer buffer = CharBuffer.allocate(8);
    		// Buffer已经准备好了向Buffer中写数据    写模式
    		System.out.println("capacity:" + buffer.capacity()); // 8
    		System.out.println("limit:" + buffer.limit()); // 8
    		System.out.println("position:" + buffer.position()); // 0	
    		buffer.put('a');
    		buffer.put('b');
    		buffer.put('c');
    		System.out.println("------------------------");	
    		System.out.println("capacity:" + buffer.capacity()); // 8
    		System.out.println("limit:" + buffer.limit()); // 8
    		System.out.println("position:" + buffer.position()); // 3	
    		System.out.println("------------------------");
    		// 切换模式  ,limit变为position的位置然后将position变为0
    		buffer.flip();
    		System.out.println("capacity:" + buffer.capacity()); // 8
    		System.out.println("limit:" + buffer.limit()); // 3
    		System.out.println("position:" + buffer.position()); // 0
    		System.out.println("------------------------");
            	System.out.println("------------------");
    		buffer.clear(); // 将postion 清 0 ,将limit = capacity	
    		System.out.println("capacity:" + buffer.capacity()); // 8
    		System.out.println("limit:" + buffer.limit()); // 8
    		System.out.println("position:" + buffer.position()); // 0
    		// 注意: 调用clear方法只是将读模式改为写模式,并不会清空缓冲区的数据
    
    	}
    

    Channel(通道)

    Channel原理类似于传统的流对象,区别在于:
    1.Channel能够将指定的部分或者全部文件映射到内存中
    2.程序如果想要读取Channel中的数据,不能够直接读写,必须经过Buffer
    简单来说:Channel通过Buffer(缓冲区)进行读写操作。read()表示读取通道数据到缓冲区,write()表示把缓冲区数据写入到通道。

    Channel实现类
    • FileChannel 和文件相关的通道
    • DatagramChannel 和UDP协议传输数据相关的通道
    • SocketChannel 和TCP协议相关的数据传输通道
    • ServerSocket 和TCP协议相关的数据传输通道
    • Pipe.SinkChannel、Pipe.SourceChannel //线程通信管道传输数据
    Channel常用方法
    • read() : 将Channel中的数据读取到Buffer中
    • write() : 向Buffer中写入数据
    • map(): 将channel中的数据全部或者部分映射到Buffer中(MappedByteBuffer,本质也是一个ByteBuffer),map()方法参数(读写模式,映射起始位置,数据长度)。
      • inChannel.map(mode, position, size)
      • MappedByteBuffer mappBuffer = inChannel.map(MapMode.READ_ONLY, 0, srcFile.length());
    public static void main(String[] args) throws IOException {
    		File srcFile = new File("nio-a.txt");
    		FileInputStream fis = new FileInputStream(srcFile);
    		FileOutputStream fos = new FileOutputStream(new File("nio-b.txt"));
    		
    		// 获取Channel对象
    		FileChannel inChannel = fis.getChannel();
    		FileChannel outChannel = fos.getChannel();
    		// 获取MapByteBuffer对象
    		MappedByteBuffer mapBuffer = inChannel.map(MapMode.READ_ONLY, 0, srcFile.length());
    		//字符集解码器	
    		Charset charset = Charset.forName("GBK");
    		outChannel.write(mapBuffer);
    		CharsetDecoder decoder = charset.newDecoder();
    		CharBuffer charBuffer = decoder.decode(mapBuffer);
    		System.out.println(charBuffer);
    		
    		
    	}
    
    

    通道可以异步读写,异步读写表示通道执行读写操作时,也能做别的事情,解决线程阻塞。如果使用文件管道(FileChannel),建议用RandomAccessFile来创建管道,因为该类支持读写模式以及有大量处理文件的方法。

    public static void main(String[] args) throws Exception {
    		File f = new File("nio-a.txt");
    		
    		RandomAccessFile raf = new RandomAccessFile(f, "rw");
    		
    		FileChannel channel = raf.getChannel();
    		
    		MappedByteBuffer mapBuffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, f.length());
    		
    //		raf.seek(f.length());
    		channel.position(f.length());
    		
    		channel.write(mapBuffer);
    	}
    
    

    Charset(字符集)

    理解为现实生活的编码表对象
    当使用NIO来获取文件内容时,如果是文本数据,那么需要进行转码,才能查看正确内容,这就需要解码器。 如果要把字符数据写入文件,需要将CharBuffer转码成ByteBuffer,这就需要编码器。

    • 包含了字节和 Unicode 字符之间转换的 charset,还定义了用于创建解码器和编码器以及获取与 charset 关联的各种方法
    • CharsetDecoder(解码器):把字节转成字符,例如查看文本数据,需要转成字符才能查看,如果是字节,就看不懂了。
    • CharsetEncoder(编码器):把字符转成字节,才能被计算机理解。 因为字节是计算机最小的存储单位,所以Channel的IO操作都与ByteBuffer有关
    • 解码器和编码器都不能直接创建,需要一个Charset对象来创建对应的解码器和编码器。
    Charset常用方法
    • forName():根据传入的字符集获得对应的字符集对象。
    • defaultCharset():获得当前使用的默认字符集。
    • availableCharsets():获得所有有效的字符集。

    NIO遍历文件

    public static void main(String[] args) throws IOException {
        		//匿名子对象实现FileVisitor接口
    		FileVisitor<Path> visitor = new SimpleFileVisitor<Path>() {
    			@Override
    			public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException {
    				System.out.println("正在访问" + path + "文件");
    				if(path.endsWith("NIODemo.java")){
    					System.out.println("恭喜您找到Java");
    					return FileVisitResult.CONTINUE;
    				}
    				return FileVisitResult.CONTINUE;
    			}
    			
    			@Override
    			public FileVisitResult preVisitDirectory(Path path, BasicFileAttributes attrs) throws IOException {
    				System.out.println("准备访问" + path + "文件");
    				return FileVisitResult.CONTINUE;
    			}
    			
    			@Override
    			public FileVisitResult visitFileFailed(Path path, IOException exc) throws IOException {
    				System.out.println("准备访问" + path + "文件失败");
    				System.out.println(exc.getMessage());
    				return FileVisitResult.CONTINUE;
    			}
    		};
    			//访问文件树
    		Files.walkFileTree(Paths.get("D:\JavaSE"), visitor);
    		
    	}
    
    

    以上

    @Fzxey

  • 相关阅读:
    通过IP地址和子网掩码与运算计算相关地址
    IP地址与子网掩码的计算
    win10用键盘控制鼠标
    requirements.txt
    vue中axios使用二:axios以post,get,jsonp的方式请求后台数据
    vue中axios使用一:axios做拦截器
    git切换分支冲突解决-删除分支
    获取指定月份前的第一天和最后一天及两个日期之间的月份列表
    git远程版本回退
    git Please move or remove them before you can merge
  • 原文地址:https://www.cnblogs.com/fzxey/p/10828465.html
Copyright © 2020-2023  润新知