《Java核心技术卷二》笔记(二)文件操作和内存映射文件
文件操作
上一篇已经总结了流操作,其中也包括文件的读写。文件系统除了读写以为还有很多其他的操作,如复制、移动、删除、目录浏览、属性读写等。在Java7之前,一直使用File类用于文件的操作。Java7提供了Path,Paths,Files等类,使文件操作变得简单和全面。此外还有很多第三方库也提供了文件操作的便捷类如common.io中的FileUtils类,Ant api提供的FileSet等类。
1.File类的使用
Java7之前版本中,File类即代表了路径对象也封装了文件的绝大部分操作。
File
-
- File(String pathname)
- File(String parent, String child)
- File(File parent, String child)
- File(URI uri)
- URI toURI()
- Path toPath()
- String getPath()/toString()
- String getName() 最后一段
- String getParent()
- File getParentFile()
- String getAbsolutePath()
- File getAbsoluteFile()
- String getCanonicalPath()
- File getCanonicalFile()
- boolean isAbsolute()
- boolean isFile()
- boolean isDirectoy()
- boolean exists()
- long length()
- boolean createNewFile()
- boolean delete()
- void deleteOnExit()
- boolean mkdir()
- boolean mkdirs()
- boolean renameTo(File dst)
- File createTempFile(String prefix, String suffix, File directory)
- File createTempFile(String prefix, String suffix)
- String[] list()
- String[] list(FilenameFilter filter)
- File[] listFiles()
- File[] listFiles(FilenameFilter filter)
- File[] listFiles(FileFilter filter)
- boolean isHidden()
- long lastModified()
- boolean canRead()
- boolean canWrite()
- boolean canExecute()
- boolean setLastModified(long time)
- boolean setReadOnly()
- boolean setWritable(boolean writable, boolean ownerOnly)
- boolean setWritable(boolean writable)
- boolean setReadable(boolean readable, boolean ownerOnly)
- boolean setReadable(boolean readable)
- boolean setExecutable(boolean executable, boolean ownerOnly)
- boolean setExecutable(boolean executable)
- long getTotalSpace() 返回文件所在分区的总大小
- long getFreeSpace()
- long getUsableSpace()
2.Path接口和Files类的使用
Java7中提供了Path接口代表了文件系统路径对象,而Files类封装了文件系统的绝大部分操作。
基本用法
Paths 使用默认的FileSystem类生成Path对象
-
- static Path get(String first, String... more) 拼出路径
- static Path get(URI uri) 将URI对象转化成Path对象
File类的Path toPath()方法也可以将File对象转化成Path对象。
Path 路径对象
-
- File toFile()
- URI toUri()
- String toString()
- Path normalize() 移除., ..等冗余路径元素
- Path toAbsolutePath() 返回等价的绝对路径
- Path relativize(Path other) 返回相对于other的相对路径
- Path getParent() 返回父路径
- Path getFileName() 返回路径最后一个部件
- Path getRoot() 返回根路径
- Path resolve(Path/String other) 如果other为绝对路径,返回other;否则,将other接在this路径的后面返回。
- Path resolveSibling(Path/String other) 如果other为绝对路径,返回other;否则,将other接在this的父路径后面返回。
- boolean startsWith(Path/String other)
- boolean endsWith(Path/String other)
files类方法中涉及的Option选项用的是不定参数,同时使用多个选项的方式为:
Files.copy(src, dst, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING);
Files
-
- static byte[] readAllBytes(Path path) 不太适合大文件
- static List<String> readAllLines(Path path, Charset cs) 不太适合大文件
- static Path write(Path path, byte[] bytes, OpenOption...options) 如StandardOpenOption.APPEND|READ|WRITE....
- static Path write(Path path, Iterable<? extends CharSequence>, Charset cs, OpenOption... options)
- static InputStream newInputStream(Path p, OpenOption...options) 将文件打开为输入流
- static OutputStream newOutputStream(Path p, OpenOption...options) 将文件打开为输出流
- static BufferedReader newBufferedReader(Path path, Charset cs)
- static BufferedWriter newBufferedWriter(Path path, Charset cs, OpenOption...options)
- static Path copy(Path src, Path dst, CopyOption...options) 如StandardCopyOption.REPLACE_EXISTING|COPY_ATTRIBUTES|ATOMIC_MOVE
- static Path copy(Path src, OutputStream os )
- static Path copy(InputStream src, Path dst, CopyOption...options)
- static Path move(Path src, Path dst, CopyOption...options)
- static void delete(Path path) 删除的文件不存在会抛出异常,下面这个方法更省事
- static boolean deleteIfExists(Path path)
- static Path createDirectory(Path dir, FileAttribute<?> ...attrs) 不会自动创建中间目录
- static Path createDirectorys(Path dir, FileAttribute<?> ...attrs) 自动创建中间目录
- static Path createFile(Path path, FileAttribute<?> ...attrs) 不会自动创建中间目录,文件已经存在也会抛出异常,检查文件存在和创建文件是原子操作,其他程序在这个过程中也无法创建文件。
- static Path createTempFile(Path path, String prefix, String suffix, FileAttribute<?>...attr) 创建临时文件,可以固定文件名前缀或(和)后缀,中间部分随机生成。未指定Path的重载方法会自动在适当的临时文件目录中创建临时文件。
- static long size(Path path) 文件字节数
- static boolean exists(Path path, LinkOption...option)
- static boolean noExists(Path path, LinkOption...option)
- static boolean isHidden(Path path)
- static boolean isReadable(Path path)
- static boolean isWritable(Path path)
- static boolean isExecutable(Path path)
- static boolean isRegularFile(Path path, LinkOption...option)
- static boolean isDirectory(Path path, LinkOption...option)
- static boolean isSymbolicLink(Path path)
- static UserPrincipal getOwner(Path path, LinkOption...option)
- static Path setOwner(Path path, UserPrincipal owner)
- static Set<PosixFilePermission> getPosixFilePermissions(Path path, LinkOption...option)
- static Path setPosixFilePermissions(Path path, Set<PosixFilePermission> perms)
- static FileTime getLastModifiedTime(Path path, LinkOption...option)
- static Path setLastModifiedTime(Path path, FileTime time)
- static <A extends BasicFileAttributes> A readAttributes(Path path, Class<A> type, LinkOption... options)
- static Map<String,Object> readAttributes(Path path, String attributes, LinkOption... options)
- static Object getAttribute(Path path, String attribute, LinkOption...options)
- static Path setAttribute(Path path, String attribute, Object value, LinkOption...options)
- static DirectoryStream<Path> newDirectoryStream(Path path) 生成的DirectoryStream类实现了Iterable<T>接口,可以用来迭代目录内容
- static DirectorySteram<Path> newDirectoryStream(Path path, String glob) 生成的DirectoryStream可以迭代与glob模式匹配的目录内容(glob模式见后面)
- static DirectoryStream<Path> newDirectoryStream(Path path, Filter<? super Path> filter) 生成的DirectoryStream可以迭代与过滤器匹配的目录内容
- static Path walkFileTree(Path path, FileVisitor<? super Path> visitor) 遍历目录内容
- static Path walkFileTree(Path path, Set<FileVisitOption> options, int maxDepth, FileVisitor<? super Path> visitor)
文件属性
通过读取文件属性对象可以获取文件的各种属性信息(直接使用Files类的一些方法也可以获取或设置部分属性信息)。
BasicFileAttributes接口描述了文件的通用属性(FileTime类型的创建/最后一次访问/最后一次修改时间,常规文件/目录/符号链接,文件大小,文件主键),通用属性获取方式为: BasicFileAttributes attributes=Files.readAttributes(path, BasicFileAttributes.class);
Posix兼容文件系统还可以获取Posix属性:PosixFileAttributes attributes=Files.readAttributes(path, PosixFileAttributes.class);
PosixFilePermission为枚举类型,对应Posix的9种权限。
BasicFileAttributes
-
- FileTime creationTime()
- FileTime lastAccessTime()
- FileTime lastModifiedTime()
- boolean isRegularFile()
- boolean isDirectory()
- boolean isSymoblicLink()
- boolean isOther() 上面三种类型都不是
- long size()
- Object fileKey() 文件唯一标识
PosixFileAttributes implements BasicFileAttributes
-
- UserPrincipal owner()
- UserPrincipal group()
- Set<PosixFilePermission> permissions()
目录迭代
File类的list方法在目录包含大量的文件时效率较低,Files提供了改进的方法Directory newDirectoryStream(Path dir) 可以迭代当前目录中的对象(不包含子目录中对象)。该方法还有一个支持glob模式的重载和一个支持过滤器的重载。
Glob模式
-
- * 当前目录中匹配0个或多个字符
- ** 所有子目录中匹配0个或多个字符
- ? 匹配一个字符
- [...] 匹配一个字符集合,可用连字符 - 和取反符 ' ,如[0-9A-Za-z], ['0-9]
- {.. , ..} 匹配由 , 分隔的任意选项
- \ 转义glob特殊字符
DirectoryStream<Path> iter=Files.newDirectoryStream(Paths.get("H://zipTest")); DirectoryStream<Path> iter1=Files.newDirectoryStream(Paths.get("H://zipTest"),"*.txt"); DirectoryStream<Path> iter2=Files.newDirectoryStream(Paths.get("H://zipTest"),new DirectoryStream.Filter<Path>(){ @Override public boolean accept(Path entry) throws IOException { return !Files.isDirectory(entry); }}); System.out.println("---all---"); for(Path p: iter) System.out.println(p.toString()); System.out.println("---*.txt---"); for(Path p: iter1) System.out.println(p.toString()); System.out.println("---only file---"); for(Path p: iter2) System.out.println(p.toString());
目录遍历
遍历目录中以及所有子目录中所有项目,使用walkFileTree方法,该方法需要传入一个FileVisitor接口的对象。SimpleFileVisitor是一个实现了该接口的便捷类,该类中visitFileFailed方法会抛出异常终止遍历,其他方法什么都不做。
FileVisitor<T>
-
- FileVisitResult preVisitDirectory(T dir, BasicFileAttributes attrs) 一个目录被处理前被调用
- FileVisitResult postVisitDirectory(T dir, IOException exc) 一个目录被处理后被调用
- FileVisitResult visitFile(T file, BasicFileAttributes attrs) 遇到一个文件时调用(T为Path)
- FileVisitResult visitFileFailed(T file, IOException exc) 访问文件发生错误时调用,如无权限
walkFileTree遍历时会读取项目的属性用来判断该项目是目录还是文件,读取到的属性值就顺便传给FileVisitor的方法,FileVisitor的方法里就不需要手动去读属性了。
FileVisitor的每个方法都返回FileVisitResult枚举,指示如何进行后续操作。
FileVisitorResult
-
- CONTINUE 继续访问下一个项目
- TERMINATE 终止遍历
- SKIP_SUBTREE 跳过这个目录下的所有子项,继续遍历
- SKIP_SIBLINGS 跳过这个目录的所有平级兄弟项目,继续遍历
Files.walkFileTree(Paths.get("H://zipTest"), new SimpleFileVisitor<Path>() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { System.out.println(dir.toString()); return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { System.out.println("\t"+file.toString()); return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { return FileVisitResult.CONTINUE; } });
zip文件系统
Path、Files等类默认都是使用的当前操作系统的磁盘文件系统类型,也可以使用其他类型的文件系统。
FileSystems.newFileSystem(xxx)方法可以创建一个文件内的文件系统,然后使用和普通文件系统一样的方式操作该文件的内容。 (基于一个文件创建文件系统时会遍历 FileSystemProvider的installedProviders方法返回的所有FileSystemProvider,查找一个能创建该文件系统的FileSystemProvider,没有找到的话还查找ClassLoader能加载的文件系统...)
直接用Paths.get(xxx)方法获取是在默认文件系统里的路径(等价于FileSystems.getDefault().getPath(xxx); ),而使用FileSystem对象的getPath()方法获取的路径就是该文件系统中的路径。
FileSystem fs=FileSystems.newFileSystem(Paths.get("ziptest.zip"), null); Path copyFile=Paths.get("copyfile.txt");
Files.deleteIfExists(copyFile); Files.copy(fs.getPath("textfile.txt"), copyFile); Files.walkFileTree(fs.getPath("/"), new SimpleFileVisitor<Path>(){ @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { System.out.println(file.toString()); return FileVisitResult.CONTINUE; } });
fs.close();
3.底层的文件系统
FileSystems
FileSystem
留坑待填...
内存映射文件
内存映射文件只指将整个文件或者文件的一部分映射到内存中,这个文件就可以像内存数组一样访问。内存映射速度比带缓存的流还要快一点(带缓冲的流可以极大提高读写速度)。内存映射适用于需要随机访问大文件,对应顺序对入的中小文件没必要使用。
内存映射文件会用到FileChannel类,对文件的读写都转化成了对内存缓冲区的操作。内存映射文件的操作:首先是建立文件通道,然后调用map方法获取映射的内存缓冲区,最后操作内存缓冲区即可(缓冲区中的更改会在适当的时候和通道关闭时写回存储设备)。
FileChannel类是对磁盘文件的一种抽象,读写操作都是与缓冲区交互,主要用在内存映射,文件加锁机制,以及文件间快速数据传递等操作系统特性。
FileChannel
-
- static FileChannel open(Path path, OpenOption... options) 也使用Files类介绍中提到的StandardOpenOption
- static FileChannel open(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs)
- long size() 文件大小
- long position() 返回文件指针位置
- void position(long pos) 设置文件指针位置
- void force(boolean metaData) 强制将文件的变更同步到存储设备
- FileChannel truncate(long size) 截断
- int read(ByteBuffer dst)... 从通道读入内容到缓冲区
- int write(ByteBuffer src)... 将缓冲区内容写入通道
- long transferTo(xxx)
- long transferFrom(xxx)
- MappedByteBuffer map(MapMode, long startposition, long size) MapMode.READ_ONLY|READ_WRITE|PRIVATE
- void close() 关闭通道,如果有可写的内存映射,其中的更改会写回存储设备
FileInputStream
-
- FileChannel getChannel()
FileOutputStream
-
- FileChannel getChannel()
RandomAccessFile
-
- FileChannel getChannel()
CRC32 crc=new CRC32(); CRC32 crc1=new CRC32(); FileChannel fc=FileChannel.open(Paths.get("copyfile.txt"),StandardOpenOption.READ,StandardOpenOption.WRITE); MappedByteBuffer buffer=fc.map(MapMode.READ_WRITE, 0, fc.size()); //随机访问缓冲区 for(int p=0; p<buffer.limit(); p++) { int c=buffer.get(p); crc.update(c); } //顺序访问缓冲区 for(int p=0; p<buffer.limit(); p++) { int c=buffer.get(); crc1.update(c); } System.out.println(Long.toHexString(crc.getValue())); System.out.println(Long.toHexString(crc1.getValue()));
内存缓冲区
前面的例子已经用到了缓冲区类,现在详述总结一下这些类。缓冲区类其实就是相同类型数据构成的一个数组,再加上对缓冲区操作的一些方法。
缓冲区类的共同特点:
- 都有一个固定的容量,不可改变。
- 有一个读写位置,下一个值在此位置读写。
- 有一个界限,超过它之后读写就没有意义。
- 一个可选的标记位置,可以移动到该位置重复读入或写出。
- 各个位置的关系为:0<=标记<=读写位置<=界限<=容量
- 写和读操作在缓冲区上反复交替进行,缓冲区被循环使用。
缓冲区类一般都使用静态的allocate()方法创建或者使用warp()方法将一个数组包装成缓冲区对象。
使用过程:
- 重置:调用clear()方法,缓冲区的位置=0,界限=容量。
- 写入:然后向缓冲区写入数据,直到缓冲区位置=容量(占满)或者数据已经写完。
- 反转:调用flip()方法,缓冲区界限=位置,位置=0
- 读取:从缓冲区读取数据,直到位置=界限。然后再重置,一直循环...
读写的过程中可以调用rewind()将位置重置为0,重头读写。或者调用mark()/reset()使用标记,重新读写部分内容。
java中缓冲区都继承自Buffer抽象类,继承关系如下(不包括StringBuffer)。
Buffer
-
- ByteBuffer
- MappedByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
- ByteBuffer
Buffer
-
- Buffer clear() 重置(读写位置=0,界限=容量),准备写入
- Buffer flip() 反转(界限=读写位置,新读写位置=0),准备读取
- Buffer rewind() 界限不变,读写位置=0,可以重头读写
- Buffer mark() 在当前读写位置设置标记
- Buffer reset() 重置读写位置到标记,可以重新标记后的内容
- int remaining() 剩余可读或写的数量,即 limit-position
- boolean hasRemaining() 是否还可读或可写。即 position<limit ?
- int position() 返回当前的读写位置
- Buffer position(int newpos) 改变读写位置
- int capacity() 返回缓冲区容量
- int limit() 返回限制位置
- Buffer limit(int limit) 设置限制位置
- boolean hasArray() 该Buffer是否是包装了一个数组
- Object array() 返回该Buffer包装的数组
- int arrayOffset() 如果包装了数组,返回数组被用作缓冲的起始位置(可以将数组的一部分包装成缓冲区)。
- boolean isDirect()
- boolean isReadOnly()
最最常用的缓冲区类ByteBuffer和CharBuffer已经在前一篇中写过 : http://www.cnblogs.com/pixy/p/4779820.html
MappedByteBuffer
-
- boolean isLoaded()
- MappedByteBuffer load()
- MappedByteBuffer force()
文件加锁机制
文件加锁机制可以防止多个同时执行的程序修改同一个文件导致文件被破坏。文件加锁也通过FileChannel类实现。
文件锁是由整个Java虚拟机持有的,由同一个虚拟器启动的不同Java进程不能同时独占锁定同一个文件。
文件加锁机制依赖于底层文件系统,有些系统无法锁定,有些系统锁定后不能映射,有些系统关闭一个通道会是否对应文件上所有的锁(避免在一个文件上使用多个通道),网络文件系统尽量避免使用锁。
FileChannel
-
- FileLock lock() 阻塞直到获得锁
- FileLock lock(long position, long size, boolean shared) 锁定文件的一部分,阻塞直到获得锁(锁定区域之后新增内容不会被锁,除非size设为Long.MAX_VALUE), shared设为true时获得共享税(允许多个进程读入,阻止任何进程独占)。
- FileLock tryLock() 立刻返回,无法获得锁时返回null
- FileLock tryLock(long position, long size, boolean shared)
FileLock
-
- void close()
- boolean isShared() 返回是否为共享锁
链接文件
Path link = FileSystems.getDefault().getPath("realFile"); Path target = FileSystems.getDefault().getPath("linkfile"); //create hard link Files.createLink(link, target); //create symbolic link Files.createSymbolicLink(link, target); //create symbolic with file attribute PosixFileAttributes attrs = Files.readAttributes(target, PosixFileAttributes.class); FileAttribute<Set<PosixFilePermission>> attr = PosixFilePermissions.asFileAttribute(attrs.permissions()); Files.createSymbolicLink(link, target, attr); boolean isSymbolciLink1 = Files.isSymbolicLink(link); boolean isSymbolciLink2 = (boolean) Files.getAttribute(link, "basic:isSymbolicLink"); //get linked file Path linkedPath=Files.readSymbolicLink(link);