在NIO.2的文件I/O中,Path表示文件系统中的位置。这个Path类似于java.io.File类对象,并不表示一个已经存在的实际文件或目录,所以如果你想创建一个Path来表示即将要创建的目录或文件是完全可以的。但是要注意,如果这个文件不存在,读取时就会出现错误。
在File中已经介绍过,File转换为Path需要调用File类中的toPath()方法,同样,Path也可以转换为File,调用toFile()方法即可。java.nio.file.Path接口中定义了一些文件操作常用的方法。如创建Path、获取路径的相关信息等,如下:
Path listing = Paths.get("\\foo\\x");// 创建一个Path System.out.println(listing.isAbsolute());// false 是否为绝对路径 System.out.println(listing.toAbsolutePath());// C:\foo\x System.out.println( listing.getFileName() );// 获取文件名 System.out.println("Number of Name Elements in the Path "+ listing.getNameCount() );// 2 System.out.println("Parent Path " + listing.getParent() );// \foo System.out.println("Root of Path " + listing.getRoot() );// // 从根到第二个元素之间的子路径,离根最近的为0,而离根最远的元素为getNameCount-1 System.out.println("Subpath from Root, 2 elements deep "+ listing.subpath(0, 2) );// foo\x
Paths类是一个工具类,提供返回一个路径的辅助方法,源代码如下:
public final class Paths { private Paths() { } //more public static Path get(String first, String... more) { return FileSystems.getDefault().getPath(first, more); } public static Path get(URI uri) { String scheme = uri.getScheme(); if (scheme == null) throw new IllegalArgumentException("Missing scheme"); // check for default provider to avoid loading of installed providers if (scheme.equalsIgnoreCase("file")) return FileSystems.getDefault().provider().getPath(uri); // try to find provider for (FileSystemProvider provider: FileSystemProvider.installedProviders()) { if (provider.getScheme().equalsIgnoreCase(scheme)) { return provider.getPath(uri); } } throw new FileSystemNotFoundException("Provider \"" + scheme + "\" not installed"); } }可以调用如上的两个方法获取Path对象。获取到后就可以进行操作了。下面来看一下查找和遍历目录树的例子。
public class TestDirectoryStream { public static void main(String[] args) throws IOException { Path listing = Paths.get("C:\\foo\\x"); List l=TestDirectoryStream.listSourceFiles(listing); for(int i=0;i<l.size();i++ ){ System.out.println(l.get(i));// 输出 } } static List<Path> listSourceFiles(Path dir) throws IOException { List<Path> result = new ArrayList<>(); try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir,"*.{c,h,cpp,hpp,txt}")) { for (Path entry : stream) { // stream实例可以迭代 result.add(entry); } } catch (DirectoryIteratorException ex) { throw ex.getCause(); } return result; } }查找并输出x目录下的所有符合glob模式匹配的文件。调用的主要方法就是Files类下的newDirectoryStream()方法,源代码如下:
public static DirectoryStream<Path> newDirectoryStream(Path dir, String glob) throws IOException { // avoid creating a matcher if all entries are required. if (glob.equals("*")) return newDirectoryStream(dir); // create a matcher and return a filter that uses it. FileSystem fs = dir.getFileSystem(); final PathMatcher matcher = fs.getPathMatcher("glob:" + glob); DirectoryStream.Filter<Path> filter = new DirectoryStream.Filter<Path>() { @Override public boolean accept(Path entry) { return matcher.matches(entry.getFileName()); } }; return fs.provider().newDirectoryStream(dir, filter); }或者还可以遍历目录树并查找文件:
public class TestFileVisitor { public static void main(String[] args) throws IOException { Path startingDir = Paths.get("c:\\foo"); Files.walkFileTree(startingDir, new FindJavaVisitor()); } private static class FindJavaVisitor extends SimpleFileVisitor<Path> { public FileVisitResult preVisitDirectory(Path file, BasicFileAttributes attrs) { return FileVisitResult.CONTINUE; } public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { if (file.toString().endsWith(".txt")) { System.out.println(file.getFileName()); } return FileVisitResult.CONTINUE; } } }用到的主要方法就是Files的walkFileTree(Path startingDir,FileVisitor<? super Path> visitor)。由于FileVisitor类接口中定义的方法很多,为了减少工作量,我们可以继承SimplefileVisitor类来实现部分方法,然后在这些方法中定义我们自己的功能,如visitFile()方法中将扩展名为.txt的文件输出。
如果要针对某个文件进行如下操作时,主要是通过提供的Files工具类来完成。
1、创建、删除
Path p=Paths.get("c:\\foo\\pp.txt"); //Files.createFile(p);// 创建pp.txt文件 Files.delete(p);// 删除pp.txt文件
2、移动、复制、重命名文件
Path p1=Paths.get("c:\\foo\\pp.txt"); Path p2=Paths.get("c:\\foo\\x\\pp2.txt"); Path p3=Paths.get("c:\\foo\\x\\pp3.txt"); Files.copy(p1, p2);// 复制pp.txt文件到x目录下并且重新命名为pp2.txt Files.move(p1, p3);// 将pp.txt文件移动到x目录下并且重新命名为pp3.txt
3、文件属性的处理
try { Path zip = Paths.get("C:\\foo"); System.out.println(zip.toAbsolutePath().toString());// C:\foo System.out.println(Files.getLastModifiedTime(zip));// 2014-01-26T09:46:07.696941Z System.out.println(Files.size(zip));// 4096 System.out.println(Files.isSymbolicLink(zip));// false System.out.println(Files.isDirectory(zip));// true // 执行批量读取 System.out.println(Files.readAttributes(zip, "*")); } catch (IOException ex) { System.out.println("Exception" + ex.getMessage()); }最后一行运行结果如下:
{lastModifiedTime=2014-01-26T09:46:07.696941Z, fileKey=null, isDirectory=true, lastAccessTime=2014-01-26T09:46:07.696941Z, isOther=false, isSymbolicLink=false, isRegularFile=false, creationTime=2013-12-02T11:54:55.062597Z, size=4096}
4、读取、写入文件
在新的NIO中,可以对文件进行行读取,也可以按照行进行写入,使用到的主要方法就是Files类中的newBufferedReader()和newBufferedWriter(),源代码如下:
public static BufferedReader newBufferedReader(Path path, Charset cs) throws IOException { CharsetDecoder decoder = cs.newDecoder(); Reader reader = new InputStreamReader(newInputStream(path), decoder); return new BufferedReader(reader); } public static BufferedWriter newBufferedWriter(Path path, Charset cs,OpenOption... options)throws IOException { CharsetEncoder encoder = cs.newEncoder(); Writer writer = new OutputStreamWriter(newOutputStream(path, options), encoder); return new BufferedWriter(writer); }可以看到,其实是通过装饰器模式对流进行一步步的封装,最后得到了缓冲流。
编写的测试程序如下:
Path logFile=Paths.get("C:\\foo\\mm.txt"); try(BufferedReader reader=Files.newBufferedReader(logFile, StandardCharsets.UTF_8)){ String line; while((line=reader.readLine())!=null){ System.out.println(line); } }注意,虽然StandardCharsets.UTF-8指定了编码,但是如果有汉字,仍然会报出java.nio.charset.MalformedInputException异常。
Path logFile=Paths.get("C:\\foo\\mm.txt"); try(BufferedWriter writer=Files.newBufferedWriter(logFile, StandardCharsets.UTF_8,StandardOpenOption.WRITE)){ writer.write("1234"); writer.write("lkajd"); }
如上演示了行的读取和写入,其实在Files类中还提供了两个辅助方法readAllLines()和readAllBytes()方法,用于读取全部行和全部字节。源代码如下:
public static byte[] readAllBytes(Path path) throws IOException { long size = size(path); if (size > (long)Integer.MAX_VALUE) throw new OutOfMemoryError("Required array size too large"); try (InputStream in = newInputStream(path)) { return read(in, (int)size); } } public static List<String> readAllLines(Path path, Charset cs) throws IOException { try (BufferedReader reader = newBufferedReader(path, cs)) { List<String> result = new ArrayList<>(); for (;;) { String line = reader.readLine(); if (line == null) break; result.add(line); } return result; } }可以看到readAllLines()方法其实就是按行读取内容,然后存储到List中并返回。而readAllBytes()读取的文件字节数不能大于Integer.MAX_VALUE,否则会抛出异常。调用read()方法,源代码如下:
private static byte[] read(InputStream source, int initialSize) throws IOException{ int capacity = initialSize; byte[] buf = new byte[capacity]; int nread = 0; int rem = buf.length; int n; // read to EOF which may read more or less than initialSize (eg: file // is truncated while we are reading) while ((n = source.read(buf, nread, rem)) > 0) { nread += n; rem -= n; assert rem >= 0; if (rem == 0) { // need larger buffer int newCapacity = capacity << 1; if (newCapacity < 0) { if (capacity == Integer.MAX_VALUE) throw new OutOfMemoryError("Required array size too large"); newCapacity = Integer.MAX_VALUE; } rem = newCapacity - capacity; buf = Arrays.copyOf(buf, newCapacity); capacity = newCapacity; } } return (capacity == nread) ? buf : Arrays.copyOf(buf, nread); }其实最终还是调用了字节流的read()方法进行读取,然后将读取到的结果拷贝到一个更大的缓存数组中。
5、灵活读取、写入文件
在NIO中,可以使用SeekableByteChannel来灵活的处理文件,类似于RandomAccessFile类提供的一些方法,但是这里提出了一种新的I/O抽象-通道。SeekableByteChannel接口的定义如下:
public interface SeekableByteChannel extends ByteChannel{ int read(ByteBuffer dst) throws IOException; int write(ByteBuffer src) throws IOException; /** * Returns this channel‘s position. */ long position() throws IOException; /** * Sets this channel‘s position. */ SeekableByteChannel position(long newPosition) throws IOException; /** * Returns the current size of entity to which this channel is connected. */ long size() throws IOException; /** * Truncates the entity, to which this channel is connected, to the given * size. */ SeekableByteChannel truncate(long size) throws IOException; }可以改变通道的位置和大小,所以处理文件内容更加灵活。这个接口的一个抽象实现类为java.nio.channels.FileChannel。通过调用open()方法来获取FileChannel的具体实例。编写一个测试实例,如下:
Path logFile=Paths.get("C:\\temp.txt"); ByteBuffer buffer=ByteBuffer.allocate(1024); FileChannel channel=FileChannel.open(logFile, StandardOpenOption.READ); channel.read(buffer,channel.size()-100);// 读取文件最后的100个字符 buffer.flip();//此行语句一定要有 while (buffer.hasRemaining()) { System.out.print((char)buffer.get()); }输出文件最后100个字符。