paths-files.md 15.1 KB
Newer Older
沉默王二's avatar
File  
沉默王二 已提交
1
---
沉默王二's avatar
沉默王二 已提交
2
title: 聊聊 Java NIO 中的Paths 和 Files
沉默王二's avatar
nio  
沉默王二 已提交
3
shortTitle: Paths和Files
沉默王二's avatar
File  
沉默王二 已提交
4 5 6 7
category:
  - Java核心
tag:
  - Java IO
沉默王二's avatar
nio  
沉默王二 已提交
8 9
description: Paths 和 Files 是 Java NIO 中的两个核心类。Paths 提供了一系列静态方法,用于操作路径(Path 对象)。Files 类提供了丰富的文件操作方法,如文件的创建、删除、移动、复制、读取和写入等。Files 还支持文件遍历(如 walkFileTree 方法),可以处理文件目录树。
author: 沉默王二
沉默王二's avatar
File  
沉默王二 已提交
10 11 12
head:
  - - meta
    - name: keywords
沉默王二's avatar
nio  
沉默王二 已提交
13
      content: Java,nio,paths,files,path
沉默王二's avatar
File  
沉默王二 已提交
14 15
---

沉默王二's avatar
nio  
沉默王二 已提交
16
# 12.4 Paths 和 Files
沉默王二's avatar
File  
沉默王二 已提交
17

沉默王二's avatar
nio  
沉默王二 已提交
18
Paths 和 Files 在 Java 7 的时候引入,作为对 [`java.io.File` 类](https://tobebetterjavaer.com/io/file-path.html)的补充和改进。
沉默王二's avatar
File  
沉默王二 已提交
19

沉默王二's avatar
nio  
沉默王二 已提交
20
### Paths 类
沉默王二's avatar
File  
沉默王二 已提交
21

沉默王二's avatar
nio  
沉默王二 已提交
22
Paths 类主要用于操作文件和目录路径。它提供了一些静态方法,用于创建`java.nio.file.Path`实例,代表文件系统中的路径。
沉默王二's avatar
nio  
沉默王二 已提交
23 24

下面是 Paths 的一个示例。
沉默王二's avatar
File  
沉默王二 已提交
25 26

```java
沉默王二's avatar
nio  
沉默王二 已提交
27 28 29 30 31
// 创建一个Path实例,表示当前目录下的一个文件
Path path = Paths.get("example.txt");

// 创建一个绝对路径
Path absolutePath = Paths.get("/home/user/example.txt");
沉默王二's avatar
File  
沉默王二 已提交
32 33
```

沉默王二's avatar
nio  
沉默王二 已提交
34
java.nio.file.Path 接口在 Java NIO.2 中代表一个文件系统中的路径。它提供了一系列方法来操作和查询路径。
沉默王二's avatar
File  
沉默王二 已提交
35

沉默王二's avatar
nio  
沉默王二 已提交
36
![](https://cdn.tobebetterjavaer.com/stutymore/paths-files-20230404181334.png)
沉默王二's avatar
File  
沉默王二 已提交
37

沉默王二's avatar
nio  
沉默王二 已提交
38
下面是 Paths 和 Path 一起使用的实例:
沉默王二's avatar
File  
沉默王二 已提交
39

沉默王二's avatar
nio  
沉默王二 已提交
40 41
```java
Path path = Paths.get("docs/配套教程.md");
沉默王二's avatar
File  
沉默王二 已提交
42

沉默王二's avatar
nio  
沉默王二 已提交
43 44
// 获取文件名
System.out.println("File name: " + path.getFileName());
沉默王二's avatar
File  
沉默王二 已提交
45

沉默王二's avatar
nio  
沉默王二 已提交
46 47
// 获取父目录
System.out.println("Parent: " + path.getParent());
沉默王二's avatar
File  
沉默王二 已提交
48

沉默王二's avatar
nio  
沉默王二 已提交
49 50
// 获取根目录
System.out.println("Root: " + path.getRoot());
沉默王二's avatar
File  
沉默王二 已提交
51

沉默王二's avatar
nio  
沉默王二 已提交
52 53 54
// 将路径与另一个路径结合
Path newPath = path.resolve("config/app.properties");
System.out.println("Resolved path: " + newPath);
沉默王二's avatar
File  
沉默王二 已提交
55

沉默王二's avatar
nio  
沉默王二 已提交
56 57 58 59 60 61 62 63 64 65 66 67 68
// 简化路径
Path normalizedPath = newPath.normalize();
System.out.println("Normalized path: " + normalizedPath);

// 将相对路径转换为绝对路径
Path absolutePath = path.toAbsolutePath();
System.out.println("Absolute path: " + absolutePath);

// 计算两个路径之间的相对路径
Path basePath = Paths.get("/docs/");
Path targetPath = Paths.get("/docs/imgs/itwanger");
Path relativePath = basePath.relativize(targetPath);
System.out.println("Relative path: " + relativePath);
沉默王二's avatar
File  
沉默王二 已提交
69
```
沉默王二's avatar
nio  
沉默王二 已提交
70

沉默王二's avatar
nio  
沉默王二 已提交
71
### Files 类
沉默王二's avatar
File  
沉默王二 已提交
72

沉默王二's avatar
nio  
沉默王二 已提交
73
`java.nio.file.Files`类提供了大量静态方法,用于处理文件系统中的文件和目录。这些方法包括文件的创建、删除、复制、移动等操作,以及读取和设置文件属性。
沉默王二's avatar
File  
沉默王二 已提交
74

沉默王二's avatar
nio  
沉默王二 已提交
75
下面展示一个 Files 和 Paths 一起使用的示例:
沉默王二's avatar
File  
沉默王二 已提交
76 77

```java
沉默王二's avatar
nio  
沉默王二 已提交
78 79 80 81 82
// 创建一个Path实例
Path path = Paths.get("logs/javabetter/itwanger4.txt");

// 创建一个新文件
Files.createFile(path);
沉默王二's avatar
File  
沉默王二 已提交
83

沉默王二's avatar
nio  
沉默王二 已提交
84 85 86
// 检查文件是否存在
boolean exists = Files.exists(path);
System.out.println("File exists: " + exists);
沉默王二's avatar
File  
沉默王二 已提交
87

沉默王二's avatar
nio  
沉默王二 已提交
88 89 90
// 删除文件
Files.delete(path);
```
沉默王二's avatar
File  
沉默王二 已提交
91

沉默王二's avatar
nio  
沉默王二 已提交
92
以下是一些常用方法及其示例:
沉默王二's avatar
File  
沉默王二 已提交
93

沉默王二's avatar
nio  
沉默王二 已提交
94
1、`exists(Path path, LinkOption... options)`:检查文件或目录是否存在。
沉默王二's avatar
File  
沉默王二 已提交
95 96

```java
沉默王二's avatar
nio  
沉默王二 已提交
97 98 99
Path path = Paths.get("file.txt");
boolean exists = Files.exists(path);
System.out.println("File exists: " + exists);
沉默王二's avatar
File  
沉默王二 已提交
100 101
```

沉默王二's avatar
nio  
沉默王二 已提交
102
LinkOption 是一个枚举类,它定义了如何处理文件系统链接的选项。它位于 java.nio.file 包中。LinkOption 主要在与文件或目录的路径操作相关的方法中使用,以控制这些方法如何处理符号链接。符号链接是一种特殊类型的文件,它在 Unix 和类 Unix 系统(如 Linux 和 macOS)上很常见。在 Windows 上,类似的概念被称为快捷方式。
沉默王二's avatar
File  
沉默王二 已提交
103

沉默王二's avatar
nio  
沉默王二 已提交
104
2、`createFile(Path path, FileAttribute<?>... attrs)`:创建一个新的空文件。
沉默王二's avatar
File  
沉默王二 已提交
105 106

```java
沉默王二's avatar
nio  
沉默王二 已提交
107 108
Path newPath = Paths.get("newFile.txt");
Files.createFile(newPath);
沉默王二's avatar
File  
沉默王二 已提交
109 110
```

沉默王二's avatar
nio  
沉默王二 已提交
111 112 113 114
FileAttribute 是一个泛型接口,用于处理各种不同类型的属性。在使用 FileAttribute 时,你需要为其提供一个特定的实现。`java.nio.file.attribute` 包中的 PosixFileAttributes 类提供了 POSIX(Portable Operating System Interface,定义了许多与文件系统相关的操作,包括文件和目录的创建、删除、读取和修改。)文件属性的实现。

```java
Path path = Paths.get("fileWithPermissions.txt");
沉默王二's avatar
File  
沉默王二 已提交
115

沉默王二's avatar
nio  
沉默王二 已提交
116 117
Set<PosixFilePermission> permissions = PosixFilePermissions.fromString("rw-r-----");
FileAttribute<Set<PosixFilePermission>> fileAttribute = PosixFilePermissions.asFileAttribute(permissions);
沉默王二's avatar
File  
沉默王二 已提交
118

沉默王二's avatar
nio  
沉默王二 已提交
119 120
Files.createFile(path, fileAttribute);
```
沉默王二's avatar
File  
沉默王二 已提交
121

沉默王二's avatar
nio  
沉默王二 已提交
122 123 124
PosixFileAttributes 接口提供了获取 POSIX 文件属性的方法,如文件所有者、文件所属的组以及文件的访问权限。以上示例会创建一个读写属性的文件。

3、`createDirectory(Path dir, FileAttribute<?>... attrs)`:创建一个新的目录。
沉默王二's avatar
File  
沉默王二 已提交
125 126

```java
沉默王二's avatar
nio  
沉默王二 已提交
127 128
Path newDir = Paths.get("newDirectory");
Files.createDirectory(newDir);
沉默王二's avatar
File  
沉默王二 已提交
129 130
```

沉默王二's avatar
nio  
沉默王二 已提交
131
4、`delete(Path path)`:删除文件或目录。
沉默王二's avatar
File  
沉默王二 已提交
132

沉默王二's avatar
nio  
沉默王二 已提交
133 134 135 136
```java
Path pathToDelete = Paths.get("fileToDelete.txt");
Files.delete(pathToDelete);
```
沉默王二's avatar
File  
沉默王二 已提交
137

沉默王二's avatar
nio  
沉默王二 已提交
138
5、`copy(Path source, Path target, CopyOption... options)`:复制文件或目录。
沉默王二's avatar
File  
沉默王二 已提交
139 140

```java
沉默王二's avatar
nio  
沉默王二 已提交
141 142 143
Path sourcePath = Paths.get("sourceFile.txt");
Path targetPath = Paths.get("targetFile.txt");
Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING);
沉默王二's avatar
File  
沉默王二 已提交
144 145
```

沉默王二's avatar
nio  
沉默王二 已提交
146
在 Java NIO 中,有两个实现了 CopyOption 接口的枚举类:StandardCopyOption 和 LinkOption。
沉默王二's avatar
File  
沉默王二 已提交
147

沉默王二's avatar
nio  
沉默王二 已提交
148 149 150 151 152 153
StandardCopyOption 枚举类提供了以下两个选项:

- REPLACE_EXISTING:如果目标文件已经存在,该选项会使 `Files.copy()` 方法替换目标文件。如果不指定此选项,`Files.copy()` 方法在目标文件已存在时将抛出 FileAlreadyExistsException。
- COPY_ATTRIBUTES:此选项表示在复制文件时,尽可能地复制文件的属性(如文件时间戳、权限等)。如果不指定此选项,那么目标文件将具有默认的属性。

6、`move(Path source, Path target, CopyOption... options)`:移动或重命名文件或目录。
沉默王二's avatar
File  
沉默王二 已提交
154 155

```java
沉默王二's avatar
nio  
沉默王二 已提交
156 157 158
Path sourcePath = Paths.get("sourceFile.txt");
Path targetPath = Paths.get("targetFile.txt");
Files.move(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING);
沉默王二's avatar
File  
沉默王二 已提交
159 160
```

沉默王二's avatar
nio  
沉默王二 已提交
161
7、`readAllLines(Path path, Charset cs)`:读取文件的所有行到一个字符串列表。
沉默王二's avatar
File  
沉默王二 已提交
162

沉默王二's avatar
nio  
沉默王二 已提交
163 164 165 166 167
```java
Path path = Paths.get("file.txt");
List<String> lines = Files.readAllLines(path, StandardCharsets.UTF_8);
lines.forEach(System.out::println);
```
沉默王二's avatar
File  
沉默王二 已提交
168

沉默王二's avatar
nio  
沉默王二 已提交
169
8、`write(Path path, Iterable<? extends CharSequence> lines, Charset cs, OpenOption... options)`:将字符串列表写入文件。
沉默王二's avatar
File  
沉默王二 已提交
170 171

```java
沉默王二's avatar
nio  
沉默王二 已提交
172 173 174
Path path = Paths.get("file.txt");
List<String> lines = Arrays.asList("沉默王二 1", "沉默王二 2", "沉默王二 3");
Files.write(path, lines, StandardCharsets.UTF_8);
沉默王二's avatar
File  
沉默王二 已提交
175 176
```

沉默王二's avatar
nio  
沉默王二 已提交
177 178 179 180 181 182 183 184 185
OpenOption 是 Java NIO 中一个用于配置文件操作的接口。它提供了在使用 `Files.newByteChannel()``Files.newInputStream()``Files.newOutputStream()``AsynchronousFileChannel.open()``FileChannel.open()` 方法时定制行为的选项。

在 Java NIO 中,有两个实现了 OpenOption 接口的枚举类:StandardOpenOption 和 LinkOption。

StandardOpenOption 枚举类提供了以下几个选项:

- READ:以读取模式打开文件。
- WRITE:以写入模式打开文件。
- APPEND:以追加模式打开文件。
沉默王二's avatar
nio  
沉默王二 已提交
186
- TRUNCATE_EXISTING:在打开文件时,截断文件的内容,使其长度为 0。仅适用于 WRITE 或 APPEND 模式。
沉默王二's avatar
nio  
沉默王二 已提交
187 188 189 190 191 192 193 194
- CREATE:当文件不存在时创建文件。如果文件已存在,则打开文件。
- CREATE_NEW:当文件不存在时创建文件。如果文件已存在,抛出 FileAlreadyExistsException。
- DELETE_ON_CLOSE:在关闭通道时删除文件。
- SPARSE:提示文件系统创建一个稀疏文件。
- SYNC:要求每次更新文件的内容或元数据时都进行同步。
- DSYNC:要求每次更新文件内容时都进行同步。

8、`newBufferedReader(Path path, Charset cs) 和 newBufferedWriter(Path path, Charset cs, OpenOption... options)`:创建 BufferedReader 和 BufferedWriter 对象以读取和写入文件。
沉默王二's avatar
File  
沉默王二 已提交
195 196

```java
沉默王二's avatar
nio  
沉默王二 已提交
197 198 199 200 201 202 203 204 205 206 207 208 209 210
Path path = Paths.get("file.txt");

// Read file
try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
}

// Write file
Path outputPath = Paths.get("outputFile.txt");
try (BufferedWriter writer = Files.newBufferedWriter(outputPath, StandardCharsets.UTF_8)) {
    writer.write("沉默王二");
沉默王二's avatar
File  
沉默王二 已提交
211 212 213
}
```

沉默王二's avatar
nio  
沉默王二 已提交
214 215 216 217 218 219 220 221 222 223
#### Files.walkFileTree() 静态方法

这个方法可以递归地访问目录结构中的所有文件和目录,并允许您对这些文件和目录执行自定义操作。使用 walkFileTree 方法时,需要提供一个起始路径(起始目录)和一个实现了 FileVisitor 接口的对象。FileVisitor 接口包含四个方法,它们在遍历过程中的不同阶段被调用:

- preVisitDirectory:在访问目录之前调用。
- postVisitDirectory:在访问目录之后调用。
- visitFile:在访问文件时调用。
- visitFileFailed:在访问文件失败时调用。

来看下面这个示例:
沉默王二's avatar
File  
沉默王二 已提交
224 225

```java
沉默王二's avatar
nio  
沉默王二 已提交
226 227 228 229 230 231 232 233 234 235
public class WalkFileTreeExample {
    public static void main(String[] args) {
        Path startingDir = Paths.get("docs");
        MyFileVisitor fileVisitor = new MyFileVisitor();

        try {
            Files.walkFileTree(startingDir, fileVisitor);
        } catch (IOException e) {
            e.printStackTrace();
        }
沉默王二's avatar
File  
沉默王二 已提交
236 237
    }

沉默王二's avatar
nio  
沉默王二 已提交
238 239 240 241 242 243
    private static class MyFileVisitor extends SimpleFileVisitor<Path> {
        @Override
        public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
            System.out.println("准备访问目录: " + dir);
            return FileVisitResult.CONTINUE;
        }
沉默王二's avatar
File  
沉默王二 已提交
244

沉默王二's avatar
nio  
沉默王二 已提交
245 246 247 248 249
        @Override
        public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
            System.out.println("正在访问目录: " + dir);
            return FileVisitResult.CONTINUE;
        }
沉默王二's avatar
File  
沉默王二 已提交
250

沉默王二's avatar
nio  
沉默王二 已提交
251 252 253 254 255 256 257 258 259 260 261
        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
            System.out.println("访问文件: " + file);
            return FileVisitResult.CONTINUE;
        }

        @Override
        public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
            System.err.println("访问文件失败: " + file);
            return FileVisitResult.CONTINUE;
        }
沉默王二's avatar
File  
沉默王二 已提交
262
    }
沉默王二's avatar
nio  
沉默王二 已提交
263
}
沉默王二's avatar
File  
沉默王二 已提交
264 265
```

沉默王二's avatar
nio  
沉默王二 已提交
266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
运行结果如下:

```
准备访问目录: docs
访问文件: docs/安装环境.md
准备访问目录: docs/imgs
访问文件: docs/imgs/init_03.jpg
准备访问目录: docs/imgs/itwanger
访问文件: docs/imgs/itwanger/tongzhishu.jpeg
访问文件: docs/imgs/itwanger/tongzhishu1.jpeg
访问文件: docs/imgs/itwanger/tongzhishu1.pdf
正在访问目录: docs/imgs/itwanger
访问文件: docs/imgs/init_02.jpg
访问文件: docs/imgs/init_00.jpg
访问文件: docs/imgs/init_01.jpg
访问文件: docs/imgs/init_04.jpg
正在访问目录: docs/imgs
访问文件: docs/服务器启动教程.md
访问文件: docs/配套教程.md
访问文件: docs/约定.md
访问文件: docs/本地开发环境配置教程.md
访问文件: docs/前端工程结构说明.md
正在访问目录: docs
```

在这个示例中,我们创建了一个名为 MyFileVisitor 的自定义 FileVisitor 类,它扩展了 SimpleFileVisitor 类。SimpleFileVisitor 是 FileVisitor 接口的一个实现,它提供了一些默认的行为。我们可以覆盖 SimpleFileVisitor 中的方法以实现自己的逻辑。在这个例子中,我们只是打印出了访问的文件和目录。然后,我们使用 Files.walkFileTree 方法遍历文件树。这个方法会遍历整个目录结构,并调用 MyFileVisitor 中的相应方法。

其中,FileVisitResult 枚举包含以下四个选项:
沉默王二's avatar
File  
沉默王二 已提交
294

沉默王二's avatar
nio  
沉默王二 已提交
295 296 297 298
- CONTINUE : 继续
- TERMINATE : 终止
- SKIP_SIBLINGS : 跳过兄弟节点,然后继续
- SKIP_SUBTREE : 跳过子树(不访问此目录的条目),然后继续,仅在 preVisitDirectory 方法返回时才有意义,除此以外和 CONTINUE 相同。
沉默王二's avatar
File  
沉默王二 已提交
299

沉默王二's avatar
nio  
沉默王二 已提交
300
#### 搜索文件
沉默王二's avatar
File  
沉默王二 已提交
301

沉默王二's avatar
nio  
沉默王二 已提交
302
`walkFileTree()` 方法还可以用于搜索文件,下面这个例子扩展了 SimpleFileVisitor 来查找一个名为 itwanger.txt 的文件:
沉默王二's avatar
File  
沉默王二 已提交
303 304

```java
沉默王二's avatar
nio  
沉默王二 已提交
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346
public class FindFileWithWalkFileTree {
    public static void main(String[] args) {
        Path startingDir = Paths.get("logs");
        String targetFileName = "itwanger.txt";
        FindFileVisitor findFileVisitor = new FindFileVisitor(targetFileName);

        try {
            Files.walkFileTree(startingDir, findFileVisitor);
            if (findFileVisitor.isFileFound()) {
                System.out.println("找到文件了: " + findFileVisitor.getFoundFilePath());
            } else {
                System.out.println("ooh,文件没找到");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static class FindFileVisitor extends SimpleFileVisitor<Path> {
        private String targetFileName;
        private Path foundFilePath;

        public FindFileVisitor(String targetFileName) {
            this.targetFileName = targetFileName;
        }

        public boolean isFileFound() {
            return foundFilePath != null;
        }

        public Path getFoundFilePath() {
            return foundFilePath;
        }

        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
            String fileName = file.getFileName().toString();
            if (fileName.equals(targetFileName)) {
                foundFilePath = file;
                return FileVisitResult.TERMINATE;
            }
            return FileVisitResult.CONTINUE;
沉默王二's avatar
File  
沉默王二 已提交
347 348
        }
    }
沉默王二's avatar
nio  
沉默王二 已提交
349
}
沉默王二's avatar
File  
沉默王二 已提交
350 351
```

沉默王二's avatar
nio  
沉默王二 已提交
352 353 354 355 356
在主方法中,我们使用 Files.walkFileTree 方法遍历文件树,并传递一个起始目录和 FindFileVisitor 实例。遍历完成后,我们检查是否找到了目标文件,如果找到了,就打印出它的路径。

搜索结果如下所示:

![](https://cdn.tobebetterjavaer.com/stutymore/paths-files-20230404190926.png)
沉默王二's avatar
File  
沉默王二 已提交
357

沉默王二's avatar
nio  
沉默王二 已提交
358
### 小结
沉默王二's avatar
File  
沉默王二 已提交
359

沉默王二's avatar
nio  
沉默王二 已提交
360
Paths 和 Files 是 Java NIO 中的两个核心类。Paths 提供了一系列静态方法,用于操作路径(Path 对象)。它可以将字符串或 URI 转换为 Path 对象,方便后续操作。Files 类提供了丰富的文件操作方法,如文件的创建、删除、移动、复制、读取和写入等。这些方法支持各种选项和属性,如覆盖、保留属性和符号链接处理。Files 还支持文件遍历(如 walkFileTree 方法),可以处理文件目录树。总之,Paths 和 Files 为文件和目录操作提供了简洁、高效的方法。
沉默王二's avatar
File  
沉默王二 已提交
361

沉默王二's avatar
nio  
沉默王二 已提交
362
---
沉默王二's avatar
File  
沉默王二 已提交
363

沉默王二's avatar
nio  
沉默王二 已提交
364
最近整理了一份牛逼的学习资料,包括但不限于 Java 基础部分(JVM、Java 集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类 Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是 2022 年全网最全的学习和找工作的 PDF 资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
沉默王二's avatar
File  
沉默王二 已提交
365

沉默王二's avatar
沉默王二 已提交
366
微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
沉默王二's avatar
File  
沉默王二 已提交
367

沉默王二's avatar
nio  
沉默王二 已提交
368
![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/gongzhonghao.png)