killJava系列 -- 文件类相关

前言

最近作项目的时候,用java获取文件。
虽然用框架很容易,但是其内部的原理让我很疑惑。在自己写相似的代码的时候,往往会出现各种各样的错误。所以这里,对相关的类以及方法进行一个整合。
比如 file 类,path 类。 绝对路径与相对路径。 getResource 方法 , getRealPath方法等。

绝对路径与相对路径

在使用 File 类的时候,发现绝对路径和相对路径的使用有很大的区别。
大家都知道:File类是用来构造文件或文件夹的类,在其构造函数中要求传入一个String类型的参数,用于指示文件所在的路径
绝对路径名是完整的路径名,不需要任何其他信息就可以定位自身表示的文件。
相对路径名必须使用来自其他路径名的信息进行解释

因为我使用的是 idea ,所以下面,我就用idea 给大家演示一下,他们的区别。
废话不多说,直接上代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class testFile {
public static void main(String[] args) throws IOException {
// 绝对路径
File fi1 = new File("D://sy.ini");
// 相对路径
File fi2 = new File("sy.ini");
String test = "000";
try {
// 将 test 分别写入 fi1 fi2
FileOutputStream fo1 = new FileOutputStream(fi1);
FileOutputStream fo2 = new FileOutputStream(fi2);
fo1.write(test.getBytes());
fo2.write(test.getBytes());
} catch (FileNotFoundException e) {
e.printStackTrace();
}
// 验证 getPath 与 getAbsolubtePath 的区别。
System.out.println(fi1.getPath());
System.out.println(fi1.getAbsolutePath());

System.out.println(fi2.getPath());
System.out.println(fi2.getAbsolutePath());
}
}

输出的结果如下

1
2
3
4
D:\sy.ini
D:\sy.ini
sy.ini
D:\Programme\0-Java\J_Example\Arithmetic\sy.ini

用两张图片辅助结果

从这个结果中,我们可以看出两点

  1. getPath 只是简单的返回 你赋予的String 值,不管是 相对路径还是绝对路径。
  2. 相对路径是相对于自身的项目地址。并且不加 “/“

getResource

用代码说话。。。

1
2
3
4
URL resource = testFile.class.getClassLoader().getResource(".");
URL resource1 = testFile.class.getResource(".");
System.out.println(resource);
System.out.println(resource1);

输出的结果为

1
2
file:/D:/Programme/0-Java/J_Example/Arithmetic/out/production/3_basic/
file:/D:/Programme/0-Java/J_Example/Arithmetic/out/production/3_basic/test_01/

所以对于 getResource 来说 。

  1. 如果使用 Class 类 ,则代表该类所在的包为 相对路径的起点。
  2. 如果使用 ClassLoader类 , 则代表该类所在的 模块为 相对路径的起点。

JSP 中的路径

使用 EL 表达式
${pageContext.request.contextPath} 这里的路径指的是 web 的根目录.

File类 与 Path类

Path 类 是 JDK 7 中加入的新内容。比File 类更快,而且有报错机制,所以更容易使用。

他们两个的 区别 我会写在内部的 注释中。
就不单独拿出来写了。

  1. 创建文件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 如果存在重复 会报错。
    Path path = Paths.get("D://test.txt");
    Files.createFile(path);
    // 这里有一个 方法,可直接设置文件的属性。
    Set perms= PosixFilePermissions.fromString("rw-rw-rw-")
    Files.crateFile(path,perms);


    // 如果存在重复,会重新创建。
    // 可以使用 file.exists() 来确认是否存在重复。
    File file = new File("D://test02.txt");
    file.createNewFile();

共同点: 想要创建多级目录下的文件,都必须先创建目录,才能创建文件。

  1. 创建目录

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 可以直接创建多级目录
    Path path = Paths.get("D://test/test01/");
    Files.createDirectories(path);

    // mkdir 只能创建一级目录
    // mkdirs 可以创建多级目录
    File file = new File("D://test02/test3/test3");
    file.mkdir();
    file.mkdirs();
  2. 删除

    1
    2
    3
    4
    5
    6
    7
    8
    // 如果文件夹下存在多级目录,则报错
    // DirectoryNotEmptyException
    Path path = Paths.get("D://test/");
    Files.delete(path);

    // 如果文件夹下存在多级目录,则没有反应。。
    File file = new File("D://test02");
    file.delete();

如果想要删除相应的文件,直接将 路径更改为文件的路径即可。

  1. 遍历文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public static void fileForEach(String path) {
File file = new File(path);
File[] files = file.listFiles();
for (File f : files) {
// 判断是 文件还是 目录
if (f.isFile()) {
System.out.println(f.getName() + "是文件!");
} else if (f.isDirectory()) {
System.out.println(f.getName());
fileForEach(f.getPath());
}
}
}

public static void main(String[] args) throws IOException {
Path path = Paths.get("D://test/");
Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
// 访问文件夹前使用
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
System.out.println(dir.getFileName());
return super.preVisitDirectory(dir, attrs);
}

// 访问文件夹后使用
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
return super.postVisitDirectory(dir, exc);
}

// 访问文件时使用
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (file.toString().endsWith(".txt")) {
System.out.println(file.getFileName());
}
return super.visitFile(file, attrs);
}

// 访问文件失败使用
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
return super.visitFileFailed(file, exc);
}
});

fileForEach("D://test02/");
}}

这里出现了 SimpleFileVisitor 这个类,具体使用方法就是覆写其内部方法。
经过我的测试 , 因为这个类的方法, 可以比 fileForEach 快大约 30%

  1. 复制与移动
    移动的同时可以更改名字。这一点,都一样。并且与 linux 的操作也类似

Path 类

1
2
3
4
Path path = Paths.get("D://test/test01/cs.txt");
Path to = Paths.get("D://test/test01/test2.txt");
Files.move(path, to, StandardCopyOption.REPLACE_EXISTING);
Files.copy(path, to, StandardCopyOption.REPLACE_EXISTING);

这里的 StandardCopyOption 有三个属性。
注意: ATOMIC_MOVE 方法只支持 move 方法。如果将之使用到 copy 方法,则会报错。

1
2
3
4
5
6
7
8
9
10
11
12
/* 如果存在则覆盖
* Replace an existing file if it exists.
*/
REPLACE_EXISTING,
/* 将属性一同拷贝。
* Copy attributes to the new file.
*/
COPY_ATTRIBUTES,
/* 只支持 move 方法,不支持 copy 方法
* Move the file as an atomic file system operation.
*/
ATOMIC_MOVE;

下面是file 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 拷贝 方法
public void copyFile(String oldFile,String newFile){
try{
int bytesum = 0;
int byteread = 0;
File oldfile = new File(oldFile);
//判断文件是否存在,如果文件存在则实现该文件向新文件的复制
if(oldfile.exists()){
//读取原文件
InputStream ins = new FileInputStream(oldFile);
//创建文件输出流,写入文件
FileOutputStream outs = new FileOutputStream(newFile);
//创建缓冲区,大小为500字节
byte[] buffer = new byte[500];
//每次从文件流中读取500字节数据,计算当前为止读取的数据总数
while((byteread = ins.read(buffer)) != -1){
bytesum += byteread;
System.out.println(bytesum);
//把当前缓冲区中的数据写入新文件
outs.write(buffer,0,byteread);
}
ins.close();
}
else //如果原文件不存在,则扔出异常
throw new Exception();
}catch(Exception ex){
System.out.print("原文件不存在!");
ex.printStackTrace();
}

// 移动 方法
File file = new File("D://test02/test02/test02.txt");
file.renameTo(new File("D://test02/test02/test.txt"));

从这里我们可以看出, path 类相对应的 复制方法 非常简单,不需要使用 直接使用输入输出流就可以复制文件。

  1. 输入输出流

Path类

File类

1
2
3
File file = new File("D://test02/test02/test02.txt");
FileInputStream fileInputStream = new FileInputStream(file);
FileReader fileReader = new FileReader(file);

相对比,我们也可以看出,Path类相对 file类也简化了很多操作。更有利于开发。

  1. 相互转化
    1
    2
    3

    path.toFile()
    File.toPath()

总结

  1. 关于路径的使用总结。
  2. 关于Path 与 File 类的使用总结。
    Path 类 相比较于 File 类 ,在操作上更直观,没有涉及到更深的层面.可以看出 研发人员 从中作出了很多改进。 可以让人更专注于逻辑的编写,而非是 底层的基础。
    虽然并没有针对其 性能作出确切的比较,不过就现有的网络统计来说, Path 类在使用中大都会比 File 类快 , 并且在最新的 lucene 中,也是用 Path 代替了 file 的操作, 相关的文章请参考 []。
    综上,推荐使用 Path 类替代 File 类。

前言

最近作项目的时候,用java获取文件。
虽然用框架很容易,但是其内部的原理让我很疑惑。在自己写相似的代码的时候,往往会出现各种各样的错误。所以这里,对相关的类以及方法进行一个整合。
比如 file 类,path 类。 绝对路径与相对路径。 getResource 方法 , getRealPath方法等。

绝对路径与相对路径

在使用 File 类的时候,发现绝对路径和相对路径的使用有很大的区别。
大家都知道:File类是用来构造文件或文件夹的类,在其构造函数中要求传入一个String类型的参数,用于指示文件所在的路径
绝对路径名是完整的路径名,不需要任何其他信息就可以定位自身表示的文件。
相对路径名必须使用来自其他路径名的信息进行解释

因为我使用的是 idea ,所以下面,我就用idea 给大家演示一下,他们的区别。
废话不多说,直接上代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class testFile {
public static void main(String[] args) throws IOException {
// 绝对路径
File fi1 = new File("D://sy.ini");
// 相对路径
File fi2 = new File("sy.ini");
String test = "000";
try {
// 将 test 分别写入 fi1 fi2
FileOutputStream fo1 = new FileOutputStream(fi1);
FileOutputStream fo2 = new FileOutputStream(fi2);
fo1.write(test.getBytes());
fo2.write(test.getBytes());
} catch (FileNotFoundException e) {
e.printStackTrace();
}
// 验证 getPath 与 getAbsolubtePath 的区别。
System.out.println(fi1.getPath());
System.out.println(fi1.getAbsolutePath());

System.out.println(fi2.getPath());
System.out.println(fi2.getAbsolutePath());
}
}

输出的结果如下

1
2
3
4
D:\sy.ini
D:\sy.ini
sy.ini
D:\Programme\0-Java\J_Example\Arithmetic\sy.ini

用两张图片辅助结果

从这个结果中,我们可以看出两点

  1. getPath 只是简单的返回 你赋予的String 值,不管是 相对路径还是绝对路径。
  2. 相对路径是相对于自身的项目地址。并且不加 “/“

getResource

用代码说话。。。

1
2
3
4
URL resource = testFile.class.getClassLoader().getResource(".");
URL resource1 = testFile.class.getResource(".");
System.out.println(resource);
System.out.println(resource1);

输出的结果为

1
2
file:/D:/Programme/0-Java/J_Example/Arithmetic/out/production/3_basic/
file:/D:/Programme/0-Java/J_Example/Arithmetic/out/production/3_basic/test_01/

所以对于 getResource 来说 。

  1. 如果使用 Class 类 ,则代表该类所在的包为 相对路径的起点。
  2. 如果使用 ClassLoader类 , 则代表该类所在的 模块为 相对路径的起点。

JSP 中的路径

使用 EL 表达式
${pageContext.request.contextPath} 这里的路径指的是 web 的根目录.

File类 与 Path类

Path 类 是 JDK 7 中加入的新内容。比File 类更快,而且有报错机制,所以更容易使用。

他们两个的

我会写在内部的 注释中。
1
2
3
就不单独拿出来写了。

1. 创建文件

// 如果存在重复 会报错。
Path path = Paths.get("D://test.txt");
Files.createFile(path);
// 这里有一个 方法,可直接设置文件的属性。
Set perms= PosixFilePermissions.fromString("rw-rw-rw-")
Files.crateFile(path,perms);


// 如果存在重复,会重新创建。
// 可以使用 file.exists() 来确认是否存在重复。
File file = new File("D://test02.txt");
file.createNewFile();
1
2
3
4
共同点: 想要创建多级目录下的文件,都必须先创建目录,才能创建文件。


2. 创建目录
// 可以直接创建多级目录
Path path = Paths.get("D://test/test01/");
Files.createDirectories(path);

// mkdir 只能创建一级目录
// mkdirs 可以创建多级目录
File file = new File("D://test02/test3/test3");
file.mkdir();
file.mkdirs();
1
2

3. 删除
// 如果文件夹下存在多级目录,则报错
// DirectoryNotEmptyException
Path path = Paths.get("D://test/");
Files.delete(path);

// 如果文件夹下存在多级目录,则没有反应。。
File file = new File("D://test02");
file.delete();
1
2
3
如果想要删除相应的文件,直接将 路径更改为文件的路径即可。

4. 遍历文件
public static void fileForEach(String path) {
    File file = new File(path);
    File[] files = file.listFiles();
    for (File f : files) {
        // 判断是 文件还是 目录
        if (f.isFile()) {
            System.out.println(f.getName() + "是文件!");
        } else if (f.isDirectory()) {
            System.out.println(f.getName());
            fileForEach(f.getPath());
        }
    }
}

public static void main(String[] args) throws IOException {
    Path path = Paths.get("D://test/");
    Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
        // 访问文件夹前使用
        @Override
        public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
            System.out.println(dir.getFileName());
            return super.preVisitDirectory(dir, attrs);
        }

        // 访问文件夹后使用
        @Override
        public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
            return super.postVisitDirectory(dir, exc);
        }

        // 访问文件时使用
        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
            if (file.toString().endsWith(".txt")) {
                System.out.println(file.getFileName());
            }
            return super.visitFile(file, attrs);
        }

        // 访问文件失败使用
        @Override
        public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
            return super.visitFileFailed(file, exc);
        }
    });

    fileForEach("D://test02/");
}}
1
2
3
4
5
6
7
8

这里出现了 **SimpleFileVisitor** 这个类,具体使用方法就是覆写其内部方法。
经过我的测试 , 因为这个类的方法, 可以比 ** fileForEach** 快大约 ** 30% ** 。

5. 复制与移动
移动的同时可以更改名字。这一点,都一样。并且与 linux 的操作也类似

Path 类
Path path = Paths.get("D://test/test01/cs.txt");
Path to = Paths.get("D://test/test01/test2.txt");
Files.move(path, to, StandardCopyOption.REPLACE_EXISTING);
Files.copy(path, to, StandardCopyOption.REPLACE_EXISTING);
1
2
3

这里的 ** StandardCopyOption ** 有三个属性。
注意: ATOMIC_MOVE 方法只支持 move 方法。如果将之使用到 copy 方法,则会报错。
/* 如果存在则覆盖
 * Replace an existing file if it exists. 
 */
REPLACE_EXISTING,
/* 将属性一同拷贝。
 * Copy attributes to the new file.
 */
COPY_ATTRIBUTES,
/* 只支持 move 方法,不支持 copy 方法
 * Move the file as an atomic file system operation.
 */
ATOMIC_MOVE;
1
2

下面是file 类
// 拷贝 方法
public void copyFile(String oldFile,String newFile){
try{
int bytesum = 0;
int byteread = 0;
File oldfile = new File(oldFile);
//判断文件是否存在,如果文件存在则实现该文件向新文件的复制
if(oldfile.exists()){
    //读取原文件
    InputStream ins = new FileInputStream(oldFile);
    //创建文件输出流,写入文件
    FileOutputStream outs = new FileOutputStream(newFile);
    //创建缓冲区,大小为500字节
    byte[] buffer = new byte[500];
    //每次从文件流中读取500字节数据,计算当前为止读取的数据总数
    while((byteread = ins.read(buffer)) != -1){
    bytesum += byteread;
    System.out.println(bytesum);
    //把当前缓冲区中的数据写入新文件
    outs.write(buffer,0,byteread);
    }
    ins.close();
}
else  //如果原文件不存在,则扔出异常
    throw new Exception();
}catch(Exception ex){
System.out.print("原文件不存在!");
ex.printStackTrace();
}

// 移动 方法
File file = new File("D://test02/test02/test02.txt");
file.renameTo(new File("D://test02/test02/test.txt"));
1
2
3
4
5
6
7
8
9

从这里我们可以看出, path 类相对应的 复制方法 非常简单,不需要使用 直接使用输入输出流就可以复制文件。

6. 输入输出流

Path类
![](http://kk.3dot141.com/201709172336_740.png)

File类
File file = new File("D://test02/test02/test02.txt");
FileInputStream fileInputStream = new FileInputStream(file);
FileReader fileReader = new FileReader(file);
1
2
3
4

相对比,我们也可以看出,Path类相对 file类也简化了很多操作。更有利于开发。

7. 相互转化

path.toFile()
File.toPath()

`

总结

  1. 关于路径的使用总结。
  2. 关于Path 与 File 类的使用总结。
    Path 类 相比较于 File 类 ,在操作上更直观,没有涉及到更深的层面.可以看出 研发人员 从中作出了很多改进。 可以让人更专注于逻辑的编写,而非是 底层的基础。
    虽然并没有针对其 性能作出确切的比较,不过就现有的网络统计来说, Path 类在使用中大都会比 File 类快 , 并且在最新的 lucene 中,也是用 Path 代替了 file 的操作, 相关的文章请参考 []。
    综上,推荐使用 Path 类替代 File 类。
0%