前言
后端面试经常会被问到文件读写、大数据处理等问题。而这对一个选手关于OS、并发、数据结构等的考察是很全面的。因此特此开一个专题来完成这系列的学习。
大三的时候面试被问过大数据文件读取与处理、统计的问题,当时模糊回答了外排序、分治的说法,但是具体怎么做完全没有想法。
龙哥(我的一个师兄)今天也让我尝试一下自己手动解决这样的问题。
龙哥一共给了我两个题目:
- (入门)一个10G的大文件,每一行加一个行号,并输出到另一个文件上,实现出来。
- (进阶)一个10G的大文件,我要统计出现频率TOP 10的词语,实现出来。(同类型的题还有 top 10的ip、 top 10的query)
因此,在这篇博客中将完成第一个入门实验。
实验环境:
- Win10
- Java8
数据准备
为了给第二个实验做数据准备,我们这里生成ip地址作为实验数据。
- 文件是字符串形式的,一个utf-8下一个字符就是一个字节,
- 对于一个ip
192.168.101.101\n
,一共16个字符,实际上肯定会比16小,如192.168.1.1
- 要生成10GB的文件,则要有:1010241024*1024/16=671088640行,即6.7亿行。
- 因此,我这里直接生成七亿条
/**
* 生成模拟IP数据
*
* @param logNum 生成数据条数
* @param PATH 生成数据的路径
* @throws IOException I/O error
*/
public static void generateData(long logNum, String PATH) throws IOException {
/* 创建文件 */
File fp = new File(PATH);
File parentDir = fp.getParentFile();
if (!parentDir.exists()) {
parentDir.mkdirs();
}
fp.createNewFile();
/* 生成随机ip写入文件 */
SecureRandom random = new SecureRandom();
try (BufferedWriter bw = new BufferedWriter(new FileWriter(fp))) {
for (int i = 0; i < logNum; i++) {
if (i % 1000000 == 0)
System.out.printf("已经写了:%d百万个ip了,等等吧\n", i / 1000000);
StringBuilder sb = new StringBuilder();
sb.append("192.").append(random.nextInt(255)).append(".").append(random.nextInt(255)).append(".")
.append(random.nextInt(255)).append('\n');
bw.write(sb.toString());
}
bw.flush();
}
}
在Main直接调用DataUtils.generateLog(700000000,"D:\\Code\\javacode\\LongGeBigData\\data\\ip.log");
然后等待五分钟
当然我们也取前100行来做测试:head -n 100 ip.log >> ip.sam.100.log
思路
第一题要求是:一个10G的大文件,每一行加一个行号,并输出到另一个文件上。
思路1
由于题目允许输出到另一个文件,所以我们可以通过BufferedReader和BufferedWriter来处理。BufferedReader读取源文件,BufferedWriter写去另一个文件。很合理。默认private static int defaultCharBufferSize = 8192;
InputStream、OutputStream 处理字节流
InputStreamReader / OutputStreamWriter 处理字符流,将字节流转换为字符流.
BufferedReader BufferedWriter 通用的缓冲方式读写文本
/**
* 添加行号
*
* @param INPUT_PATH 输入路径
* @param OUTPUT_PATH 输出路径
* @throws IOException ioexception
*/
public static void addLineNum(String INPUT_PATH, String OUTPUT_PATH) throws IOException {
File fp_in = new File(INPUT_PATH);
File fp_out = new File(OUTPUT_PATH);
if (!fp_out.getParentFile().exists()) {
fp_out.getParentFile().mkdirs();
}
fp_out.createNewFile();
int count = 1;
try (BufferedWriter bw = new BufferedWriter(new FileWriter(fp_out));
BufferedReader br = new BufferedReader(new FileReader(fp_in))) {
String line;
while((line=br.readLine())!=null){
if (count % 1000000 == 0)
System.out.printf("已经处理了:%d百万个ip了,等等吧\n", count / 1000000);
bw.write(count++ +" "+ line +'\n');
}
bw.flush();
}
}
经过demo测试是正确的,所以我们直接跑10GB文件,等一下就知道对不对了。
生成ip.out.log
之后,我们看看尾巴tail ip.out.log
的行号,以及用wc -l ip.out.log
看看是不是一致的就可以了。
(base) ➜ data tail ip.out.log
...
699999998 192.94.89.23
699999999 192.151.29.90
700000000 192.237.193.74
后话
明天完成真正的大文件题目,一个10G的访问IP列表文件,我要统计出现频率TOP 10的IP
方法是:
- 生成大文件
- 拆分大文件
- 小文件处理逻辑
- 线程池处理多个小文件
- 合并结果输出
明天将一一完成。欢迎留言交流。
2022-3-21 更新
上面的代码有一个问题,如果一行就很大,比如只有一行就10GB,这样会直接爆内存吗?如果会,应该怎么做? 留坑