• Guava Files 源码分析(一)


    Files中的工厂

    Files类中对InputStream, OutputStream以及Reader,Writer的操作封装了抽象工厂模式,抽象工厂是InputSupplier与OutputSupplier,具体工厂是Files中的newInputStreamSupplier(), newOutputStreamSupplier()等方法

    而InputStream, OutputStream以及Reader,Writer则是抽象产品, 他们的各种实现和装饰器包装则为具体产品

     

    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,

    java.nio.charset.Charset charset

    )

    这样调用CharStreams.newReaderSupplier()便可以通过传入不同的Supplier具体实现来达到多态和解耦

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

    未完待续 ... 

  • 相关阅读:
    JQuery的js写法
    Reapter 中客户端控件和服务器端控件的选择
    ASP.NET 中随时向页面输入数据
    SQL补充查询
    repeater项模版bottom事件获取该bottom所在行id为lblName的label控件的text
    微软Visual Studio 2010架构设计功能应用
    动态表格
    如何选中jsTree中已checked的Item的信息
    据说看完这21个故事的人,30岁前都成了亿万富翁。你是下一个吗?
    WebSocket编解码器
  • 原文地址:https://www.cnblogs.com/zemliu/p/3316030.html
Copyright © 2020-2023  润新知