• 第十二章 NIO


    12、NIO
    12.1 Java NIO 概述 1课时
    12.2 Java NIO.2 之Path、Paths 与 Files 的使用 1课时
    12.3 自动资源管理 1课时
    12.4 缓冲区(Buffer) 1课时
    12.5 通道(Channel)与文件通道(FileChannel) 1课时
    12.6管道(Pipe)中的SinkChannel和SourceChannel 1课时
    12.7 字符集(Charset) 1课时
    ##12-1 Java NIO 概述
    Java NIO 概述
    • Java NIO (New IO,Non-Blocking IO)是从Java 1.4版本开始引入的一套新的IO API,可以替代标准的Java IO API。NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的、基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作。
    NIO. 2
    • 随着 JDK 7 的发布,Java对NIO进行了极大的扩展,增强了对文件处理和文件系统特性的支持,以至于我们称他们为 NIO.2。因为 NIO 提供的一些功能,NIO已经成为文件处理中越来越重要的部分。
    Java NIO 与 IO 的主要区别
    <tr>
    	<td >IO</td>    
        <td >NIO</td>  
    </tr>
    <tr>
    	<td >面向流(Stream Oriented):单向的</td>    
        <td >面向缓冲区(Buffer Oriented):通道是单向的,也可以是双向的</td>  
    </tr>
    <tr>
    	<td >阻塞IO(Blocking IO)</td>    
        <td >非阻塞IO(Non Blocking IO)</td>  
    </tr>
    <tr>
    	<td >(无)</td>    
        <td >选择器(Selectors)</td>  
    </tr>
    
    ##12-2 NIO.2 之 Path、Paths、Files的使用
    Path接口
    • Path 常用方法:

      • String toString() : 返回调用 Path 对象的字符串表示形式
      • boolean startsWith(String path) : 判断是否以 path 路径开始
      • boolean endsWith(String path) : 判断是否以 path 路径结束
      • boolean isAbsolute() : 判断是否是绝对路径
      • Path getParent() :返回Path对象包含整个路径,不包含 Path 对象指定的文件路径
      • Path getRoot() :返回调用 Path 对象的根路径
      • Path getFileName() : 返回与调用 Path 对象关联的文件名
      • Path getName(int idx) : 返回指定索引位置 idx 的路径名称
      • int getNameCount() : 返回Path 根目录后面元素的数量
      • Path toAbsolutePath() : 作为绝对路径返回调用 Path 对象
      • Path resolve(Path p) :合并两个路径,返回合并后的路径对应的Path对象
      • File toFile(): 将Path转化为File类的对象
    • java.nio.file.Files 用于操作文件或目录的工具类。

    • Files常用方法:

      • Path copy(Path src, Path dest, CopyOption … how) : 文件的复制
      • Path createDirectory(Path path, FileAttribute<?> … attr) : 创建一个目录
      • Path createFile(Path path, FileAttribute<?> … arr) : 创建一个文件
      • void delete(Path path) : 删除一个文件,如果不存在,执行报错
      • void deleteIfExists(Path path) : Path对应的文件如果存在,执行删除
      • Path move(Path src, Path dest, CopyOption…how) : 将 src 移动到 dest 位置
      • long size(Path path) : 返回 path 指定文件的大小
    • Files常用方法:用于判断

      • boolean exists(Path path, LinkOption … opts) : 判断文件是否存在
      • boolean isDirectory(Path path, LinkOption … opts) : 判断是否是目录
      • boolean isRegularFile(Path path, LinkOption … opts) : 判断是否是文件
      • boolean isHidden(Path path) : 判断是否是隐藏文件
      • boolean isReadable(Path path) : 判断文件是否可读
      • boolean isWritable(Path path) : 判断文件是否可写
      • boolean notExists(Path path, LinkOption … opts) : 判断文件是否不存在
    • Files常用方法:用于操作内容

      • SeekableByteChannel newByteChannel(Path path, OpenOption…how) : 获取与指定文件的连接,how 指定打开方式。
      • DirectoryStream newDirectoryStream(Path path) : 打开 path 指定的目录
      • InputStream newInputStream(Path path, OpenOption…how):获取 InputStream 对象
      • OutputStream newOutputStream(Path path, OpenOption…how) : 获取 OutputStream 对象

    Path案例

    /**
     * 1. jdk 7.0 时,引入了 Path、Paths、Files三个类。
     * 2.此三个类声明在:java.nio.file包下。
     * 3.Path可以看做是java.io.File类的升级版本。也可以表示文件或文件目录,与平台无关
     * 
     * 4.如何实例化Path:使用Paths.
     * 	 static Path get(String first, String … more) : 用于将多个字符串串连成路径
     *   static Path get(URI uri): 返回指定uri对应的Path路径
     */
    public class PathTest {
    	
    	//Path中的常用方法
    	@Test
    	public void test2(){
    		Path path1 = Paths.get("d:\", "nio\nio1\nio2\hello.txt");
    		Path path2 = Paths.get("hello.txt");
    		
    //		String toString() : 返回调用 Path 对象的字符串表示形式
    		System.out.println(path1);
    		
    //		boolean startsWith(String path) : 判断是否以 path 路径开始
    		System.out.println(path1.startsWith("d:\nio"));
    //		boolean endsWith(String path) : 判断是否以 path 路径结束
    		System.out.println(path1.endsWith("hello.txt"));
    //		boolean isAbsolute() : 判断是否是绝对路径
    		System.out.println(path1.isAbsolute() + "~");
    		System.out.println(path2.isAbsolute() + "~");
    //		Path getParent() :返回Path对象包含整个路径,不包含 Path 对象指定的文件路径
    		System.out.println(path1.getParent());
    		System.out.println(path2.getParent());
    //		Path getRoot() :返回调用 Path 对象的根路径
    		System.out.println(path1.getRoot());
    		System.out.println(path2.getRoot());
    //		Path getFileName() : 返回与调用 Path 对象关联的文件名
    		System.out.println(path1.getFileName() + "~");
    		System.out.println(path2.getFileName() + "~");
    //		int getNameCount() : 返回Path 根目录后面元素的数量
    //		Path getName(int idx) : 返回指定索引位置 idx 的路径名称
    		for(int i = 0;i < path1.getNameCount();i++){
    			System.out.println(path1.getName(i) + "*****");
    		}
    		
    //		Path toAbsolutePath() : 作为绝对路径返回调用 Path 对象
    		System.out.println(path1.toAbsolutePath());
    		System.out.println(path2.toAbsolutePath());
    //		Path resolve(Path p) :合并两个路径,返回合并后的路径对应的Path对象
    		Path path3 = Paths.get("d:\", "nio");
    		Path path4 = Paths.get("nioo\hi.txt");
    		path3 = path3.resolve(path4);
    		System.out.println(path3);
    		
    //		File toFile(): 将Path转化为File类的对象
    		File file = path1.toFile();//Path--->File的转换
    		
    		Path newPath = file.toPath();//File--->Path的转换
    		
    	}
    	
    	//如何使用Paths实例化Path
    	@Test
    	public void test1(){
    		Path path1 = Paths.get("d:\nio\hello.txt");//new File(String filepath)
    		
    		Path path2 = Paths.get("d:\", "nio\hello.txt");//new File(String parent,String filename);
    		
    		System.out.println(path1);
    		System.out.println(path2);
    		
    		Path path3 = Paths.get("d:\", "nio");
    		System.out.println(path3);
    	}
    }
    

    Files案例

    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.nio.channels.SeekableByteChannel;
    import java.nio.file.DirectoryStream;
    import java.nio.file.Files;
    import java.nio.file.LinkOption;
    import java.nio.file.Path;
    import java.nio.file.Paths;
    import java.nio.file.StandardCopyOption;
    import java.nio.file.StandardOpenOption;
    import java.util.Iterator;
    
    import org.junit.Test;
    /**
     * Files工具类的使用:操作文件或目录的工具类
     *
     */
    public class FilesTest {
    	
    	
    	/**
    	 * StandardOpenOption.READ:表示对应的Channel是可读的。
    	 * StandardOpenOption.WRITE:表示对应的Channel是可写的。
    	 * StandardOpenOption.CREATE:如果要写出的文件不存在,则创建。如果存在,忽略
    	 * StandardOpenOption.CREATE_NEW:如果要写出的文件不存在,则创建。如果存在,抛异常
    	 */
    	@Test
    	public void test3() throws IOException{
    //		SeekableByteChannel newByteChannel(Path path, OpenOption…how) : 获取与指定文件的连接,how 指定打开方式。
    		Path path1 = Paths.get("d:\nio", "hello.txt");
    		SeekableByteChannel channel = Files.newByteChannel(path1, StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE);
    		
    //		DirectoryStream<Path>  newDirectoryStream(Path path) : 打开 path 指定的目录
    		Path path2 = Paths.get("e:\teach");
    		DirectoryStream<Path> directoryStream = Files.newDirectoryStream(path2);
    		Iterator<Path> iterator = directoryStream.iterator();
    		while(iterator.hasNext()){
    			System.out.println(iterator.next());
    		}
    		
    //		InputStream newInputStream(Path path, OpenOption…how):获取 InputStream 对象
    		InputStream inputStream = Files.newInputStream(path1, StandardOpenOption.READ);
    		
    //		OutputStream newOutputStream(Path path, OpenOption…how) : 获取 OutputStream 对象
    		OutputStream outputStream = Files.newOutputStream(path1, StandardOpenOption.WRITE,StandardOpenOption.CREATE);
    		
    	}
    	
    	@Test
    	public void test2() throws IOException{
    		Path path1 = Paths.get("d:\nio", "hello.txt");
    		Path path2 = Paths.get("xxx.txt");
    //		boolean exists(Path path, LinkOption … opts) : 判断文件是否存在
    		System.out.println(Files.exists(path2, LinkOption.NOFOLLOW_LINKS));
    		
    //		boolean isDirectory(Path path, LinkOption … opts) : 判断是否是目录
    		//不要求此path对应的物理文件存在。
    		System.out.println(Files.isDirectory(path1, LinkOption.NOFOLLOW_LINKS));
    		
    //		boolean isRegularFile(Path path, LinkOption … opts) : 判断是否是文件
    		
    //		boolean isHidden(Path path) : 判断是否是隐藏文件
    		//要求此path对应的物理上的文件需要存在。才可判断是否隐藏。否则,抛异常。
    //		System.out.println(Files.isHidden(path1));
    		
    //		boolean isReadable(Path path) : 判断文件是否可读
    		System.out.println(Files.isReadable(path1));
    //		boolean isWritable(Path path) : 判断文件是否可写
    		System.out.println(Files.isWritable(path1));
    //		boolean notExists(Path path, LinkOption … opts) : 判断文件是否不存在
    		System.out.println(Files.notExists(path1, LinkOption.NOFOLLOW_LINKS));
    	}
    	
    	@Test
    	public void test1() throws IOException{
    		Path path1 = Paths.get("d:\nio", "hello.txt");
    		Path path2 = Paths.get("xxx.txt");
    		
    //		Path copy(Path src, Path dest, CopyOption … how) : 文件的复制
    		//要想复制成功,要求path1对应的物理上的文件存在。path1对应的文件没有要求。
    //		Files.copy(path1, path2, StandardCopyOption.REPLACE_EXISTING);
    		
    //		Path createDirectory(Path path, FileAttribute<?> … attr) : 创建一个目录
    		//要想执行成功,要求path对应的物理上的文件目录不存在。一旦存在,抛出异常。
    		Path path3 = Paths.get("d:\nio\nio1");
    //		Files.createDirectory(path3);
    		
    //		Path createFile(Path path, FileAttribute<?> … arr) : 创建一个文件
    		//要想执行成功,要求path对应的物理上的文件不存在。一旦存在,抛出异常。
    		Path path4 = Paths.get("d:\nio\hi.txt");
    //		Files.createFile(path4);
    		
    //		void delete(Path path) : 删除一个文件/目录,如果不存在,执行报错
    //		Files.delete(path4);
    		
    //		void deleteIfExists(Path path) : Path对应的文件/目录如果存在,执行删除.如果不存在,正常执行结束
    		Files.deleteIfExists(path3);
    		
    //		Path move(Path src, Path dest, CopyOption…how) : 将 src 移动到 dest 位置
    		//要想执行成功,src对应的物理上的文件需要存在,dest对应的文件没有要求。
    //		Files.move(path1, path2, StandardCopyOption.ATOMIC_MOVE);
    		
    //		long size(Path path) : 返回 path 指定文件的大小
    		long size = Files.size(path2);
    		System.out.println(size);
    
    	}
    }
    
    Path、Paths和Files核心API

    早期的java只提供了一个File类来访问文件系统,但File类的功能比较有限,所提供的方法性能也不高。而且,大多数方法在出错时仅返回失败,并不会提供异常信息。

    NIO. 2为了弥补这种不足,引入了Path接口,代表一个平台无关的平台路径,描述了目录结构中文件的位置。Path可以看成是File类的升级版本,实际引用的资源也可以不存在。

    在以前IO操作都是这样写的:

    import java.io.File;
    File file = new File("index.html");
    

    但在Java7 中,我们可以这样写:

    import java.nio.file.Path; 
    import java.nio.file.Paths; 
    Path path = Paths.get("index.html");
    

    同时,NIO.2还提供了Files、Paths工具类,Files包含了大量静态的工具方法来操作文件;Paths则包含了两个返回Path的静态工厂方法。

    • Paths 类提供的静态 get() 方法用来获取 Path 对象:

        static Path get(String first, String … more) : 用于将多个字符串串连成路径
        static Path get(URI uri): 返回指定uri对应的Path路径
      
    URI、URL和URN的区别

    URI,是uniform resource identifier,统一资源标识符,用来唯一的标识一个资源。而URL是uniform resource locator,统一资源定位符,它是一种具体的URI,即URL可以用来标识一个资源,而且还指明了如何locate这个资源。而URN,uniform resource name,统一资源命名,是通过名字来标识资源,比如mailto:java-net@java.sun.com。也就是说,URI是以一种抽象的,高层次概念定义统一资源标识,而URL和URN则是具体的资源标识的方式。URL和URN都是一种URI。

    在Java的URI中,一个URI实例可以代表绝对的,也可以是相对的,只要它符合URI的语法规则。而URL类则
    不仅符合语义,还包含了定位该资源的信息,
    因此它不能是相对的。

    12-3 自动资源管理

    自动资源管理案例

    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.nio.channels.SeekableByteChannel;
    import java.nio.file.Files;
    import java.nio.file.Paths;
    import java.nio.file.StandardOpenOption;
    
    import org.junit.Test;
    
    /**
     * 
     * jdk 7 提供基于try-catch的自动资源管理
    
     */
    public class ARMTest {
    	
    	/**
    	 * 能够实现资源的自动关闭,需要满足:
    	 * 1.可以在一条 try 语句中管理多个资源,每个资源以“;” 隔开即可。
    	 * 2.需要关闭的资源,必须实现了 AutoCloseable 接口或其子接口 Closeable
    	 * 
    	 * 目的:不需要再使用finally,显式的关闭资源了。
    	 * 
    	 */
    	@Test
    	public void test2(){
    		try(
    				FileInputStream fis = new FileInputStream("xxx.txt");
    				FileOutputStream fos = new FileOutputStream("abc.txt");
    				SeekableByteChannel channel = Files.newByteChannel(Paths.get("d:\nio", "hello.txt"), StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE)
    			)
    		{
    			//可能出现异常的代码
    			int data;
    			while((data = fis.read()) != -1){
    				System.out.println((char)data);
    			}
    		}catch(Exception e){
    			e.printStackTrace();
    		}
    	}
    	
    	@Test
    	public void test1() {
    		
    		FileInputStream fis = null;
    		try {
    			fis = new FileInputStream("xxx.txt");
    			int data;
    			while((data = fis.read()) != -1){
    				System.out.println((char)data);
    			}
    		} catch (Exception e) {
    			e.printStackTrace();
    		}finally{
    			if(fis != null){
    				try {
    					fis.close();
    				} catch (IOException e) {
    					e.printStackTrace();
    				}
    				
    			}
    		}
    		
    		
    		
    	}
    }
    
    • Java 7 增加了一个新特性,该特性提供了另外一种管理资源的方式,这种方式能自动关闭文件。这个特性有时被称为自动资源管理(Automatic Resource Management, ARM), 该特性以 try 语句的扩展版为基础。自动资源管理主要用于当不再需要文件(或其他资源)时,可以防止无意中忘记释放它们。

    • 自动资源管理基于 try 语句的扩展形式:

        try(需要关闭的资源声明){
        	//可能发生异常的语句
        }catch(异常类型 变量名){
        	//异常的处理语句
        }
        ……
        finally{
        	//一定执行的语句
        }
      

    当 try 代码块结束时,自动释放资源。因此不需要显示的调用 close() 方法。该形式也称为“带资源的 try 语句”

    注意:

    ① try 语句中声明的资源被隐式声明为 final ,资源的作用局限于带资源的 try 语句

    ② 可以在一条 try 语句中管理多个资源,每个资源以“;” 隔开即可。

    ③ 需要关闭的资源,必须实现了 AutoCloseable 接口或其子接口 Closeable

    FileUtils案例

    导入import org.apache.commons.io.FileUtils;

    import java.io.File;
    import java.io.IOException;
    
    import org.apache.commons.io.FileUtils;
    import org.junit.Test;
    
    public class FileUtilsTest {
    	
    	@Test
    	public void test1() throws IOException{
    		File srcFile = new File("Dilraba.jpg");
    		File destFile = new File("Dilraba1.jpg");
    		FileUtils.copyFile(srcFile, destFile);
    	}
    }
    

    12-4 缓冲区(Buffer)

    缓冲区案例

    import java.nio.ByteBuffer;
    import java.nio.CharBuffer;
    
    import org.junit.Test;
    
    
    /**
     * 一、NIO的使用中两个重要的要素:缓冲区(Buffer)、通道(Channel)
     * 缓冲区(Buffer):存储数据的。   ---->byte[] buffer = new byte[1024]
     * 通道(Channel):代表着数据源与目标节点之间的连接,负责缓冲区的传输。 --->IO流
     * 
     * 二者的交互:Java NIO 中的 Buffer 主要用于与 NIO 通道(Channel)进行交互,
     * 数据是从通道读入缓冲区,从缓冲区写入通道中的。
    
     * 
     * 二、缓冲区(Buffer)的结构 (除boolean之外)
     *   java.nio.Buffer抽象类
     *   	|----ByteBuffer
     *   	|----CharBuffer
     *   
     *   	|----ShortBuffer
     *   	|----IntBuffer
     *   	|----LongBuffer
     *   	|----FloatBuffer
     *   	|----DoubleBuffer
     *   XxxBuffer底层使用xxx[]进行存储。
     * 
     * 三、如何实例化缓冲区?调用缓冲区类XxxBuffer的静态方法:allocate(int capacity)
     *   举例:ByteBuffer byteBuffer = ByteBuffer.allocate(10);byte[] hb = new byte[10];
     *   	类似:ArrayList list = new ArrayList(10);//Object[] eleData = new Object[10];
     *   说明:方法的形参,决定了底层创建的数组的长度
     * 
     * 四、Buffer中的常用属性:
     * 	  capacity:容量,决定了底层数组的长度,表明了最大存储数据的容量
     *    limit:限制,默认情况下,limit等于capacity.在读数据模式下,limit<=capacity.表明最大可以读取数据的量
     *    position:位置,表明了当前读取或写入数据的位置
     *    mark:标记。默认值为-1.
     *    
     *    关系式:mark <= position <= limit <= capacity
     *    
     *    类比:项目三中TeamService类中的属性:
     *    private final int MAX_MEMBER = 5;//相当于capacity
    	  private Programmer[] team = new Programmer[MAX_MEMBER];//Buffer底层封装的数组
    	  private int total;//相当于limit
     *    index:读取、写入数组指定为的索引:position
     *    
     * 五、Buffer中的常用方法:
     *      1)最基本的两个方法:put(Xxx xxx) / get()
     *      2)其他方法:见ppt中的表格即可。
     * 
     * 六、针对于ByteBuffer来讲,可以创建非直接缓冲区:allocate(int capacity)
     * 							   直接缓冲区:allocateDirect(int capacity) / FileChannel 的 map()
     *    
     *    了解非直接缓冲区 与 直接缓冲区的区别
     */
    public class BufferTest {
    	
    	@Test
    	public void test3(){
    		ByteBuffer byteBuffer = ByteBuffer.allocate(10);
    		byteBuffer.put("hello".getBytes());
    		
    		//array():返回缓冲区底层的数组
    		byte[] array = byteBuffer.array();
    		System.out.println(array.length);
    		
    		for(int i = 0;i < array.length;i++){
    			System.out.println(array[i]);
    		}
    		
    		//isDirect():判断此时的字节缓冲区是否是直接缓冲区
    		System.out.println(byteBuffer.isDirect());
    	}
    	
    	@Test
    	public void test2(){
    		ByteBuffer byteBuffer = ByteBuffer.allocate(10);
    		byteBuffer.put("hello".getBytes());
    		System.out.println(byteBuffer.capacity());
    		System.out.println(byteBuffer.limit());
    		System.out.println(byteBuffer.position());
    		
    		byteBuffer.flip();
    		byte[] dst = new byte[5];	
    		byteBuffer.get(dst,0,2);//从数组角标0开始,写入两个字节的数组
    		System.out.println(byteBuffer.capacity());
    		System.out.println(byteBuffer.limit());
    		System.out.println(byteBuffer.position());
    		
    		System.out.println("***********mark()***************");
    		byteBuffer.mark();//标记当前position的位置
    		
    		byteBuffer.get(dst,2,2);//从数组角标2开始,写入两个字节的数组
    		System.out.println(byteBuffer.capacity());
    		System.out.println(byteBuffer.limit());
    		System.out.println(byteBuffer.position());
    		
    //		byteBuffer.rewind();//position = 0;
    		//reset():此方法必须要配合着mark()使用。调用此方法其调用mark(),否则抛异常。
    		byteBuffer.reset();//position = mark;即为将position置为标记的位置
    		
    		
    		if(byteBuffer.hasRemaining()){//判断是否还有元素没有读取到。
    			System.out.println(byteBuffer.remaining());//还有几个没有读取到。
    		}
    		System.out.println();
    		
    		while(byteBuffer.hasRemaining()){
    			System.out.println((char)byteBuffer.get());
    		}
    		
    		
    		
    	}
    	
    	
    	@Test
    	public void test1(){
    		System.out.println("*********allocate(10)************");
    		ByteBuffer byteBuffer = ByteBuffer.allocate(10);
    //		CharBuffer charBuffer = CharBuffer.allocate(10);
    		
    		System.out.println(byteBuffer.capacity());
    		System.out.println(byteBuffer.limit());
    		System.out.println(byteBuffer.position());
    		
    		System.out.println("*********put()************");
    		byteBuffer.put("hello".getBytes());//写入长度为5的字节数.每put一个字节,position就+1
    		
    		System.out.println(byteBuffer.capacity());
    		System.out.println(byteBuffer.limit());
    		System.out.println(byteBuffer.position());
    		
    		System.out.println("*********flip()************");
    		byteBuffer.flip();//切换为读数据模式。将limit设置为position,position归零
    		
    		System.out.println(byteBuffer.capacity());
    		System.out.println(byteBuffer.limit());
    		System.out.println(byteBuffer.position());
    		
    		System.out.println("*********get()************");
    		//方式一:
    //		System.out.println((char)byteBuffer.get());//每get一个字节,position也自动+1
    //		System.out.println((char)byteBuffer.get());
    		
    		//方式二:
    		byte[] dst = new byte[3];
    		byteBuffer.get(dst);
    		
    		System.out.println(byteBuffer.capacity());
    		System.out.println(byteBuffer.limit());
    		System.out.println(byteBuffer.position());
    		
    		
    //		System.out.println("*********rewind()************");
    //		byteBuffer.rewind();//重置position
    //		System.out.println(byteBuffer.capacity());
    //		System.out.println(byteBuffer.limit());
    //		System.out.println(byteBuffer.position());
    		
    		
    		System.out.println("*********clear()************");
    		byteBuffer.clear();//清空.将position归零,limit设置为capacity.数据并未删除。
    		System.out.println(byteBuffer.capacity());
    		System.out.println(byteBuffer.limit());
    		System.out.println(byteBuffer.position());
    		
    		System.out.println((char)byteBuffer.get());
    	}
    }
    
    缓冲区和通道
    • Java NIO系统的核心在于:通道(Channel)和缓冲区(Buffer)。通道表示IO源到 IO 设备(例如:文件、套接字)的连接。若需要使用 NIO 系统,需要获取用于连接 IO 设备的通道以及用于容纳数据的缓冲区。然后操作缓冲区,对数据进行处理。

    简而言之,Channel 负责传输, Buffer 负责存储数据

    缓冲区 (Buffer)
    • 缓冲区(Buffer):一个用于特定基本数据类型(除boolean型外)的容器,底层使用数组存储。由 java.nio 包定义的,所有缓冲区都是 Buffer 抽象类的子类。
    • Java NIO 中的 Buffer 主要用于与 NIO 通道(Channel)进行交互,数据是从通道读入缓冲区,从缓冲区写入通道中的。

    • Buffer 就像一个数组,可以保存多个相同类型的数据。根据数据类型不同(boolean 除外) ,有以下 Buffer 常用子类:

      上述 Buffer 类 他们都采用相似的方法进行管理数据,只是各自管理的数据类型不同而已。都是通过各自类的如下方法获取一个 Buffer 对象:

      static XxxBuffer allocate(int capacity) : 创建一个容量为 capacity 的 XxxBuffer 对象

    缓冲区的基本属性
    • 容量 (capacity) :表示 Buffer 最大数据容量,一旦声明后,不能更改。通过Buffer中的capacity()获取。缓冲区capacity不能为负。
    • 限制 (limit):第一个不应该读取或写入的数据的索引,即位于 limit 后的数据不可读写。通过Buffer中的limit()获取。缓冲区的limit不能为负,并且不能大于其capacity。
    • 位置 (position):当前要读取或写入数据的索引。通过Buffer中的position()获取。缓冲区的position不能为负,并且不能大于其limit。
    • 标记 (mark):标记是一个索引,通过 Buffer 中的 mark() 方法将mark标记为当前position位置。 之后可以通过调用 reset() 方法将 position恢复到标记的mark处。
    • 标记、位置、限制、容量遵守以下不变式:
      0 <= mark <= position <= limit <= capacity
    缓冲区的数据基本操作
    • Buffer 所有子类提供了两个用于数据操作的方法:

      put(Xxx xxx) 与 get() 方法

    以ByteBuffer类为例:

    • 放入数据到 Buffer 中

      put(byte b):将给定单个字节写入缓冲区的当前位置
      put(byte[] src):将 src 中的字节写入缓冲区的当前位置
      put(int index, byte b):将指定字节写入缓冲区的索引位置(不会移动 position)

    • 获取 Buffer 中的数据

      get() :读取单个字节
      get(byte[] dst):批量读取多个字节到 dst 中
      get(int index):读取指定索引位置的字节(不会移动 position)

    12-5 通道(Channel)与文件通道(FileChannel)

    通道案例

    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.RandomAccessFile;
    import java.nio.ByteBuffer;
    import java.nio.MappedByteBuffer;
    import java.nio.channels.FileChannel;
    import java.nio.channels.FileChannel.MapMode;
    import java.nio.file.Paths;
    import java.nio.file.StandardOpenOption;
    
    import org.junit.Test;
    
    /**
     * 一、java.nio.channels.Channel接口
     *   Channel:不负责存储数据,数据存储在Buffer中,Channel负责Buffer的传输
     *   
     * 
     * 二、
     * java.nio.channels.Channel
     * 		|-----FileChannel:处理本地文件
     * 		
     * 		|-----SocketChannel:TCP网络编程的客户端的Channel
     * 		|-----ServerSocketChannel:TCP网络编程的服务器端的Channel
     * 		|-----DatagramChannel:UDP网络编程中发送端和接收端的Channel
     * 
     * 		|-----Pipe.SinkChannel
     * 		|-----Pipe.SourceChannel
     * 
     * 三、如何实例化Channel
     * 		方式一:调用相关结构的getChannel()
     * 		FileInputStream / FileOutputStream / RandomAccessFile ----> FileChannel
     * 		
     * 		Socket --->SocketChannel
     * 		ServerSocket ---->ServerSocketChannel
     * 		DatagramSocket --->DatagramChannel
     * 
     * 		说明:Channel可以是单向的,也可以是双向的。
     * 		
     *     方式二:XxxChannel的静态方法:open().  
     *     方式三:Files工具类的静态方法:newByteChannel() 得到的是字节通道。
     * 	       说明:方式二和方式三在jdk7.0新增的。
     * 
     * 四、Channel的常用方法:读:read(ByteBuffer buffer) / write(ByteBuffer buffer)
     */
    public class ChannelTest {
    	
    	
    	@Test
    	public void test4() throws Exception{
    		
    		RandomAccessFile readRaf = new RandomAccessFile("IDEA.java", "r");
    		RandomAccessFile writeRaf = new RandomAccessFile("NEWIDEA.java", "rw");
    		
    		//实例化Channel
    		FileChannel inChannel = readRaf.getChannel();
    		FileChannel outChannel = writeRaf.getChannel();
    		
    		ByteBuffer buffer1 = ByteBuffer.allocate(1024);
    		ByteBuffer buffer2 = ByteBuffer.allocate(2048);
    		ByteBuffer[] dsts = {buffer1,buffer2};
    		
    		inChannel.read(dsts);//分散读取
    		
    		//改为可读模式
    		buffer1.flip();
    		buffer2.flip();
    		System.out.println(new String(buffer1.array(),0,buffer1.limit()));
    		System.out.println();
    		System.out.println(new String(buffer2.array(),0,buffer2.limit()));
    		
    		//测试聚集写入
    		outChannel.write(dsts);
    		
    		
    		outChannel.close();
    		inChannel.close();
    	}
    	
    	@Test
    	public void test3() throws IOException{
    		FileChannel inChannel = FileChannel.open(Paths.get("Dilraba.jpg"), StandardOpenOption.READ);
    		
    		FileChannel outChannel = FileChannel.open(Paths.get("mm1.jpg"), StandardOpenOption.WRITE,StandardOpenOption.CREATE);
    		
    		//transferTo():将数据从可读的Channel中转换到可写的Channel中
    //		inChannel.transferTo(0, inChannel.size(), outChannel);
    		//transferFrom():将数据从可读的Channel中转换到可写的Channel中
    		outChannel.transferFrom(inChannel, 0, inChannel.size());
    		
    		
    		inChannel.close();
    		outChannel.close();
    		
    	}
    	
    	//使用Channel实例化的方式二,使用直接缓冲区,实现文件的复制
    	@Test
    	public void test2(){
    		FileChannel inChannel = null;
    		FileChannel outChannel = null;
    		try {
    			long start = System.currentTimeMillis();
    			
    //			String srcPath = "Dilraba.jpg";
    //			String destPath = "Dilraba3.jpg";
    			
    			
    			String srcPath = "C:\Users\Administrator\Desktop\score\战狼.mp4";
    			String destPath = "C:\Users\Administrator\Desktop\score\战狼1.mp4";
    			
    			
    			//实例化Channel
    			inChannel = FileChannel.open(Paths.get(srcPath), StandardOpenOption.READ);
    			outChannel = FileChannel.open(Paths.get(destPath), StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE);
    			
    			//得到直接缓冲区
    			MappedByteBuffer inMappedBuffer = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());//size():返回操作的文件的大小
    			MappedByteBuffer outMappedBuffer = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());
    			
    			//数据的读写操作
    			byte[] buffer = new byte[inMappedBuffer.limit()];
    			inMappedBuffer.get(buffer);
    			outMappedBuffer.put(buffer);
    			
    			
    			long end = System.currentTimeMillis();
    			System.out.println("直接缓冲区花费的时间:" + (end - start));//1929-1894
    			
    			
    		} catch (IOException e) {
    			e.printStackTrace();
    		}finally{
    			if(inChannel != null){
    				try {
    					inChannel.close();
    				} catch (IOException e) {
    					e.printStackTrace();
    				}
    				
    			}
    			if(outChannel != null){
    				
    				try {
    					outChannel.close();
    				} catch (IOException e) {
    					e.printStackTrace();
    				}
    			}
    		}
    		
    		
    	}
    	
    	
    	//使用Channel实例化的方式一,使用非直接缓冲区,实现文件的复制操作
    	@Test
    	public void test1(){
    		FileChannel inChannel = null;
    		FileChannel outChannel = null;
    		FileInputStream fis = null;
    		FileOutputStream fos = null;
    		try {
    			
    			long start = System.currentTimeMillis();
    			
    //			String srcPath = "Dilraba.jpg";
    //			String destPath = "Dilraba2.jpg";
    			
    			
    			String srcPath = "C:\Users\Administrator\Desktop\score\战狼.mp4";
    			String destPath = "C:\Users\Administrator\Desktop\score\战狼2.mp4";
    			
    			
    			fis = new FileInputStream(srcPath);
    			fos = new FileOutputStream(destPath);
    			
    			//实例化Channel
    			inChannel = fis.getChannel();
    			outChannel = fos.getChannel();
    			
    			//提供ByteBuffer
    			ByteBuffer buffer = ByteBuffer.allocate(1024);
    			while(inChannel.read(buffer) != -1){
    				buffer.flip();//修改为读数据模式
    				outChannel.write(buffer);
    				buffer.clear();//清空
    			}
    			
    			long end = System.currentTimeMillis();
    			System.out.println("非直接缓冲区花费的时间:" + (end - start));//20795-13768
    			
    		} catch (Exception 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();
    				}
    				
    			}
    			
    		}
    		
    		
    	}
    }
    
    通道 (Channel)
    • 通道 (Channel):由 java.nio.channels 包定义的。
      • Channel 表示 IO 源与目标节点打开的连接。
      • Channel 类似于传统的“流”。只不过 Channel 本身不能直接存储数据,Channel 只能与 Buffer 进行交互。
    传统的IO和NIO理解


    PIO(parts in one)模式下硬盘和内存之间的数据传输是由CPU来控制的


    DMA模式下,CPU必须向DMA(理解为秘书)控制器下达命令,让DMA控制器来处理数据的传送,数据传输玩波比再把信息反馈给CPU,这样就很大程度上减轻了CPU资源占有率,可以大大节省系统资源。
    DMA模式又可以分为Single-Word(单字节DMA)和Multi-Word(多字节DMA)两种,其中所能达到的最大传输速率也只能有16.6MB/s.

    12-6 管道(Pipe)中的SinkChannel和SourceChannel

    管道(Pipe)案例

    import java.io.IOException;
    import java.nio.ByteBuffer;
    import java.nio.channels.Pipe;
    
    import org.junit.Test;
    
    public class PipeTest {
    
    	/**
    	 * 管道的中SinkChannel 和 SourceChannel使用举例
    	 * @throws IOException
    	 */
    	@Test
    	public void test1() throws IOException{
    		//获取管道
    		Pipe pipe = Pipe.open();
    		
    		//将缓冲区中的数据写入管道
    		ByteBuffer buf = ByteBuffer.allocate(1024);
    		//线程1
    		Pipe.SinkChannel sinkChannel = pipe.sink();
    		buf.put("通过单向管道发送数据".getBytes());
    		buf.flip();
    		sinkChannel.write(buf);
    		
    		//线程2
    		//读取缓冲区中的数据
    		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();
    	}
    	
    }
    

    12-7 字符集(Charset)

    字符集案例

    import java.nio.ByteBuffer;
    import java.nio.CharBuffer;
    import java.nio.charset.CharacterCodingException;
    import java.nio.charset.Charset;
    import java.nio.charset.CharsetDecoder;
    import java.nio.charset.CharsetEncoder;
    import java.util.Iterator;
    import java.util.Map.Entry;
    import java.util.Set;
    import java.util.SortedMap;
    
    import org.junit.Test;
    
    /**
     * Charset:字符集
     * 
     * 1.如何实例化:Charset的静态方法:forName(String charset)
     * 
     * 2.如何得到编码器和解码器:newEncoder()/newDecoder();
     */
    public class CharsetTest {
    	
    	
    	@Test
    	public void test2() throws Exception{
    		//1.如何实例化
    //		Charset charset = Charset.forName("gbk");
    		Charset charset = Charset.forName("utf-8");
    		
    		//2.得到编码器 和 解码器
    		CharsetEncoder encoder = charset.newEncoder();//编码器
    		CharsetDecoder decoder = charset.newDecoder();//解码器
    		
    		CharBuffer charBuffer = CharBuffer.allocate(1024);
    		charBuffer.put("保持微笑");
    		charBuffer.flip();
    		ByteBuffer byteBuffer = encoder.encode(charBuffer);//编码
    		//将编码后的数据输出到控制台
    //		byte[] array = byteBuffer.array();
    		for(int i = 0;i < byteBuffer.limit();i++){
    //			System.out.println(array[i]);
    			System.out.println(byteBuffer.get());
    		}
    		
    		byteBuffer.flip();
    		CharBuffer charBuffer2 = decoder.decode(byteBuffer);//解码
    		
    		System.out.println(new String(charBuffer2.array(),0,charBuffer2.limit()));
    		
    		System.out.println("******************");
    		Charset charset1 = Charset.forName("gbk");
    		CharsetDecoder decoder2 = charset1.newDecoder();
    		byteBuffer.flip();
    		CharBuffer charBuffer3 = decoder2.decode(byteBuffer);//解码
    		
    		System.out.println(new String(charBuffer3.array(),0,charBuffer3.limit()));
    		
    		
    	}
    	
    	
    	@Test
    	public void test1(){
    		SortedMap<String,Charset> charsets = Charset.availableCharsets();
    		Set<Entry<String,Charset>> entrySet = charsets.entrySet();
    		
    		Iterator<Entry<String, Charset>> iterator = entrySet.iterator();
    		while(iterator.hasNext()){
    			Entry<String, Charset> entry = iterator.next();
    			System.out.println(entry.getKey() + "---->" + entry.getValue());
    		}
    		
    	}
    }
  • 相关阅读:
    cf 786B. Legacy(线段树区间建图)
    cf 1416D. Graph and Queries (生成树重构+线段树维护dfs序)
    cf 1437E. Make It Increasing
    cf 1434D. Roads and Ramen (树上最长偶权链)
    cf 1413C (贪心排序+双指针)
    cf 1421E. Swedish Heroes (dp)
    CF1428 F.Fruit Sequences
    11.Redis详解(十一)------ 过期删除策略和内存淘汰策略
    10.Redis详解(十)------ 集群模式详解
    9.Redis详解(九)------ 哨兵(Sentinel)模式详解
  • 原文地址:https://www.cnblogs.com/ttzzyy/p/9742765.html
Copyright © 2020-2023  润新知