Java7新特性——新I/O

版权声明:本文为博主原创文章,未经允许不得转载。 https://blog.csdn.net/qq_31142553/article/details/81712716

NIO.2是一组新的类和方法,主要存在于java.nio包内。

主要优点:

  • 它完全取代了java.io.File与文件系统的交互。
  • 它提供了新的异步处理类,让你无需手动配置线程池和其他底层并发控制,便可在后台线程中执行文件和网络I/O操作。
  • 它引入了新的Network-Channel构造方法,简化了套接字(Socket)与通道的编码工作。

文件I/O的基石:Path

Path通常代表文件系统中的位置,比如C:\Users\dell\Desktop\nio。NIO.2把位置(由Path表示)的概念和物理文件系统的处理(比如复制一个文件)分得很清楚,物理文件系统的处理通常是由Files辅助类实现。

  • Path:Path类中的方法可以用来获取路径信息,访问该路径中的各元素,将路径转换为其他形式,或提取路径中的一部分。有的方法还可以匹配路径字串以及移除路径中的冗余项
  • Paths:工具类,提供返回一个路径的辅助方法,比如get(String first, String... more)和get(URI uri)
  • FileSystem:与文件系统交互的类,无论是默认的文件系统,还是通过其统一资源标识(URI)获取的可选文件系统
  • FIleSystems:工具类,提供各种方法,比如其中用于返回默认文件系统的FIleSystems.getDefault()

Path不一定代表真实的文件或目录。 

1、创建一个Path

Path path = Paths.get("C:\\Users\\dell\\Desktop\\nio", "a.txt");

其实,等同于Path path = FileSystems.getDefault().getPath("C:\\Users\\dell\\Desktop\\nio", "a.txt");

2、从Path中获取信息

Path path = FileSystems.getDefault().getPath("C:\\Users\\dell\\Desktop\\nio", "a.txt");
        
System.out.println("File Name:" + path.getFileName());
        
System.out.println("Number of Name Elements in the Path:" + path.getNameCount());
        
System.out.println("Root of Path:" + path.getRoot());
        
System.out.println("Subpath from Root,2 elements deep:" + path.subpath(0, 2));

3、移除冗余项

Path normalizePath = Paths.get("./PathTest.java").normalize();

 此外,toRealPath()方法也很有效,它融合了toAbsolutePath()和normalize()两个方法的功能,还能检测并跟随符号连接。

4、转换Path

合并两个Path之间的路径:

Path path = Paths.get("C:\\Users\\dell");
Path completePath = path.resolve("Desktop\\nio");
System.out.println(completePath);

  

取得两个Path之间的路径:

Path path1 = Paths.get("C:\\Users\\dell");
Path path2 = Paths.get("C:\\Users\\dell\\Desktop\\nio");
System.out.println(path1.relativize(path2));

 

比较

startsWith(Path prefix)、equals(Path path)、endsWith(Path suffix)。

5、NIO.2 Path和Java已有的FIle类

处理目录和目录树

  • java.io.File.toPath():把File转化为Path
  • java.nio.file.Path.toFile():把Path转化为File

java.nio.file.DirectorySystem<T>接口和它的实现类提供了很多功能:

  • 循环遍历目录中的子项,比如查找目录中的文件
  • 用glob表达式(比如*Foobar*)进行目录子项匹配和基于MIME的内容检测(比如text/xml)文件
  • 用walkFileTree方法实现递归移动、复制和删除操作

1、在目录中查找文件

        Path dir = Paths.get("C:\\\\Users\\\\dell\\\\Desktop\\\\nio");
		
		try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.txt")) {
			for (Path entry : stream) {
				System.out.println(entry.getFileName());
			}
		} catch (IOException e) {
			System.out.println(e.getMessage());
		}

 

 2、遍历目录树

Java7可以很容易地搜寻目录树中的文件,在子目录中查找,并对它们执行操作。

关键方法是:Path walkFileTree(Path start, FileVisitor<? super Path> visitor) throws IOException,其中visitor提供了一个默认实现类SimpleFileVisitor<T>。

/**
 * Java7的新I/O
 * @author z_hh
 * @time 2018年8月16日
 */
public class Nio {

	public static void main(String[] args) throws IOException {
		
		Path startingDir = Paths.get("D:\\eclipse_workspace\\zhh_src\\src");
		Files.walkFileTree(startingDir, new FindJavaVisitor());
		
	}
	
	private static class FindJavaVisitor extends SimpleFileVisitor<Path> {
		@Override
		public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
			if (file.toString().endsWith(".java")) {
				System.out.println(file.getFileName());
			}
			return FileVisitResult.CONTINUE;
		}
	}

}

重写了SimpleFileVisitor的visitFile方法来判断文件名是否以.java结尾。

其它用例包括递归移动、复制、删除或者修改文件。

为了确保递归等操作的安全性,walkFileTree方法不会自动跟随符号链接。 

NIO.2的文件系统I/O 

文件处理的基础类

Files:让你轻松复制、移动、删除或处理文件的工具类,有你需要的所有方法

WatchService:用来监视文件或目录的核心类,不管它们有没有变化

1、创建和删除文件

        // 创建文件
		Path txt = Paths.get("C:\\Users\\dell\\Desktop\\nio\\z.txt");
		Path path = Files.createFile(txt);
		// 创建文件并分配权限
		Path jpg = Paths.get("C:\\Users\\dell\\Desktop\\nio\\h.jpg");
		Set<PosixFilePermission> perms = PosixFilePermissions.fromString("r--r--r--");
		FileAttribute<Set<PosixFilePermission>> attr = PosixFilePermissions.asFileAttribute(perms);
		Files.createFile(jpg, attr);
		// 删除
		Files.delete(jpg);
		Files.deleteIfExists(jpg);

2、文件的复制和移动

Path source = Paths.get("C:\\Users\\dell\\Desktop\\nio\\z.txt");
		Path target = Paths.get("C:\\Users\\dell\\Desktop\\nio\\dir");
		Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
		
		Files.move(source, target, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);

3、文件的属性

 基本文件属性的支持

特定文件属性支持

try {
			Path path = Paths.get("C:\\Users\\dell\\Desktop\\nio\\a.txt");
			// 获取属性范围
			PosixFileAttributes attrs = Files.readAttributes(path, PosixFileAttributes.class);
			// 读取访问许可
			Set<PosixFilePermission> posixPermissions = attrs.permissions();
			// 消除访问许可
			posixPermissions.clear();
			// 日志信息
			String owner = attrs.owner().getName();
			String perms = PosixFilePermissions.toString(posixPermissions);
			System.out.format("%s %s%n", owner, perms);
			// 设置新的访问许可
			posixPermissions.add(PosixFilePermission.OWNER_READ);
			posixPermissions.add(PosixFilePermission.GROUP_READ);
			posixPermissions.add(PosixFilePermission.OTHERS_READ);
			posixPermissions.add(PosixFilePermission.OWNER_WRITE);
			Files.setPosixFilePermissions(path, posixPermissions);
		} catch (Exception e) {
			e.printStackTrace();
		}

符号连接

Path readSymbolicLink(Path link) throws IOException

 4、快速读写数据

打开文件

  • BufferedReader newBufferedReader(Path path, Charset cs) throws IOException
  • BufferedReader newBufferedReader(Path path, Charset cs) throws IOException

简化读取和写入

  • List<String> readAllLines(Path path, Charset cs) throws IOException
  • byte[] readAllBytes(Path path) throws IOException

 5、文件修改通知

java.nio.file.WatchService类用客户线程监视注册文件或目录的变化,并且在检测到变化时返回一个事件。这种事件通知对于安全监测、属性文件中的数据刷新等很多用例都很有用。是现在某些应用程序中常用的轮询机制(相对而言性能较差)的理想替代品。(好像还有一种回调的方式^_^

        try {
			WatchService watcher = FileSystems.getDefault().newWatchService();
			Path dir = FileSystems.getDefault().getPath("C:\\Users\\dell\\Desktop\\nio");
			// 检测变化
			WatchKey key = dir.register(watcher, StandardWatchEventKinds.ENTRY_MODIFY);
			while (true) {
				// 得到写一个key及其事件
				key = watcher.take();
				for (WatchEvent<?> event : key.pollEvents()) {
					// 检查是否为创建事件
					if (event.kind() == StandardWatchEventKinds.ENTRY_MODIFY) {
						System.out.println("Home dir has entry create!");
					}
				}
				// 重置检测key
				key.reset();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

6、SeekableByteChannel

 Java7引入SeekableByteChannel接口,是为了让开发人员能够改变字节通道的位置和大小。比如,应用服务器为了分析日志中的某个错误码,可以让多个线程去访问连接在一个大型日志文件上的字节通道。

jdk中有一个java.nio.channels.SeekableByteChannel接口的实现类——java.nio.channels.FileChannel。这个类可以在文件读取或写入时保持当前位置。

Path logFile = Paths.get("C:\\error.log");
		ByteBuffer buffer = ByteBuffer.allocate(1024);
		FileChannel channel = FileChannel.open(logFile, StandardOpenOption.READ);
		channel.read(buffer, channel.size() - 1000);

异步I/O操作

异步I/O其实只是一种在读写操作结束前允许其他操作的I/O处理。

Java7中有三个新的异步通道:

  • AsynchronousFileChannel——用于文件I/O
  • AsynchronousSocketChannel——用于套接字I/O,支持超时
  • AsynchronousServerSocketChannel——用于套接字接受异步连接

 使用新的异步I/O API时,主要有两种形式,将来式和回调式。

1、将来式

Path file = Paths.get("C:\\Users\\dell\\Desktop\\nio\\a.txt");
		// 异步打开文件
		AsynchronousFileChannel channel = AsynchronousFileChannel.open(file);
		// 读取100,000字节
		ByteBuffer buffer = ByteBuffer.allocate(100_000);
		Future<Integer> result = channel.read(buffer, 0);
		while (!result.isDone()) {
			// 干点别的事情
			System.out.println("睡个觉!");
		}
		// 获取结果
		Integer byteRead = result.get();
		System.out.println("Bytes read:" + byteRead);

AsynchronousFileChannel会关联线程池,它的任务是接收I/O处理事件,并分发给负责处理通道中I/O操作结果的结果处理器。跟通道中发起的I/O操作关联的结果处理器确保是由线程池中的某个线程产生的。

2、回调式

        Path file = Paths.get("C:\\Users\\dell\\Desktop\\nio\\a.txt");
		// 以异步方式打开文件
		AsynchronousFileChannel channel = AsynchronousFileChannel.open(file);
		ByteBuffer buffer = ByteBuffer.allocate(100_000);
		// 从通道中读取数据
		channel.read(buffer, 0, buffer, new CompletionHandler<Integer, ByteBuffer>() {

			// 获取完成时的回调方法
			@Override
			public void completed(Integer result, ByteBuffer attachment) {
				System.out.println("Byte read:" + result);
			}

			@Override
			public void failed(Throwable exc, ByteBuffer attachment) {
				System.out.println("fail:" + exc.getMessage());
			}
		});

以上两个离职都是基于文件的,但也同样适合AsynchronousSocketChannel和AsynchronousServerSocketChannel。

Socket和Channel的整合

 NetworkChannel

java.nio.channels.NetworkChannel代表一个连接到网络套接字通道的映射。

        SelectorProvider provider = SelectorProvider.provider();
		try {
			// 将NetworkChannel绑定到3080上
			NetworkChannel socketChannel = provider.openSocketChannel();
			InetSocketAddress address = new InetSocketAddress(3080);
			socketChannel = socketChannel.bind(address);
			// 检查套接字选项
			Set<SocketOption<?>> socketOptions = socketChannel.supportedOptions();
			System.out.println(socketOptions.toString());
			// 设置套接字的Tos(服务条款)选项
			socketChannel.setOption(StandardSocketOptions.IP_TOS, 3);
			// 获取SO_KEEPALIVE选项
			Boolean keepAlive = socketChannel.getOption(StandardSocketOptions.SO_KEEPALIVE);
			// ...
		} catch (Exception e) {
			e.printStackTrace();
		}

MulticastChannel

多播(或组播)表示一对多的网络通讯,通常用来指代IP多播。使用java.nio.channels.MulticastChannel及其默认实现类DatagramChannel可以轻松地对多播发送和接收数据。

        // 选择网络接口
		NetworkInterface networkInterface = NetworkInterface.getByName("net1");
		// 打开DatagramChannel
		DatagramChannel dc = DatagramChannel.open(StandardProtocolFamily.INET);
		// 将通道设置为多播
		dc.setOption(StandardSocketOptions.SO_REUSEADDR, true)// 链式调用
			.bind(new InetSocketAddress(8080))
			.setOption(StandardSocketOptions.IP_MULTICAST_IF, networkInterface);
		// 加入多播组
		InetAddress group = InetAddress.getByName("180.90.4.12");
		MembershipKey key = dc.join(group, networkInterface);

到此为止,我们对NIO.2 API的初步研究已经结束了。希望你喜欢这次行色匆匆的旅程!

猜你喜欢

转载自blog.csdn.net/qq_31142553/article/details/81712716