十三、NIO

Java NIO 簡介

Java NIO(New IO)是從Java 1.4版本開始引入的 一個新的IO API,可以替代標準的Java IO API。 NIO與原來的IO有同樣的作用和目的,但是使用 的方式完全不同,NIO支持面向緩衝區的、基於 通道的IO操作。NIO將以更加高效的方式進行文 件的讀寫操作。

Java NIO 與 IO 的主要區別

    IO                                 NIO
面向流(Stream Oriented)             面向緩衝區(Buffer Oriented)
阻塞IO(Blocking IO)                 非阻塞IO(Non Blocking IO)
(無)                                選擇器(Selectors)

通道和緩衝區

Java NIO系統的核心在於:通道(Channel)和緩衝區 (Buffer)。通道表示打開到 IO 設備(例如:文件、 套接字)的連接。若需要使用 NIO 系統,需要獲取 用於連接 IO 設備的通道以及用於容納數據的緩衝區。然後操作緩衝區,對數據進行處理。

Channel 負責傳輸(連接), Buffer 負責存儲

 

緩衝區(Buffer):

在 Java NIO 中負責數據的存取。緩衝區就是數組。用於存儲不同數據類型的數據

 

/*
 * 一、緩衝區(Buffer):在 Java NIO 中負責數據的存取。緩衝區就是數組。用於存儲不同數據類型的數據
 * 
 * 根據數據類型不同(boolean 除外),提供了相應類型的緩衝區:
 * ByteBuffer
 * CharBuffer
 * ShortBuffer
 * IntBuffer
 * LongBuffer
 * FloatBuffer
 * DoubleBuffer
 * 上述緩衝區的管理方式幾乎一致,通過 allocate() 獲取緩衝區
 * 
 * 二、緩衝區存取數據的兩個核心方法:
 * put() : 存入數據到緩衝區中
 * get() : 獲取緩衝區中的數據
 * 
 * 三、緩衝區中的四個核心屬性:
 * capacity : 容量,表示緩衝區中最大存儲數據的容量。一旦聲明不能改變。
 * limit : 界限,表示緩衝區中可以操作數據的大小。(limit 後數據不能進行讀寫)
 * position : 位置,表示緩衝區中正在操作數據的位置。
 * mark : 標記,表示記錄當前 position 的位置。可以通過 reset() 恢復到 mark 的位置
 * 0 <= mark <= position <= limit <= capacity
 * 
 * 四、直接緩衝區與非直接緩衝區:
 * 非直接緩衝區:通過 allocate() 方法分配緩衝區,將緩衝區建立在 JVM 的內存中
 * 直接緩衝區:通過 allocateDirect() 方法分配直接緩衝區,將緩衝區建立在物理內存中。可以提高效率
 */
public class TestBuffer {

	@Test
	public void test1() {
		String str = "abcde";

		//1. 分配一個指定大小的緩衝區
		ByteBuffer buf = ByteBuffer.allocate(1024);

		System.out.println("-----------------allocate()----------------");
		System.out.println(buf.position());//0
		System.out.println(buf.limit());//1024
		System.out.println(buf.capacity());//1024

		//2. 利用 put() 存入數據到緩衝區中
		buf.put(str.getBytes());//放入5個字節數據

		System.out.println("-----------------put()----------------");
		System.out.println(buf.position());//5
		System.out.println(buf.limit());//1024
		System.out.println(buf.capacity());//1024

		//3. 切換讀取數據模式
		buf.flip();

		System.out.println("-----------------flip()----------------");
		System.out.println(buf.position());//0
		System.out.println(buf.limit());//5
		System.out.println(buf.capacity());//1024

		//4. 利用 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());//5
		System.out.println(buf.limit());//5
		System.out.println(buf.capacity());//1024

		//5. rewind() : 可重複讀 類似於切換到讀模式
		buf.rewind();

		System.out.println("-----------------rewind()----------------");
		System.out.println(buf.position());//0
		System.out.println(buf.limit());//5
		System.out.println(buf.capacity());//1024

		//6. clear() : 清空緩衝區. 但是緩衝區中的數據依然存在,但是處於“被遺忘”狀態
		buf.clear();

		System.out.println("-----------------clear()----------------");
		System.out.println(buf.position());//0
		System.out.println(buf.limit());//1024
		System.out.println(buf.capacity());//1024

		System.out.println((char) buf.get());

	}

	@Test
	public void test2() {
		String str = "abcde";
		ByteBuffer buf = ByteBuffer.allocate(1024);
		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());

		//reset() : 恢復到 mark 的位置
		buf.reset();
		System.out.println(buf.position());

		//判斷緩衝區中是否還有剩餘數據
		if (buf.hasRemaining()) {
			//獲取緩衝區中可以操作的數量
			System.out.println(buf.remaining());
		}
	}
	
	@Test
	public void test3(){
		//分配直接緩衝區
		ByteBuffer buf = ByteBuffer.allocateDirect(1024);
		boolean direct = buf.isDirect();//判斷是否是直接緩衝區
		System.out.println(direct);
	}
}

 

通道(Channel)

通道(Channel):由 java.nio.channels 包定義 的。Channel 表示 IO 源與目標打開的連接。 Channel 類似於傳統的“流”。只不過 Channel 本身不能直接訪問數據,Channel 只能與 Buffer 進行交互

用於源節點與目標節點的連接。在 Java NIO 中負責緩衝區中數據的傳輸。Channel 本身不存儲數據,因此需要配合緩衝區進行傳輸。

/*
 * 一、通道(Channel):用於源節點與目標節點的連接。在 Java NIO 中負責緩衝區中數據的傳輸。Channel 本身不存儲數據,因此需要配合緩衝區進行傳輸。
 * 
 * 二、通道的主要實現類
 * 	java.nio.channels.Channel 接口:
 * 		|--FileChannel -- 用於操作本地文件數據傳輸(本地IO)
 * 		|--SocketChannel -- 用於TCP(網絡IO)
 * 		|--ServerSocketChannel -- 用於TCP(網絡IO)
 * 		|--DatagramChannel -- 用於UDP(網絡IO)
 * 
 * 三、獲取通道
 * 1. Java 針對支持通道的類提供了 getChannel() 方法
 * 		本地 IO:
 * 		FileInputStream/FileOutputStream
 * 		RandomAccessFile
 * 
 * 		網絡IO:
 * 		Socket
 * 		ServerSocket
 * 		DatagramSocket
 * 		
 * 2. 在 JDK 1.7 中的 NIO.2 針對各個通道提供了靜態方法 open()
 * 3. 在 JDK 1.7 中的 NIO.2 的 Files 工具類的 newByteChannel()
 * 
 * 四、通道之間的數據傳輸
 * transferFrom()
 * transferTo()
 * 
 * 五、分散(Scatter)與聚集(Gather)
 * 分散讀取(Scattering Reads):將通道中的數據分散到多個緩衝區中
 * 聚集寫入(Gathering Writes):將多個緩衝區中的數據聚集到通道中
 * 
 * 六、字符集:Charset
 * 編碼:字符串 -> 字節數組
 * 解碼:字節數組  -> 字符串
 * 
 */
public class TestChannel {

	//利用通道完成文件的複製(非直接緩衝區)
	@Test
	public void test1() {//10874-10953
		long start = System.currentTimeMillis();

		FileInputStream fis = null;
		FileOutputStream fos = null;
		//①獲取通道
		FileChannel inChannel = null;
		FileChannel outChannel = null;
		try {
			fis = new FileInputStream("d:/1.mkv");
			fos = new FileOutputStream("d:/2.mkv");

			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 {
			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();}
			}
		}

		long end = System.currentTimeMillis();
		System.out.println("耗費時間爲:" + (end - start));

	}

	//使用直接緩衝區完成文件的複製(內存映射文件)
	@Test
	public void test2() throws IOException{//2127-1902-1777
		long start = System.currentTimeMillis();
		
		FileChannel inChannel = FileChannel.open(Paths.get("d:/1.mkv"), StandardOpenOption.READ);
		FileChannel outChannel = FileChannel.open(Paths.get("d:/2.mkv"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);
		
		//內存映射文件
		MappedByteBuffer inMappedBuf = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());
		MappedByteBuffer outMappedBuf = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());
		
		//直接對緩衝區進行數據的讀寫操作
		byte[] dst = new byte[inMappedBuf.limit()];
		inMappedBuf.get(dst);
		outMappedBuf.put(dst);
		
		inChannel.close();
		outChannel.close();
		
		long end = System.currentTimeMillis();
		System.out.println("耗費時間爲:" + (end - start));
	}

	//********通道之間的數據傳輸(直接緩衝區)********
	@Test
	public void test3() throws IOException {
		FileChannel inChannel = FileChannel.open(Paths.get("d:/1.mkv"), StandardOpenOption.READ);
		FileChannel outChannel = FileChannel.open(Paths.get("d:/2.mkv"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);

//		inChannel.transferTo(0, inChannel.size(), outChannel);
		outChannel.transferFrom(inChannel, 0, inChannel.size());

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

	//分散和聚集
	@Test
	public void test4() throws IOException {
		RandomAccessFile raf1 = new RandomAccessFile("1.txt", "rw");

		//1. 獲取通道
		FileChannel channel1 = raf1.getChannel();

		//2. 分配指定大小的緩衝區
		ByteBuffer buf1 = ByteBuffer.allocate(100);
		ByteBuffer buf2 = ByteBuffer.allocate(1024);

		//3. 分散讀取
		ByteBuffer[] bufs = {buf1, buf2};
		channel1.read(bufs);

		for (ByteBuffer byteBuffer : bufs) {
			byteBuffer.flip();
		}

		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()));

		//4. 聚集寫入
		RandomAccessFile raf2 = new RandomAccessFile("2.txt", "rw");
		FileChannel channel2 = raf2.getChannel();

		channel2.write(bufs);
	}

	@Test
	public void test5() {
		Map<String, Charset> map = Charset.availableCharsets();

		Set<Entry<String, Charset>> set = map.entrySet();

		for (Entry<String, Charset> entry : set) {
			System.out.println(entry.getKey() + "=" + entry.getValue());
		}
	}

	//字符集
	@Test
	public void test6() throws IOException {
		Charset cs1 = Charset.forName("GBK");

		//獲取編碼器
		CharsetEncoder ce = cs1.newEncoder();

		//獲取解碼器
		CharsetDecoder cd = cs1.newDecoder();

		CharBuffer cBuf = CharBuffer.allocate(1024);
		cBuf.put("尚硅谷威武!");
		cBuf.flip();

		//編碼
		ByteBuffer bBuf = ce.encode(cBuf);

		for (int i = 0; i < 12; i++) {
			System.out.println(bBuf.get());
		}

		//解碼
		bBuf.flip();
		CharBuffer cBuf2 = cd.decode(bBuf);
		System.out.println(cBuf2.toString());

		System.out.println("------------------------------------------------------");

		Charset cs2 = Charset.forName("UTF-8");
		bBuf.flip();
		CharBuffer cBuf3 = cs2.decode(bBuf);
		System.out.println(cBuf3.toString());
	}
	
}

NIO2.0

public class TestNIO2 {

	@Test
	public void test1() {
		Path path = Paths.get("e:/", "nio/hello.txt");

		System.out.println(path.endsWith("hello.txt"));
		System.out.println(path.startsWith("e:/"));

		System.out.println(path.isAbsolute());
		System.out.println(path.getFileName());

		for (int i = 0; i < path.getNameCount(); i++) {
			System.out.println(path.getName(i));
		}
	}

	/*
		Paths 提供的 get() 方法用來獲取 Path 對象:
			Path get(String first, String … more) : 用於將多個字符串串連成路徑。
		Path 常用方法:
			boolean endsWith(String path) : 判斷是否以 path 路徑結束
			boolean startsWith(String path) : 判斷是否以 path 路徑開始
			boolean isAbsolute() : 判斷是否是絕對路徑
			Path getFileName() : 返回與調用 Path 對象關聯的文件名
			Path getName(int idx) : 返回的指定索引位置 idx 的路徑名稱
			int getNameCount() : 返回Path 根目錄後面元素的數量
			Path getParent() :返回Path對象包含整個路徑,不包含 Path 對象指定的文件路徑
			Path getRoot() :返回調用 Path 對象的根路徑
			Path resolve(Path p) :將相對路徑解析爲絕對路徑
			Path toAbsolutePath() : 作爲絕對路徑返回調用 Path 對象
			String toString() : 返回調用 Path 對象的字符串表示形式
	 */
	@Test
	public void test2() {
		Path path = Paths.get("e:/nio/hello.txt");

		System.out.println(path.getParent());
		System.out.println(path.getRoot());

//		Path newPath = path.resolve("e:/hello.txt");
//		System.out.println(newPath);

		Path path2 = Paths.get("1.jpg");
		Path newPath = path2.toAbsolutePath();
		System.out.println(newPath);

		System.out.println(path.toString());
	}

	@Test
	public void test3() throws IOException {
		Path path1 = Paths.get("e:/nio/hello.txt");
		Path path2 = Paths.get("e:/nio/hello2.txt");

		Files.copy(path1, path2, StandardCopyOption.REPLACE_EXISTING);
	}

	@Test
	public void test4() throws IOException {
		Path dir = Paths.get("e:/nio/nio2");
//		Files.createDirectory(dir);

		Path file = Paths.get("e:/nio/nio2/hello3.txt");
//		Files.createFile(file);

		Files.deleteIfExists(file);
	}

	/*
		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) : 刪除一個文件
			Path move(Path src, Path dest, CopyOption…how) : 將 src 移動到 dest 位置
			long size(Path path) : 返回 path 指定文件的大小
	 */
	@Test
	public void test5() throws IOException {
		Path path1 = Paths.get("e:/nio/hello2.txt");
		Path path2 = Paths.get("e:/nio/hello7.txt");

		System.out.println(Files.size(path2));

//		Files.move(path1, path2, StandardCopyOption.ATOMIC_MOVE);
	}

	/*
		Files常用方法:用於判斷
			boolean exists(Path path, LinkOption … opts) : 判斷文件是否存在
			boolean isDirectory(Path path, LinkOption … opts) : 判斷是否是目錄
			boolean isExecutable(Path path) : 判斷是否是可執行文件
			boolean isHidden(Path path) : 判斷是否是隱藏文件
			boolean isReadable(Path path) : 判斷文件是否可讀
			boolean isWritable(Path path) : 判斷文件是否可寫
			boolean notExists(Path path, LinkOption … opts) : 判斷文件是否不存在
			public static <A extends BasicFileAttributes> A readAttributes(Path path,Class<A> type,LinkOption... options) : 獲取與 path 指定的文件相關聯的屬性。
	 */
	@Test
	public void test6() throws IOException {
		Path path = Paths.get("e:/nio/hello7.txt");
//		System.out.println(Files.exists(path, LinkOption.NOFOLLOW_LINKS));

		BasicFileAttributes readAttributes = Files.readAttributes(path, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
		System.out.println(readAttributes.creationTime());
		System.out.println(readAttributes.lastModifiedTime());

		DosFileAttributeView fileAttributeView = Files.getFileAttributeView(path, DosFileAttributeView.class, LinkOption.NOFOLLOW_LINKS);

		fileAttributeView.setHidden(false);
	}

	/*
		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 對象
	 */
	@Test
	public void test7() throws IOException {
		SeekableByteChannel newByteChannel = Files.newByteChannel(Paths.get("1.jpg"), StandardOpenOption.READ);

		DirectoryStream<Path> newDirectoryStream = Files.newDirectoryStream(Paths.get("e:/"));

		for (Path path : newDirectoryStream) {
			System.out.println(path);
		}
	}

	//自動資源管理:自動關閉實現 AutoCloseable 接口的資源
	@Test
	public void test8() {
		try (
				FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
				FileChannel outChannel = FileChannel.open(Paths.get("2.jpg"), StandardOpenOption.WRITE, StandardOpenOption.CREATE)
		) {

			ByteBuffer buf = ByteBuffer.allocate(1024);
			inChannel.read(buf);

		} catch (IOException e) {

		}
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章