Files类中对InputStream, OutputStream以及Reader,Writer的操做封装了抽象工厂模式,抽象工厂是InputSupplier与OutputSupplier,具体工厂是Files中的newInputStreamSupplier(), newOutputStreamSupplier()等方法java
而InputStream, OutputStream以及Reader,Writer则是抽象产品, 他们的各类实现和装饰器包装则为具体产品node
Files中将Input与Output(包括InputStream,OutputStream和Reader,Writer两大IO派系)的生成使用抽象工厂来实现,其中表明抽象工厂的两个类为缓存
public interface InputSupplier<T> { T getInput() throws IOException; } public interface InputSupplier<T> { T getInput() throws IOException; }
在某些类的对Input和Output操做的工具方法则会使用这个Supplier做为形参,例如安全
CharStreams.newReaderSupplier(多线程
com.google.common.io.InputSupplier<? extends java.io.InputStream> in,app
java.nio.charset.Charset charsetless
)dom
这样调用CharStreams.newReaderSupplier()即可以经过传入不一样的Supplier具体实现来达到多态和解耦ide
Files中工厂的具体实现抽象为了一个方法,以下:函数
public static InputSupplier<FileInputStream> newInputStreamSupplier( final File file) { Preconditions.checkNotNull(file); return new InputSupplier<FileInputStream>() { @Override public FileInputStream getInput() throws IOException { return new FileInputStream(file); } }; } public static OutputSupplier<FileOutputStream> newOutputStreamSupplier( File file) { return newOutputStreamSupplier(file, false); } public static OutputSupplier<FileOutputStream> newOutputStreamSupplier( final File file, final boolean append) { Preconditions.checkNotNull(file); return new OutputSupplier<FileOutputStream>() { @Override public FileOutputStream getOutput() throws IOException { return new FileOutputStream(file, append); } }; } public static InputSupplier<InputStreamReader> newReaderSupplier(File file, Charset charset) { return CharStreams.newReaderSupplier(newInputStreamSupplier(file), charset); } public static OutputSupplier<OutputStreamWriter> newWriterSupplier(File file, Charset charset) { return newWriterSupplier(file, charset, false); } public static OutputSupplier<OutputStreamWriter> newWriterSupplier(File file, Charset charset) { return newWriterSupplier(file, charset, false); }
这些工厂返回的产品大体包括了FileInputStream, FileOutputStream, Reader, Writer
/** * 这里有意思的有些特殊文件是file.length() == 0,可是文件却有实际内容 * 因此不能直接开辟一个file.length()大小的byte[] buff来读取文件内容 * Guava的解决方法是经过一个buff[0x1000]大小的buff来逐步读取这个特殊的文件 * 将其写入到一个OutputStream之后再一次性将它out.toByteArray()并返回 * * 而对于file.length() != 0 的状况,则是直接开辟一个byte[] buff[file.length()]大小的buff * 一次性将file里的内容读取到buff中并返回,从而避免额外像读取上面说的特殊文件那样频繁的开辟小的byte[] buff */ public static byte[] toByteArray(File file) throws IOException { Preconditions.checkArgument(file.length() <= Integer.MAX_VALUE); if (file.length() == 0) { // Some special files are length 0 but have content nonetheless. return ByteStreams.toByteArray(newInputStreamSupplier(file)); } else { // Avoid an extra allocation and copy. byte[] b = new byte[(int) file.length()]; boolean threw = true; InputStream in = new FileInputStream(file); try { ByteStreams.readFully(in, b); threw = false; } finally { Closeables.close(in, threw); } return b; } } /** * 经过toByteArray()方法将文件内容包装成字符串 */ public static String toString(File file, Charset charset) throws IOException { return new String(toByteArray(file), charset.name()); } /** * 使用小buff byte[0x1000] 的方法来copy文件 * 由于直接使用InputStream不像File.length()那样能够直接得到长度 */ public static void copy(InputSupplier<? extends InputStream> from, File to) throws IOException { ByteStreams.copy(from, newOutputStreamSupplier(to)); }
另外还有不少关于文件读取和写入函数的重载方法,实现方式大同小异,只是参数变化了一下
/** * 依旧是那个问题,有些特殊文件显示的文件长度为0,因此必须经过读取文件的byte内容去比较是否相等 */ public static boolean equal(File file1, File file2) throws IOException { if (file1 == file2 || file1.equals(file2)) { return true; } /* * Some operating systems may return zero as the length for files * denoting system-dependent entities such as devices or pipes, in * which case we must fall back on comparing the bytes directly. */ long len1 = file1.length(); long len2 = file2.length(); if (len1 != 0 && len2 != 0 && len1 != len2) { return false; } return ByteStreams.equal(newInputStreamSupplier(file1), newInputStreamSupplier(file2)); }
/** * Atomically creates a new directory somewhere beneath the system's * temporary directory (as defined by the {@code java.io.tmpdir} system * property), and returns its name. * * <p>Use this method instead of {@link File#createTempFile(String, String)} * when you wish to create a directory, not a regular file. A common pitfall * is to call {@code createTempFile}, delete the file and create a * directory in its place, but this leads a race condition which can be * exploited to create security vulnerabilities, especially when executable * files are to be written into the directory. * * <p>This method assumes that the temporary volume is writable, has free * inodes and free blocks, and that it will not be called thousands of times * per second. * * @return the newly-created directory * @throws IllegalStateException if the directory could not be created */ public static File createTempDir() { File baseDir = new File(System.getProperty("java.io.tmpdir")); String baseName = System.currentTimeMillis() + "-"; for (int counter = 0; counter < TEMP_DIR_ATTEMPTS; counter++) { File tempDir = new File(baseDir, baseName + counter); if (tempDir.mkdir()) { return tempDir; } } throw new IllegalStateException("Failed to create directory within " + TEMP_DIR_ATTEMPTS + " attempts (tried " + baseName + "0 to " + baseName + (TEMP_DIR_ATTEMPTS - 1) + ')'); }
Guava Doc中说到,若是直接使用File.createTempFile()会有安全问题,先介绍一下createTempFile()
public static java.io.File createTempFile(java.lang.String prefix, java.lang.String suffix, java.io.File directory) throws java.io.IOException
它的功能是建立临时文件,提供prefix和suffix以及建立的dir,文件名会使用prefix+random+suffix的形式构成,而中间的random随机数则是使LazyInitialization.random.nextLong()生成,最后使用递归的方式生成这个临时文件
createTempFile()方法的返回值是生成文件之后的抽象路径File对象
根据doc的提示,应该使用 deleteOnExit()方法来删除临时文件,deleteOnExit()的意思是在JVM中止时才删除这个文件.至关于缓存了删除命令,若是有多个文件有deleteOnExit(),他们在JVM中止时会逆序开始删除(最后调用deleteOnExit()方法的文件最早删除,相似于栈)
所谓安全问题, StackOverflow上有人提到了,加入使用createTempFile()来建立临时目录,会这么写
public static File createTempDirectory() throws IOException { final File temp; temp = File.createTempFile("temp", Long.toString(System.nanoTime())); if(!(temp.delete())) { throw new IOException("Could not delete temp file: " + temp.getAbsolutePath()); } if(!(temp.mkdir())) { throw new IOException("Could not create temp directory: " + temp.getAbsolutePath()); } return (temp); }
而后参见几位网友的讨论
按照Sarel Botha的说法,使用了 file.mkdir() 的返回值作判断后抛出异常是不会有问题的
而若是没有使用file.mkdir()的返回值作判断, 潜在的问题是在于Linux的tmp目录使用了sticky bit(只能删除用户本身建立的文件), 在多线程状况下会存在file.delete()和file.mkdir()操做的线程竞争问题.具体是什么我也没搞清楚
原帖地址: http://stackoverflow.com/questions/617414/create-a-temporary-directory-in-java
随意总而言之, 就是使用Guava提供的createTmpDir()更安全