问题?
Java7新增了关于文件属性信息的一些新特性,通过java.nio.file.*包下面的类可以实现设置或者读取文件的元数据信息(比如最后修改时间,创建时间,文件大小,是否为目录等等)。尤其是UserDefinedFileAttributeView,可以用来自定义文件的元数据信息。于是在自己的mac上写了个小程序测试了下:
1 import java.io.IOException; 2 import java.nio.ByteBuffer; 3 import java.nio.charset.Charset; 4 import java.nio.file.Files; 5 import java.nio.file.Path; 6 import java.nio.file.Paths; 7 import java.nio.file.attribute.UserDefinedFileAttributeView; 8 import java.util.*; 9 10 public class FileAttributeViewDemo { 11 private final static Charset CS = Charset.forName("UTF-8"); 12 13 public Map<String, String> getAttributes(Path path) throws IOException { 14 Map<String, String> map = new HashMap<>(); 15 Set<String> keys = Files.readAttributes(path, "*").keySet(); 16 for (String attr : keys) { 17 map.put(attr, Files.getAttribute(path, attr).toString()); 18 } 19 return map; 20 } 21 22 public Map<String, String> getUserMeta(Path path) throws IOException { 23 UserDefinedFileAttributeView view = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class); 24 List<String> metaKeys = view.list(); 25 Map<String, String> map = new HashMap<>(); 26 for (String metaKey : metaKeys) { 27 ByteBuffer buff = ByteBuffer.allocate(view.size(metaKey)); 28 view.read(metaKey, buff); 29 buff.flip(); 30 map.put(metaKey, CS.decode(buff).toString()); 31 } 32 return map; 33 } 34 35 public void writeUserMeta(Path path) { 36 UserDefinedFileAttributeView view = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class); 37 try { 38 view.write("author", CS.encode("everSeeker")); 39 view.write("date", CS.encode("20160505")); 40 view.write("title", CS.encode("Effective-Java中文版.pdf")); 41 view.write("pageTotal", CS.encode("229")); 42 } catch (IOException e) { 43 e.printStackTrace(); 44 } 45 } 46 47 public static void main(String[] args) throws IOException { 48 FileAttributeViewDemo demo = new FileAttributeViewDemo(); 49 Path path = Paths.get("/root/xxx/Java/Effective-Java.pdf"); 50 //读取文件属性信息 51 Map<String, String> attrMap = demo.getAttributes(path); 52 for (Map.Entry<String, String> entry : attrMap.entrySet()) { 53 System.out.println(entry.getKey() + " : " + entry.getValue()); 54 } 55 System.out.println("--------------------------------------------------------------"); 56 //写自定义文件属性 57 demo.writeUserMeta(path); 58 //读取自定义文件属性 59 Map<String, String> userMetaMap = demo.getUserMeta(path); 60 for (Map.Entry<String, String> entry : userMetaMap.entrySet()) { 61 System.out.println(entry.getKey() + " : " + entry.getValue()); 62 } 63 } 64 }
整个程序分为3个部分:1、读取文件Effective-Java.pdf文件的元数据信息;2、自定义文件的元数据信息,新增作者,时间,标题,页数这4个文件属性;3、读取自定义的属性信息。
然后运行了下,就报错了。程序的第38行,view.write("author", CS.encode("everSeeker")); 报空指针错误。通过debug模式查看,发现第36行
UserDefinedFileAttributeView view = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
定义的view==null。这就很奇怪了,因为这行代码是jdk 8.0 API手册里面的例子。把代码发给同事在window系统上跑了下,一切正常;自己又在kali linux系统上跑了下,也能正常运行。
分析:
1、首先,通过UserDefinedFileAttributeView自定义的文件元数据信息(UserMeta)肯定是持久化了。因为调用一次writeUserMeta(Path path)方法后,重启程序,直接调用getUserMeta(Path path),还是可以获得自定义的元数据信息。所以现在的问题是,这些信息持久化到哪里去了?这个问题在Vamei的博客里面找到了答案。传送门:Linux文件系统的实现。Linux文件存储系统中的inode(索引节点)存储了文件的大小,时间戳,文件权限等信息以及文件数据块block的位置信息。通过命令stat [文件名]可以直接获得inode的相关信息。
那么,mac系统的文件存储是怎样的呢?通过命令diskutil list发现,我的OSX Yosemite 10.10.5系统的分区格式为Apple_CoreStorage。和Linux系统采取了完全不同的文件系统。所以,基本判断程序异常的原因在于操作系统的区别。
2、继续研究,发现java7之后的supportedFileAttributeViews方法可以查看本机上面支持的FileAttributeView类型。
1 public void supportedWhichViews() { 2 Path path = Paths.get("/Users/xxxx/Books/Java并发编程的艺术.pdf"); 3 FileSystem fileSystem = path.getFileSystem(); 4 Set<String> supportedViews = fileSystem.supportedFileAttributeViews(); 5 6 for (String view : supportedViews) { 7 System.out.println(view); 8 } 9 }
运行结果为basic, owner, unix, posix;而在Linux系统上运行这段代码,结果为owner, dos, basic, posix, user, unix。可见空指针异常的原因在于在mac osx系统,Java7根本就不支持UserDefinedFileAttributeView这个类。