Skip to content

Commit 8e6cc2c

Browse files
committed
优化
1 parent 7cc0a31 commit 8e6cc2c

31 files changed

+942
-439
lines changed

README-1.0.0.RELEASE.md

Lines changed: 377 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,377 @@
1+
# async-file
2+
3+
[![Gitee stars](https://gitee.com/pinweiwan/async-file/badge/star.svg)](https://gitee.com/pinweiwan/async-file/stargazers)
4+
[![Gitee forks](https://gitee.com/pinweiwan/async-file/badge/fork.svg)](https://gitee.com/pinweiwan/async-file/members)
5+
[![Maven central](https://img.shields.io/maven-central/v/io.github.kavahub/kavahub-async-file.svg)](https://search.maven.org/artifact/io.github.kavahub/kavahub-async-file)
6+
[![License](https://img.shields.io/github/license/kavahub/async-file.svg)](https://github.com/kavahub/async-file/blob/main/LICENSE)
7+
[![GitHub stars](https://img.shields.io/github/stars/kavahub/async-file?style=flat-square&logo=GitHub)](https://github.com/kavahub/async-file/stargazers)
8+
[![GitHub forks](https://img.shields.io/github/forks/kavahub/async-file?style=flat-square&logo=GitHub)](https://github.com/kavahub/async-file/network/members)
9+
[![GitHub watchers](https://img.shields.io/github/watchers/kavahub/async-file?style=flat-square&logo=GitHub)](https://github.com/kavahub/async-file/watchers)
10+
[![GitHub release](https://img.shields.io/github/release/kavahub/async-file?style=flat-square&logo=GitHub?color=blu)](https://github.com/kavahub/async-file/releases)
11+
12+
[Gitee](https://gitee.com/pinweiwan/async-file)
13+
14+
[GitHub](https://github.com/kavahub/async-file)
15+
16+
#### 介绍
17+
async-file工具提供Java异步读写文件的能力,使用Java NIO 库开发。Java应用程序引入框架可以简单的,异步和非阻塞的读写文件。框架包含三个工具类:
18+
19+
20+
- [`AIOFileReader`](src/main/java/io/github/kavahub/file/reader/AIOFileReader.java) 异步读取文件,使用Java NIO库 [`AsynchronousFileChannel`](https://docs.oracle.com/javase/10/docs/api/java/nio/channels/AsynchronousFileChannel.html)[`CompletionHandler`](https://docs.oracle.com/javase/10/docs/api/java/nio/channels/CompletionHandler.html) 实现。
21+
22+
- [`AIOFileWriter`](src/main/java/io/github/kavahub/file/writer/AIOFileWriter.java) 异步写入文件,使用Java NIO库 [`AsynchronousFileChannel`](https://docs.oracle.com/javase/10/docs/api/java/nio/channels/AsynchronousFileChannel.html)[`CompletionHandler`](https://docs.oracle.com/javase/10/docs/api/java/nio/channels/CompletionHandler.html) 实现。
23+
24+
- [`NIOFileLineReader`](src/main/java/io/github/kavahub/file/reader/NIOFileLineReader.java) 非阻塞读取文件,使用 [`ForkJoinPool`](https://docs.oracle.com/javase/10/docs/api/java/util/concurrent/ForkJoinPool.html)[`BufferedReader`](https://docs.oracle.com/javase/10/docs/api/java/io/BufferedReader.html) 实现
25+
26+
提示:Java提供的 [`Files`](https://docs.oracle.com/javase/10/docs/api/java/nio/file/Files.html) 文件读取功能是阻塞的。
27+
28+
#### 安装教程
29+
30+
首先,如果项目使用Maven工具,在项目的pom.xml文件中添加依赖
31+
32+
```xml
33+
<dependency>
34+
<groupId>io.github.kavahub</groupId>
35+
<artifactId>kava-async-file</artifactId>
36+
<version>1.0.0.RELEASE</version>
37+
</dependency>
38+
```
39+
40+
如果是Gradle项目,需要添加依赖:
41+
42+
```groovy
43+
implementation 'io.github.kavahub:kava-async-file:1.0.0.RELEASE'
44+
```
45+
46+
#### 使用说明
47+
48+
[`AIOFileReader`](src/main/java/io/github/kavahub/file/reader/AIOFileReader.java)方法列表:
49+
50+
- `Query<byte[]> bytes(Path file)` : 读取文件,返回文件数据字节数组,读取的大小有默认缓冲区决定。
51+
52+
- `Query<byte[]> allBytes(Path file)` : 读取文件,返回文件所有数据字节数组。每次按默认缓冲区读取文件,完成后合并。
53+
54+
- `Query<String> line(Path file)` : 读取文件,返回文件行字符串。每次按默认缓冲区读取文件数据字节数组,按换行符分割字节数组。
55+
56+
- `Query<String> allLines(Path file)` : 读取文件,返回文件所有数据字符串。每次按默认缓冲区读取文件数据字节数组,合并后转换成字符串。
57+
58+
默认缓冲区大小定义:
59+
60+
```java
61+
public static final int BUFFER_SIZE = 4096 * 4;
62+
```
63+
64+
示例:
65+
66+
```java
67+
// 按行读取文件,并输出到控制台
68+
final Path FILE = Paths.get("src", "test", "resources", "fileWithmanyOfLine.txt");
69+
AIOFileReader.line(FILE).subscribe((data, err) -> {
70+
if (err != null) {
71+
// 处理异常,如记录日志
72+
err.printStackTrace();
73+
}
74+
75+
if (data != null) {
76+
// 文件行处理,如输出到控制台
77+
System.out.println(data);
78+
}
79+
})
80+
// 等待所有行处理完成
81+
.join();
82+
```
83+
84+
示例:
85+
86+
```java
87+
// 统计文件中单词个数,并找出次数最多的单词
88+
final Path FILE = Paths.get("src", "test", "resources", "fileToCount.txt");
89+
90+
final int MIN = 5;
91+
final int MAX = 10;
92+
93+
ConcurrentHashMap<String, Integer> words = new ConcurrentHashMap<>();
94+
AIOFileReader.line(FILE)
95+
// 过滤掉前14行
96+
.filter(line -> !line.trim().isEmpty()).skip(14)
97+
// 使用空格分隔
98+
.flatMapMerge(line -> Query.of(line.split(" ")))
99+
// 过滤单词
100+
.filter(word -> word.length() > MIN && word.length() < MAX)
101+
// 统计单词次数
102+
.onNext((w, err) -> words.merge(w, 1, Integer::sum))
103+
// 阻塞,直到文件统计完毕
104+
.blockingSubscribe();
105+
106+
Map.Entry<String, ? extends Number> common = Collections.max(words.entrySet(),
107+
Comparator.comparingInt(e -> e.getValue().intValue()));
108+
assertEquals("Hokosa", common.getKey());
109+
assertEquals(183, common.getValue().intValue());
110+
```
111+
112+
示例:
113+
114+
```java
115+
// 统计“*** END OF ”行之前所有单词的数量
116+
// 当读取到"*** END OF "行时,读线程会取消读操作,避免继续读取不需要处理的数据
117+
118+
final Path FILE = Paths.get("src", "test", "resources", "fileToCount.txt");
119+
120+
int[] count = { 0 };
121+
AIOFileReader.line(FILE)
122+
// 过滤空行
123+
.filter(line -> !line.trim().isEmpty())
124+
// 忽略前14行
125+
.skip(14)
126+
// 忽略掉‘*** END OF ’以后的行
127+
.takeWhile(line -> !line.contains("*** END OF "))
128+
// 行按空格切割成单词
129+
.flatMapMerge(line -> Query.of(line.split("\\W+")))
130+
// 去重
131+
.distinct()
132+
// 统计数量
133+
.onNext((word, err) -> {
134+
if (err == null)
135+
count[0]++;
136+
})
137+
// 显示处理中的异常
138+
.onNext((word, err) -> {
139+
if (err != null)
140+
err.printStackTrace();
141+
})
142+
// 阻塞,知道文件读取完成
143+
.blockingSubscribe();
144+
assertEquals(5206, count[0]);
145+
```
146+
147+
示例:
148+
149+
```java
150+
// 详细演示takeWhile的功能:
151+
// 1. 控制台输出前部文件内容,框架日志提示[Cancel file reading. [16384 bytes] has been readed],读取操作取消,不在读取文件数据。
152+
// 2. [16384 bytes] 信息中,16384是框架默认读取缓冲区大小,由此可以判断:文件只读取了一次
153+
final Path FILE = Paths.get("src", "test", "resources", "fileWithmanyOfLine.txt");
154+
155+
int[] count = { 0 };
156+
AIOFileReader.line(FILE)
157+
// 控制台输出
158+
.onNext((data, err) -> {
159+
if (err != null) {
160+
err.printStackTrace();
161+
}
162+
163+
if (data != null) {
164+
System.out.println("before:" + data);
165+
}
166+
})
167+
// 终止文件读操纵。
168+
.takeWhile(line -> false)
169+
.onNext((data, err) -> {
170+
if (err != null) {
171+
err.printStackTrace();
172+
}
173+
174+
if (data != null) {
175+
System.out.println("after:" +data);
176+
}
177+
}).blockingSubscribe();
178+
179+
assertEquals(0, count[0]);
180+
```
181+
182+
示例:
183+
184+
```java
185+
// 也可以使用cancel方法中断读文件操作
186+
187+
final Path FILE = Paths.get("src", "test", "resources", "fileWithmanyOfLine.txt");
188+
189+
CompletableFuture<Void> future = AIOFileReader.line(FILE).subscribe((data, err) -> {
190+
if (err != null) {
191+
System.out.println("error:" + err.getMessage());
192+
}
193+
try {
194+
TimeUnit.MILLISECONDS.sleep(10);
195+
} catch (InterruptedException e) {
196+
e.printStackTrace();
197+
}
198+
System.out.println(Thread.currentThread().getName());
199+
});
200+
201+
TimeUnit.MILLISECONDS.sleep(1000);
202+
203+
future.cancel(false);
204+
```
205+
206+
示例:
207+
208+
```java
209+
// 显示读文件线程的名称
210+
final Path FILE = Paths.get("src", "test", "resources", "fileWithmanyOfLine.txt");
211+
212+
AIOFileReader.bytes(FILE).subscribe((data, err) -> {
213+
if (err != null) {
214+
err.printStackTrace();
215+
}
216+
System.out.println(Thread.currentThread().getName());
217+
}).join();
218+
```
219+
220+
输出结果如下:
221+
222+
```text
223+
Thread-8
224+
Thread-7
225+
Thread-8
226+
Thread-7
227+
Thread-8
228+
Thread-7
229+
Thread-8
230+
Thread-7
231+
Thread-8
232+
Thread-7
233+
...
234+
```
235+
236+
其结果表明:有两个线程读取文件,线程交替读取以保证读取文件数据的顺序,这是 [`AsynchronousFileChannel`](https://docs.oracle.com/javase/10/docs/api/java/nio/channels/AsynchronousFileChannel.html) 实现的
237+
238+
239+
[`AIOFileWriter`](src/main/java/io/github/kavahub/file/writer/AIOFileWriter.java)方法列表:
240+
241+
- `CompletableFuture<Integer> write(Path file, byte[] bytes)` : 字节数组数据写入文件。
242+
243+
- `CompletableFuture<Integer> write(Path file, String line)` : 字符串数据写入文件。
244+
245+
- `CompletableFuture<Integer> write(Path file, Query<String> lines)` : 字符串流数据写入文件。
246+
247+
- `CompletableFuture<Integer> write(Path file, Iterable<String> lines)` : 字符串集合数据写入文件。
248+
249+
示例:
250+
251+
```java
252+
// 写入字符串
253+
AIOFileWriter.write(Paths.get(FILE_TO_WRITE), "This is file content:你好").join();
254+
```
255+
256+
示例:
257+
258+
```java
259+
// 分割字符串写入
260+
final String content = "This is file content:你好";
261+
262+
AIOFileWriter.write(Paths.get(FILE_TO_WRITE), String.join(System.lineSeparator(), content.split(" "))).join();
263+
```
264+
265+
示例:
266+
267+
```java
268+
// 字符流写入
269+
Query<String> data = Query.of("This is file content:你好")
270+
.flatMapMerge(line -> Query.of(line.split(" ")))
271+
.map((line) -> line + System.lineSeparator());
272+
AIOFileWriter.write(Paths.get(FILE_TO_WRITE), data).join();
273+
```
274+
275+
示例:
276+
277+
```java
278+
// 字符流转换后写入
279+
Query<String> data = Query.of("This is file content:你好")
280+
.flatMapMerge(line -> Query.of(line.split(" ")))
281+
.map((line) -> line + System.lineSeparator());
282+
AIOFileWriter.write(Paths.get(FILE_TO_WRITE), data).join();
283+
```
284+
285+
示例:
286+
287+
```java
288+
// 边读边写
289+
final Path FILE = Paths.get("src", "test", "resources", "fileWithmanyOfLine.txt");
290+
291+
Query<String> reader = AIOFileReader.line(FILE)
292+
// 忽略前2行
293+
.skip(2)
294+
// 过滤掉空行
295+
.filter(line -> !line.isBlank())
296+
// 转换成大写
297+
.map(String::toUpperCase)
298+
// 加入换行符
299+
.map((line) -> line + System.lineSeparator());
300+
AIOFileWriter.write(Paths.get(FILE_TO_WRITE), reader).join();
301+
```
302+
303+
[`NIOFileLineReader`](src/main/java/io/github/kavahub/file/reader/NIOFileLineReader.java) 方法列表:
304+
305+
`Query<String> read(Path file)` : 读取文件行。
306+
307+
```java
308+
// 读取文件行并过滤
309+
final Path FILE = Paths.get("src", "test", "resources", "fileWithmanyOfLine.txt");
310+
NIOFileLineReader.read(FILE).filter(line -> !line.trim().isEmpty()).onNext((data, err) -> {
311+
System.out.println(data);
312+
}).blockingSubscribe();
313+
```
314+
315+
316+
关于使用的建议:
317+
318+
- 文件的异步读写,并不是为了提高文件的读取性能,而是提高文件读取的吞吐量(读取更多的文件,并保持性能,使JVM可以稳定运行)。
319+
- 在大多数情况下,使用Jdk提供的[`Files`](https://docs.oracle.com/javase/10/docs/api/java/nio/file/Files.html)或许更合适。
320+
- 不要为了异步而异步,找到问题所在,也许解决问题的关键不是异步。
321+
322+
建议使用优先级: `Java NIO Files` > `NIOFileLineReader` > `AIOFileReader`
323+
324+
#### 性能
325+
326+
性能测试,参考 [`ReadLineBenchmark`](src/test/java/io/github/kavahub/file/performance/ReadLineBenchmark.java) 。 其他开源项目文件读写的性能测试 [`ReadFileBenchmark`](https://gitee.com/yangyunjiao/learn-java/blob/master/core-java/core-java-io/src/main/java/net/learnjava/ReadFileBenchmark.java)
327+
328+
329+
#### 构建项目
330+
331+
克隆代码到本地,然后运行mvn命令, 执行编译,测试,打包项目:
332+
333+
```text
334+
mvn clean install
335+
```
336+
337+
#### 发布项目
338+
339+
首先,确保项目可以正确构建。然后执行下面的命令(发布的文件要以release结尾,如:kavahub-async-file-1.0.0.RELEASE.jar):
340+
341+
```text
342+
mvn release:prepare -Prelease
343+
mvn release:perform -Prelease
344+
```
345+
346+
上面操作将包上传到了Staging Repository,需要转入Release Repository,执行命令:
347+
348+
```text
349+
mvn nexus-staging:release
350+
```
351+
352+
以上操作全部成功,发布完成。
353+
354+
发布SNAPSHOT包到仓库,命令如下:
355+
356+
```text
357+
mvn clean deploy -Prelease
358+
```
359+
360+
取消Staging Repository中的包,命令如下:
361+
362+
```text
363+
mvn nexus-staging:drop
364+
```
365+
366+
#### 其他开源项目
367+
368+
- [RxIo](https://github.com/javasync/RxIo) : Asynchronous non-blocking File Reader and Writer library for Java
369+
370+
#### 参考文档
371+
372+
- [vsCode利用git连接github](https://www.jianshu.com/p/f836da434e18)
373+
- [如何将自己的代码发布到Maven中央仓库](https://www.cnblogs.com/songyz/p/11387978.html)
374+
- [Deploying to OSSRH with Apache Maven - Introduction](https://central.sonatype.org/publish/publish-maven/)
375+
376+
377+

0 commit comments

Comments
 (0)