# 正确地处理文件

**拼接文件路径：**&#x4F7F;用Path，且文件分隔符使用File.pathSeparator，不要硬编码为`/`或者`\`

```java
//假设有如下文件：/Users/yue/Desktop/test.ev4
Path path = Paths.get("/Users", "yue", "Desktop", "test.ev4");

//注意，这种方式是错误的，即Path只会从第二个参数开始，自动添加文件分隔符，且第一个参数应该是有效路径
Path path = Paths.get(File.pathSeparator, "Users", "yue", "Desktop", "test.ev4");
```

**读取文件所有内容前，先判断文件大小，防止OOM**

```java
public static byte[] readAllBytes(String fileName, long maxSize) throws IOException {
    Path path = Paths.get(fileName);
    long size = Files.size(path);
    if (size > maxSize) {
        throw new IOException("file: " + path + ", size:" + size + "> " + maxSize);
    }
    return Files.readAllBytes(path);
}

public static List<String> readAllLines(String fileName, Charset charset, long maxSize) throws IOException {
    Path path = Paths.get(fileName);
    long size = Files.size(path);
    if (size > maxSize) {
        throw new IOException("file: " + path + ", size:" + size + "> " + maxSize);
    }
    return Files.readAllLines(path, charset);
}
```

**遍历文件：**&#x4F7F;用FileVisitor

```java
Path startingDir = Paths.get("/Users/yue/Desktop");
try {
    Files.walkFileTree(startingDir, new SimpleFileVisitor<Path>() {
        @Override
        public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException {
            System.out.println(path.getFileName());
            return FileVisitResult.CONTINUE;
        }
    });
} catch (IOException e) {
    e.printStackTrace();
}
```

**判断文件是否在某路径下：**&#x4F7F;用file.getCanonicalPath()，不必递归

```java
//使用file.getCanonicalPath()
public static boolean isSubFile(File dir, File file) throws IOException {
    return file.getCanonicalPath().startsWith(dir.getCanonicalPath() + File.pathSeparator);
}
//递归方式
public static boolean isInDirectory(File dir, File file) {

    if (file == null)
        return false;

    if (file.equals(dir))
        return true;

    return isInDirectory(dir, file.getParentFile());
}
```

**监视文件改变：**&#x4F7F;用WatchService

```java
public class Configs {
    public static Properties properties;
    static {
        final String configFilename = "app.properties";
        final ClassPathResource resource = new ClassPathResource(configFilename);
        try {
            //先加载一次配置文件
            properties = PropertiesLoaderUtils.loadProperties(resource);
            //设置监听配置文件所在目录的文件删除/新增操作
            final WatchService watcher = FileSystems.getDefault().newWatchService();
            Paths.get(resource.getFile().getParent()).register(watcher,
                    StandardWatchEventKinds.ENTRY_CREATE,
                    StandardWatchEventKinds.ENTRY_DELETE,
                    StandardWatchEventKinds.ENTRY_MODIFY);
            //开启监听线程
            Thread watchThread = new Thread(() -> {
                try {
                    WatchKey watchKey = watcher.take();
                    for (WatchEvent<?> event : watchKey.pollEvents()) {
                        //如果是配置文件发生变化，则重新加载配置文件
                        System.out.println(event.kind() + ":::" + event.context().toString());
                        if (Objects.equals(configFilename, event.context().toString())) {
                            properties = PropertiesLoaderUtils.loadProperties(resource);
                            break;
                        }
                    }
                    watchKey.reset();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
            watchThread.setDaemon(true);
            watchThread.start();

            //JVM停止时，关闭watcher线程
            Runtime.getRuntime().addShutdownHook(new Thread(() -> {
                try {
                    watcher.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static boolean getBoolean(String key) {
        String property = properties.getProperty(key, "false");
        return "true".equals(property);
    }
    public static int getInt(String key) {
        String property = properties.getProperty(key, "0");
        return Integer.valueOf(property);
    }
}
```

**Web服务器防止非法的文件路径访问**

字符截断攻击和文件历遍漏洞原理：在文件名中插入%00的URL编码，web服务器会把%00后面的内容抛弃。

例如这样的URL：<http://www.test.com/../../../../etc/passwd�.gif>

防范方法

* 写入文件前，判断文件是否在父路径下，参考上面的函数。
* 利用Java的安全机制

```
// All files in /img/java can be read
grant codeBase "file:/home/programpath/" {
  permission java.io.FilePermission "/img/java", "read";
};
```

**参考**

[在Java里处理文件的技巧](http://www.importnew.com/19413.html)：大部分内容来源于此，略有修正和补充

[Check if file is in (sub)directory](https://stackoverflow.com/questions/18227634/check-if-file-is-in-subdirectory)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://maxwell.gitbook.io/way-to-architect/java-yu-yan/io/iofen-lei/zheng-que-di-chu-li-wen-jian.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
