3.4.2 zip/jar文件系统
NIO. 2的一个重要新特性是允许开发人员创建自定义的文件系统实现。在Java 7之前,对文件系统的操作只能使用由Java标准库提供的基于底层操作系统支持的默认实现。Java标准库中的与文件相关的抽象,如File类,都是基于此默认实现的。在有些情况下,默认的文件系统实现不能满足要求,在这种情况下虽然可以开发出自己的文件系统,但是在使用时并没有已有的文件系统那么直接和自然。
NIO. 2把对文件系统的表示抽象出来,形成java.nio.file.FileSystem接口。如果默认的文件系统实现不能满足要求,可以通过实现此接口来添加自定义的实现,如创建基于内存的文件系统,或者创建分布式的文件系统。在FileSystem接口被引入之后,使用文件系统的代码不需要关心文件系统的底层实现细节,只需要通过Java标准库的相关API来操作即可。
除了实现FileSystem接口之外,自定义文件系统的实现还需要实现java.nio.file.spi.FileSystemProvider接口,把自定义的文件系统实现注册到Java平台中。每个文件系统都有一个对应的URI模式作为该文件系统的标识符,比如,默认的文件系统的URI模式是“file”。FileSystemProvider接口的getScheme方法返回的是该模式的值。FileSystemProvider接口的newFileSystem方法用来创建新的文件系统实现,即FileSystem接口的实现对象。在newFileSystem方法的实现中,需要根据作为参数传入的URI或路径创建出对应的FileSystem接口的实现对象。FileSystemProvider接口的实现类以标准的服务提供者接口方式进行注册,所对应的服务名称是“java.nio.file.spi.FileSystemProvider”。通过FileSystemProvider接口的installedProviders方法可以获取程序中当前可用的FileSystemProvider接口实现类的列表。
对于使用者来说,可以通过java.nio.file.FileSystems类中的静态工厂方法来获取或创建FileSystem接口的实现对象,其中getDefault方法用来获取默认的文件系统实现。通常的默认文件系统实现是基于底层操作系统上的文件系统的,可以通过系统参数“java.nio.file.spi.DefaultFileSystemProvider”来设置默认的文件系统实现的Java类名。FileSystems类中的getFileSystem方法根据URI来获取对应的FileSystem类的对象,而newFileSystem方法用来创建新的FileSystem类的对象。
Java标准库中包含了两种文件系统的实现:一种是默认的基于底层操作系统的文件系统的实现,另外一种是NIO.2中新增的操作zip和jar文件的文件系统。Java 7之前处理zip和jar等压缩文件时使用的是java.util.zip包和java.util.jar包中的Java类。这两个包中的Java类使用起来并不灵活。API的用法不同于一般的文件操作,比如向一个已经存在的zip文件中添加一个新文件的需求,通过java.util.zip包中的API来实现的代码如代码清单3-24所示。基本的实现思路是先创建一个临时文件作为中转,把zip文件中已有的内容重新复制,再添加新的文件。
代码清单3-24 向已有的zip文件中添加新文件的传统做法
public void addFileToZip(File zipFile, File fileToAdd)throws IOException{
File tempFile=File.createTempFile(zipFile.getName(),null);
tempFile.delete();
zipFile.renameTo(tempFile);
try(ZipInputStream input=new ZipInputStream(new FileInputStream(tempFile));
ZipOut put Stream out put=newZip Output Stream(new FileOutputStream(zipFile))){
ZipEntry entry=input.getNextEntry();
byte[]buf=new byte[8192];
while(entry!=null){
String name=entry.getName();
if(!name.equals(fileToAdd.getName())){
output.putNextEntry(new ZipEntry(name));
int len=0;
while((len=input.read(buf))>0){
output.write(buf,0,len);
}
}
entry=input.getNextEntry();
}
try(InputStream newFileInput=new FileInputStream(fileToAdd)){
output.putNextEntry(new ZipEntry(fileToAdd.getName()));
int len=0;
while((len=newFileInput.read(buf))>0){
output.write(buf,0,len);
}
output.closeEntry();
}
}
tempFile.delete();
}
如果使用NIO.2中新增的zip/jar文件系统,同样的需求可以通过更加简洁的方式来实现。这种实现方式是把一个zip/jar文件看成一个独立的文件系统,进而使用Java提供的与各种文件操作相关的API。创建基于zip和jar文件的文件系统的方式有两种:一种是使用模式为“jar”的URI来调用FileSystems类的newFileSystem方法;另一种是使用Path接口的实现对象来调用newFileSystem方法。如果文件路径的后缀是“.zip”或“.jar”,会自动创建对应的zip/jar文件系统实现。得到对应的FileSystem类的对象之后,可以使用FileSystem类和Files类中的方法来对文件进行操作。代码清单3-25使用zip/jar文件系统来实现与代码清单3-24同样的需求。相比较而言,代码清单3-25中的逻辑非常简洁清晰,核心的代码只有一行,即调用Files类的copy方法来完成文件的复制操作。同样,可以使用Files类中的createDirectory方法在zip/jar文件中创建新的目录,还可以使用delete方法来删除zip/jar文件中已有的目录或文件。
代码清单3-25 基于zip/jar文件系统实现的添加新文件到已有zip文件的做法
public void addFileToZip2(File zipFile, File fileToAdd)throws IOException{
Map<String, String>env=new HashMap<>();
env.put("create","true");
try(FileSystem fs=FileSystems.newFileSystem(URI.create("jar:"+zipFile.toURI()),env)){
Path pathToAddFile=fileToAdd.toPath();
Path pathInZipfile=fs.getPath("/"+fileToAdd.getName());
Files.copy(pathToAddFile, pathInZipfile, StandardCopyOption.REPLACE_EXISTING);
}
}