Guava Files 源碼分析(一)

Files中的工廠

Files類中對InputStream, OutputStream以及Reader,Writer的操做封裝了抽象工廠模式,抽象工廠是InputSupplier與OutputSupplier,具體工廠是Files中的newInputStreamSupplier(), newOutputStreamSupplier()等方法java

而InputStream, OutputStream以及Reader,Writer則是抽象產品, 他們的各類實現和裝飾器包裝則爲具體產品node

 

Input與Output工廠

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類中的具體工廠實現

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()更安全

 

未完待續 ... 

相關文章
相關標籤/搜索