《Java程序员修炼之道》读书笔记 第二章

第二章 新I/O

内容提要

  • Java7的新I/O API(即NIO.2)
  • Path———基于文件和目录的I/O新基础
  • Files应用类及其它的各种辅助方法
  • 如何实现常见的I/O应用场景
  • 介绍异步I/O

2.1 JavaI/O简史

2.1.1 Java 1.0到1.3

在Java的早期(1.0~1.3) 中没有完整的I/O支持。会遇到如下问题。

  • 没有数据缓冲区或通道的概念,开发人员要编程处理很多底层细节。
  • I/O操作会被阻塞,扩展能力受限。
  • 所支持的字符集编码有限,需要进行很多手工编码工作来支持特定类型的硬件。
  • 不支持正则表达式,数据处理困难。

在Java 1.4发布之前,Java一直没能在服务器端开发领域得到重用,我们认为主要原因就是缺乏对非阻塞I/O的支持。


2.1.2 在Java 1.4中引入的NIO

为了解决以上问题,Java开始实现对非阻塞I/O的支持,其中有两次主要改进:

  • 在Java 1.4中引入非阻塞I/O;
  • 在Java 7中对非阻塞I/O进行修改。

以下特性为2002年Java 1.4新增:

  • 为I/O操作抽象出缓冲区和通道层;
  • 字符集的编码和解码能力;
  • 提供了能够将文件映射为内存数据的接口;
  • 实现非阻塞I/O的能力;
  • 基于流行的Perl实现的正则表达式类库。

NIO的局限性:

  • 在不同的平台中对文件名的处理不一致;
  • 没有统一的文件属性模型。(比如读写访问模型)
  • 遍历目录困难。
  • 不能使用平台/操作系统的特性。
  • 不支持文件系统的非阻塞操作。(Java 1.4的确支持网络套接字的非阻塞操作。)

2.1.3 下一代I/O-NIO.2

JSR-203最终变成Java 7中见到的NIO.2 API。它有三个主要目标:

  1. 一个能批量获取文件属性的文件系统接口,去掉和特定文件系统相关的API,还有一个用于引入标准文件系统实现的服务提供者接口。
  2. 提供一个套接字和文件都能够进行异步(与轮询、非阻塞相对)I/O操作的API。
  3. 完成JSR-51中定义的套接字——通道功能,包括额外对绑定、选项配置和多播数据报的支持。

2.2 文件I/O的基石:Path

文件系统中的几个概念:

  • 目录树
  • 根路径
  • 绝对路径
  • 相对路径

学习文件I/O的关键基础类

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

2.2.1 创建一个Path

Path listing = Paths.get("/usr/bin/zip");

等同于

Path listing = FileSystems.getDefault().getPath("/usr/bin/zip");

2.2.2 从Path中获取信息
import java.nio.file.Path;
import java.nio.file.Paths;

public class Listing_2_1 {
  public static void main(String[] args){
    Path listing = Paths.get("/usr/bin/zip");//创建绝对路径
    System.out.println("File Name [" + listing.getFileName() +"]");//获取文件名
    /* 获取名称元素的数量*/
    System.out.println("Number of Name Elemen in the Path [" + listing.getNameCount() +"]");
    /*获取Path的信息*/
    System.out.println("Parent Path [" + listing.getParent() + "]");
    System.out.println("Root of Path" + listing.getRoot() + "]");
    System.out.println("SubPath form Root, 2 elements deep [" + listing.subpath(0,2) +"]");
}
}

2.2.3 移除冗余项

首先可以用normalize()去掉Path中的冗余信息

Path notmalizePatch = Paths.get("./Listing_2_1.java").normalize();

也可以通过调用toRealPath(),得到表示/application/logs/log1.txt的真正Path

Path realPath = Paths.get("/usr/logs/log1.txt").toRealPath();

2.2.4 转换Path

Path合并

Path prefix = Paths.get("/uat/");
Path completePath = prefix.resolve("conf/application.properties");

取得两个Path之间的路径

String logging = args[0];
String configuration = args[1];
Path logDir = Paths.get(logging);
Path confDir = Path.get(configuration);
Path pathToConfDir = logDir.relativize(confDir);

2.2.5 NIO.2 Path和Java已有的File类
  • java.io.File类中新增了toPath()方法,它可以马上把已有的File转化为为新的Path
  • Path类中有个toFile()方法,它可以马上把已有的Path转化为File
File file = new File("../Listing_2_1.java");
Path listing = file.toPath();
listing.toAbsolutePath();
file = listing.toFile();

2.3 处理目录和目录树

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

2.3.1 在目录中查找文件

代码清单2-2 列出目录下的properties文件

Path dir = Paths.get("C:\\workspace\\java7developer");//①设定起始路径

try(DirectoryStream<Path> stream
      = Files.newDirectoryStream(dir, "*.properties")){//②声明过滤流
  //③ 找出所有.properties 文件并输出
  for (Path entry: stream)
  {
      System.out.println(entry.getFileName());
  }
}
catch (IOException e)
{
  System.out.println(e.getMessage());
}

过滤流中用到的模式匹配称为glob模式匹配,它和Perl正则表达式类似,但稍有不同。


2.3.2 遍历目录树

关键方法是:

Files.walkFileTree(path startingDir, FileVisitor<? superPath> visitor);

代码清单2-3 列出子目录下的所有Java源码文件

public class Find{
  public static void main(String[] args) throws IOException{
    Path startingDir =
      Paths.get("C:\\workspace\\java7developer\\src");//设置起始目录
    Files.walkFileTree(startingDir, new FindJavaVisitor());//调用walkFileTree

  }
  private static class FindJavaVisitor
                  extends SimpleFileVisitor<Path>//扩展SimpleFileVisitor<Path>
    {
          @Override      
          public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)// 重写visitFile方法
            {
              if(file.toString().endsWith(".java")) {
                System.out.println(file.getFileName());
              }
              return FileVisitResult.CONTINUE;
            }     
    }
}

2.4 NIO.2的文件系统I/O

对于文件系统的操作任务,比如移动文件、修改文件属性、以及处理文件内容等,在NIO.2中都有所改善。这些操作的支持主要是由Files类提供的。

表2-2 文件处理的基础类

说明
Files让你轻松复制、移动、删除或处理文件的工具类,有你需要的所有方法
WatchService用来见识文件或目录的核心类,不管它们有没有变化

本节提要:

  • 创建和删除文件;
  • 移动、复制、重命名和删除文件;
  • 文件属性的读写;
  • 文件内容的读取和写入;
  • 处理符号链接;
  • WatchService发出文件修改通知;
  • 使用SeekableByteChannel————一个可以指定位置及大小的增强型字节通道。

2.4.1 创建和删除文件

基本的文件创建操作

Path target = Paths.get("D:\\backUp\\MyStuff.txt");
Path file = Files.createFile(target);

通常处于安全考虑,要定义创建的文件的读、写、执行权限,需要指明该文件的某些FileAttributes
下面是一个在POSIX文件系统上为属主、属主组内用户和所有用户设置读/写许可的例子。这种方法允许所有用户对即将创建的文件D:\Backup\MyStuff.txt进行读写操作。

Path target = Paths.get("D:\\Backup\\MyStuff.txt");
Set<PosixFilePermission> perms =
    PosixFilePermission.fromString("rw-rw-rw-");
FileAttribute<Set<PosixFilePermission>> attr = PosixFilePermission.asFileAttribute(perms);
Files.createFile(target, attr);

删除比较简单

Path target = Paths.get("D:\\backUp\\MyStuff.txt");
Files.delete(target);

2.4.2文件的复制和移动

基本的复制操作

Path source = Paths.get("C:\\My Documents\\Stuff.txt");
Path target = Paths.get("D:\\Backup\\MyStuff.txt");
Files.copy(source, target);

下面这个例子用到了覆盖即替换已有文件的选项

import static java.nio.file.StandardCopyOption.*;

Path source = Paths.get("C:\\My Documents\\Stuff.txt");
Path target = Paths.get("D:\\Backup\\MyStuff.txt");
Files.copy(source, target, REPLACE_EXISTING);

其他的复制选项包括COPY_ATTRIBUTES(复制文件属性)和ATOMIC_MOVE(确保在两边的操作都成功,否则回滚)。

移动和复制很像, 移动文件时保留属性,并且覆盖目标文件的示例

import static java.nio.file.StandardCopyOption.*;

Path source = Paths.get("C:\\My Documents\\Stuff.txt");
Path target = Paths.get("D:\\Backup\\MyStuff.txt");
Files.move(source, target, REPLACE_EXISTING, COPY_ATTRIBUTES);

2.4.3 文件的属性
1.基本文件属性支持

Files就可以回答与文件相关的各种问题,比如:

  • 最后修改时间
  • 文件多大
  • 是符号连接吗
  • 是目录吗

代码清单2-4 通用的文件属性

try{
  Path zip = Paths.get("/usr/bin/zip");//获取Path
  /**输出属性**/
  System.out.println(Files.getLastModifiedTime(zip));
  System.out.println(Files.size(zip));
  System.out.println(Files.isSymbolicLink(zip));
  System.out.println(Files.isDirectory(zip));
  System.out.println(Files.readAttributes(zip,"*"));//执行批量读取
} catch(IOException ex){
  System.out.println("Exception [" + ex.getMessage() + "]");
}
2.特定文件属性支持

为了支持文件系统特定的文件属性, Java 7允许文件系统提供者实现FileAttributeViewBasicFileAttributes接口。 代码清单2-5 Java 7 对文件属性的支持

import static java.nio.file.attribute.PosixFilePermission.*;
try
{
  Path profile = Paths.get("/user/Admin/.profile");

  PosixFileAttributes attrs =
      Files.readAttributes(profile,
                           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(OWNER_READ);
  posixPermissions.add(GROUP_READ);
  posixPermissions.add(OTHER_READ);
  posixPermissions.add(OWNER_WRITE);
  Files.setPosixFilePermissions(profile, posixPermissions);
}
catch(IOException e)
{
  System.out.println(e.getMessage());
}

代码清单2-6 符号链接

Path file = Paths.get("/opt/platform/java");
try
{
  if(Files.isSymbolicLink(file)) //检查符号链接
  {
    file = Files.readSymbolicLink(file); //读取符号链接
  }
  Files.readAttributes(file, BasicFileAttributes.class);//读取文件属性
}
catch (IOException e)
{
  System.out.println(e.getMessage());
}

NIO.2 API默认会跟随符号链接。如果不想跟随,需要用LinkOption.NOFOLLOW_LINKS选项。


2.4.4 快速读写数据
1.打开文件

下面的代码演示了Java 7如何用Files.newBufferedReader方法打开文件并按行读取其中的内容。

Path logFile = Paths.get("/tmp/app.log");
try(BUfferedReader reader = Files.newBufferedReader(logFile,
                StandardCharsets.UTF_8)){
  String line;
  while((line = reader.readLine()) !=null ){
    ...
  }
}

打开一个用于写入的文件

Path logFile = Path.get("/tmp/app.log");
try(BufferedWriter writer = Files.newBufferWriter(logFile,
                StandardCharsets.UTF_8,
                 StandardOpenOption.WRITE)){
      writer.write("Hello World!");
      ...            
                }
2.简化读取和写入
Path logFile = Paths.get("/tmp/app.log");
List<String> lines = Files.readAllLines(logFile, StandardCharsets.UTF_8);
byte[] bytes = Files.readAllBytes(logFile);

2.4.5 文件修改通知

在Java 7中可以用java.nio.file.WatchService类监测文件或目录的变化。是现在某些应用 程序中常用的轮询机制的理想替代品。

代码清单2-7 使用WatchService

import static java.nio.file.StandardWatchEventKinds.*;
try{
  WatchService watcher = FileSystem.getDefault().newWatchService();
  Path dir = FileSystem.getDefault().getPath("/usr/karianna");
  WatchKey key = dir.register(watcher, ENTRY_MODIFY) // 检测变化
  while(!shutdown){//检查shutdown标志
    /**得到下一个 key及事件*/
    key = watcher.take();
    for(WatchEvent<?> event: key.pollEvents()){
      if(event.kind() == ENTRY_MODIFY){//检查是否为变化时间
        System.out.println("Home dor changed!");
      }
    }
    key.reset();//重置检测key
  }
}
catch (IOException | InterruptedException e)
{
  System.out.println(e.getMessage());
}

2.4.6 SeekableByteChannel

下面的代码展示了如何运用FileChannel的寻址能力读取日志文件中的最后1000个字符。

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

2.5 异步 I/O操作

Java 7中有三个新的异步通道:

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

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


2.5.1 将来式

代码清单2-8 异步I/O——将来式

try{
  Path file = Paths.get("/usr/karianna/foobar.txt");

  AsynchromousFileChannel channel =
  AsynchromousFileChannel.open(file);//异步打开文件
  /**读取100 000字节*/
  ByteBuffer buffer = ByteBuffer.allocate(100_000);
  Future<Integer> result = channel.read(buffer,0);
  while(!result.isDone()){
    ProfitCalculator.calculateTax();//干点别的事
  }
  Integer byteRead = result.get();//获取结果
  System.out,println("Bytes read [" + bytesRead + "]");
}
catch(IOException | ExecutionException | InterruptedException e)
{
  System.out.println(e.getMessage());
}

2.5.2 回调式

代码清单2-9 异步 I/O——回调式

try{
  Path file = Paths.get("/usr/karianna/foobar.txt")
  AsynchronousFileChannel channel =
    AsynchronousFileChannel.open(file); //以异步方式打开文件

  ByteBuffer buffer = ByteBuffer.allocate(100_000);
  /**从通道中读取数据*/
  channel.read(buffer, 0, buffer, new CompletionHandler<Integer, ByteBuffer>(){
    /**读取完成时的回调方法*/
    public void completed(Integer result,
                          ByteBuffer attachment)
    {
      System.out.println("Bytes read [" + result + "]");
    }
    public void failed(Throwable exception, ByteBuffer attachment)
    {
      System.out.println(exception.getMessage());
           }
  });
}
catch (IOException e)
{
  System.out.println(e.getMessage());
}

2.6 Socket和Channel的整合

接触不多,跳读。

  • NetworkChannel
  • MulticastChannel

2.7 小节

NIO.2 的使用能明显觉得便利,并且不需要花太多心思出像以前一样处理套路式且易错的代码

转载于:https://my.oschina.net/dasven/blog/1800672

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值