Skip to content

Commit 633eeac

Browse files
committed
海量数据-5亿个数的大文件怎么排序?
1 parent ba1f236 commit 633eeac

File tree

3 files changed

+383
-0
lines changed

3 files changed

+383
-0
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# 大公司为什么禁止SpringBoot项目使用Tomcat?
2+
3+
## 前言
4+
5+
在SpringBoot框架中,我们使用最多的是Tomcat,这是SpringBoot默认的容器技术,而且是内嵌式的Tomcat。同时,SpringBoot也支持Undertow容器,我们可以很方便的用Undertow替换Tomcat,而Undertow的性能和内存使用方面都优于Tomcat,那我们如何使用Undertow技术呢?本文将为大家细细讲解。
6+
7+
## SpringBoot中的Tomcat容器
8+
9+
SpringBoot可以说是目前最火的Java Web框架了。它将开发者从繁重的xml解救了出来,让开发者在几分钟内就可以创建一个完整的Web服务,极大的提高了开发者的工作效率。Web容器技术是Web项目必不可少的组成部分,因为任Web项目都要借助容器技术来运行起来。在SpringBoot框架中,我们使用最多的是Tomcat,这是SpringBoot默认的容器技术,而且是内嵌式的Tomcat。推荐:[几乎涵盖你需要的SpringBoot所有操作](https://link.juejin.cn?target=https%3A%2F%2Fmp.weixin.qq.com%2Fs%2FCPtdGgzcvAv6JglKTioScQ)
10+
11+
## SpringBoot设置Undertow
12+
13+
对于Tomcat技术,Java程序员应该都非常熟悉,它是Web应用最常用的容器技术。我们最早的开发的项目基本都是部署在Tomcat下运行,那除了Tomcat容器,SpringBoot中我们还可以使用什么容器技术呢?没错,就是题目中的Undertow容器技术。SrpingBoot已经完全继承了Undertow技术,我们只需要引入Undertow的依赖即可,如下图所示。
14+
15+
![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/89d3d0c3442f49be8eefc65eca316c49~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.image?)
16+
17+
![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ed1025c2627f426a858507ad19ae8e31~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.image?)
18+
19+
配置好以后,我们启动应用程序,发现容器已经替换为Undertow。那我们为什么需要替换Tomcat为Undertow技术呢?
20+
21+
## Tomcat与Undertow的优劣对比
22+
23+
Tomcat是Apache基金下的一个轻量级的Servlet容器,支持Servlet和JSP。Tomcat具有Web服务器特有的功能,包括 Tomcat管理和控制平台、安全局管理和Tomcat阀等。Tomcat本身包含了HTTP服务器,因此也可以视作单独的Web服务器。但是,Tomcat和ApacheHTTP服务器不是一个东西,ApacheHTTP服务器是用C语言实现的HTTP Web服务器。Tomcat是完全免费的,深受开发者的喜爱。
24+
25+
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9d06f4449d9e4d2983b24e09b4fda910~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.image)
26+
27+
Undertow是Red Hat公司的开源产品, 它完全采用Java语言开发,是一款灵活的高性能Web服务器,支持阻塞IO和非阻塞IO。由于Undertow采用Java语言开发,可以直接嵌入到Java项目中使用。同时, Undertow完全支持Servlet和Web Socket,在高并发情况下表现非常出色。
28+
29+
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/36ee9da8d1404eefa1518f0f2796a76d~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.image)
30+
31+
我们在相同机器配置下压测Tomcat和Undertow,得到的测试结果如下所示:**QPS测试结果对比:** Tomcat
32+
33+
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/785bf4e5e3174556b158bb8e484a14b9~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.image)
34+
35+
Undertow
36+
37+
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f0ec493f88954686b9f6d3554684c9b4~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.image)
38+
39+
**内存使用对比:**
40+
41+
Tomcat
42+
43+
![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d593ce0dad884e3c8b07e0d6602fdac6~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.image?)
44+
45+
Undertow
46+
47+
![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/88a059beec2e4779ba971891fbfc8638~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.image?)
48+
49+
通过测试发现,在高并发系统中,Tomcat相对来说比较弱。在相同的机器配置下,模拟相等的请求数,Undertow在性能和内存使用方面都是最优的。并且Undertow新版本默认使用持久连接,这将会进一步提高它的并发吞吐能力。所以,如果是高并发的业务系统,Undertow是最佳选择。
50+
51+
## 最后
52+
53+
SpingBoot中我们既可以使用Tomcat作为Http服务,也可以用Undertow来代替。Undertow在高并发业务场景中,性能优于Tomcat。所以,如果我们的系统是高并发请求,不妨使用一下Undertow,你会发现你的系统性能会得到很大的提升。
54+
55+
56+
57+
> 参考链接:原文地址:toutiao.com/a677547665941699021

docs/leetcode/leetcode-share.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
## 关于刷题的一些碎碎念
2+
3+
在v站看到一篇关于刷题的文章,分享给大家~
4+
5+
> 链接:https://www.v2ex.com/t/910785
6+
7+
混 V 站久了,总会看到几个关于 LeetCode 刷题的帖子,刚刚又刷到一贴,《[刷算法题有感](https://www.v2ex.com/t/910741)》,在底下想评论,奈何写了太多,最后决定单独开贴,一吐为快。
8+
9+
**码农**来说,LeetCode 这种 OJ 适当刷刷能应付面试就得了,**别太当真,除非你就是喜欢这玩意儿**,本质上跟有人喜欢解数学题、有人喜欢写毛笔字、有人喜欢画漫画……等等等等没什么不同的话,那这个另算。
10+
11+
可是,我相信**发自内心喜欢这个的人终究是少数**。大多数自诩对算法有热情的,无非是别有目的,也可以说是动机不纯,只是刚到了刷题的甜蜜点,自我催眠一下罢了。不然遇到难题想破脑袋都想不出来,那是真的打击积极性,不自我暗示一下根本坚持不下去。
12+
13+
刷题只是为了过笔试找工作,这本身并没什么不对,**进大厂多赚点钱嘛不寒碜,但这并不能称之为热爱**。我始终认为,真正热爱算法的人,也一定会喜欢数学。因为**算法的本质就是数学**,解题时的思考过程也相差无几。不信你大可以去看看那些算法书,真较起真来是要用数学来证明算法的正确性和效率的。
14+
15+
至于去讨论区看题解,发现各路高手贴出无比巧妙刁钻优雅简洁的解法,同时还附赠了简单的数学证明过程,当然会有 mind blown 时刻,然后相比之下自己就是个渣渣,就忍不住开始自我怀疑。
16+
17+
然而,你跟那些高手比,就相当于你一个普通念书备战高考的,跟校田径队的体育特长生去比短跑长跑。你根本没花心思练过,某天心血来潮,看人家跑挺好,自己也想试试,结果跟着跑了两步,发现完全跟不上,然后说我靠这帮孙子跑太快了,我是不是体质不行没有天赋不适合跑步啊——大可不必如此。
18+
19+
不说那些从初高中开始就玩竞赛的,毕竟人家可能根本瞧不上 LeetCode 这个平台的出题水平,哪怕是读了研才开始**自学转码**,只要下足功夫,也能做到 LeetCode 竞赛分数 2400 分左右——这是什么概念? Top 0.6%。怎么做到的?先刷够 2500 道题再说。**扪心自问,你做得到吗?**先不论是否可行,只看主观能动性,你真的愿意付出大量的时间和精力在这上面吗?
20+
21+
话说回来,刷题有没有用,要看你的目的是什么。如果像上面提到的那样,为了刷题过笔试找工作进大厂赚高薪,那肯定是有用,不然 LeetCode 不会火遍全球。但 LeetCode 刷得溜,并不代表你工程能力强,能写出易于维护、方便扩展的代码,或是能做出超炫酷的软件,也不代表你的算法造诣深,能像 Dijkstra 那样发明新的算法,同样不代表你能设计出一个数据库、编译器、网络协议或是操作系统,这些领域对算法确实有相当的要求,但刷题本身跟这些完全是两码事。
22+
23+
抛开功利性的目的不谈,刷题本身对码农来说也是种思维和编程上的双重训练。能解难题,说明对某些**明确了特定条件**的问题,有较强的抽象能力。很多时候眼睁睁看着归类标签,明知道要用某一算法来解,读完了题都不知道从哪开始下手。另外题解代码能同时做到“高效、优雅、简洁、易读”,说明你比较熟悉某一门编程语言,能用一些语法糖和语言特定的“奇技淫巧”,以及在工程上至少某一局部的代码,比如明确了输入输出的某个 util 函数,能写得比较优质。
24+
25+
君不见有太多**基本编程素养不过关**的码农,为了解决一个很简单的功能,循环嵌套了一层又一层,却根本不知道一个递归就能简洁明了地搞定。对于这样的选手,简单刷几十道题就会有明显进步,那刷题肯定是有用的。
26+
27+
再简单推荐两个我的偶像,LeetCode 上比较老的题的讨论区很容易找到 [Stefan](https://leetcode.com/stefanpochmann/),这位好几个语言都玩得贼溜,但很久不更新了,大概对他来说解题只是娱乐的一种吧,感兴趣的可以去他的上古个人博客看了解更多。新题则是国人之光 [Lee215](https://leetcode.com/lee215/)~~我愿称之为 LeetCode 界的 Faker (不是)~~,基本每个题解都会有 C++/Java/Python 三种写法。国服大佬也很多,但我只刷过美服,就不太了解了。
28+
29+
虽然可能没人感兴趣,但都写了这么多,就干脆再简单聊下自己。我刷题坚持刷了一年多,每天 996 下班回家之后还开机刷道题,365 天风雨无阻毫无例外,当时自我感动得不行,最后也只是拿了个 LeetCode 的 Annual Medal 2021 徽章而已,依然菜得抠脚。到目前不刷题也已经快一年了,因为意识到在软件工程领域,无论是 Web 前后端还是别的什么,都还有更多有趣的东西可以去探索把玩。同时也是因为根本没可能去 FAANG 或是 GAMAM ,whatever ,就看开了。
Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
5亿个数的大文件怎么排序?
2+
==================================================
3+
## **问题**
4+
5+
给你1个文件`bigdata`,大小4663M,5亿个数,文件中的数据随机,一行一个整数:
6+
7+
```bash
8+
6196302
9+
3557681
10+
6121580
11+
2039345
12+
2095006
13+
1746773
14+
7934312
15+
2016371
16+
7123302
17+
8790171
18+
2966901
19+
...
20+
7005375
21+
```
22+
23+
现在要对这个文件进行排序,怎么做?
24+
25+
## 内部排序
26+
27+
先尝试内排,选2种排序方式:
28+
29+
### 3路快排:
30+
31+
```java
32+
private final int cutoff = 8;
33+
34+
public <T> void perform(Comparable<T>[] a) {
35+
perform(a,0,a.length - 1);
36+
}
37+
38+
private <T> int median3(Comparable<T>[] a,int x,int y,int z) {
39+
if(lessThan(a[x],a[y])) {
40+
if(lessThan(a[y],a[z])) {
41+
return y;
42+
}
43+
else if(lessThan(a[x],a[z])) {
44+
return z;
45+
}else {
46+
return x;
47+
}
48+
}else {
49+
if(lessThan(a[z],a[y])){
50+
return y;
51+
}else if(lessThan(a[z],a[x])) {
52+
return z;
53+
}else {
54+
return x;
55+
}
56+
}
57+
}
58+
59+
private <T> void perform(Comparable<T>[] a,int low,int high) {
60+
int n = high - low + 1;
61+
//当序列非常小,用插入排序
62+
if(n <= cutoff) {
63+
InsertionSort insertionSort = SortFactory.createInsertionSort();
64+
insertionSort.perform(a,low,high);
65+
//当序列中小时,使用median3
66+
}else if(n <= 100) {
67+
int m = median3(a,low,low + (n >>> 1),high);
68+
exchange(a,m,low);
69+
//当序列比较大时,使用ninther
70+
}else {
71+
int gap = n >>> 3;
72+
int m = low + (n >>> 1);
73+
int m1 = median3(a,low,low + gap,low + (gap << 1));
74+
int m2 = median3(a,m - gap,m,m + gap);
75+
int m3 = median3(a,high - (gap << 1),high - gap,high);
76+
int ninther = median3(a,m1,m2,m3);
77+
exchange(a,ninther,low);
78+
}
79+
80+
if(high <= low)
81+
return;
82+
//lessThan
83+
int lt = low;
84+
//greaterThan
85+
int gt = high;
86+
//中心点
87+
Comparable<T> pivot = a[low];
88+
int i = low + 1;
89+
90+
/*
91+
* 不变式:
92+
* a[low..lt-1] 小于pivot -> 前部(first)
93+
* a[lt..i-1] 等于 pivot -> 中部(middle)
94+
* a[gt+1..n-1] 大于 pivot -> 后部(final)
95+
*
96+
* a[i..gt] 待考察区域
97+
*/
98+
99+
while (i <= gt) {
100+
if(lessThan(a[i],pivot)) {
101+
//i-> ,lt ->
102+
exchange(a,lt++,i++);
103+
}else if(lessThan(pivot,a[i])) {
104+
exchange(a,i,gt--);
105+
}else{
106+
i++;
107+
}
108+
}
109+
110+
// a[low..lt-1] < v = a[lt..gt] < a[gt+1..high].
111+
perform(a,low,lt - 1);
112+
perform(a,gt + 1,high);
113+
}
114+
```
115+
116+
### 归并排序:
117+
118+
```java
119+
/**
120+
* 小于等于这个值的时候,交给插入排序
121+
*/
122+
private final int cutoff = 8;
123+
124+
/**
125+
* 对给定的元素序列进行排序
126+
*
127+
* @param a 给定元素序列
128+
*/
129+
@Override
130+
public <T> void perform(Comparable<T>[] a) {
131+
Comparable<T>[] b = a.clone();
132+
perform(b, a, 0, a.length - 1);
133+
}
134+
135+
private <T> void perform(Comparable<T>[] src,Comparable<T>[] dest,int low,int high) {
136+
if(low >= high)
137+
return;
138+
139+
//小于等于cutoff的时候,交给插入排序
140+
if(high - low <= cutoff) {
141+
SortFactory.createInsertionSort().perform(dest,low,high);
142+
return;
143+
}
144+
145+
int mid = low + ((high - low) >>> 1);
146+
perform(dest,src,low,mid);
147+
perform(dest,src,mid + 1,high);
148+
149+
//考虑局部有序 src[mid] <= src[mid+1]
150+
if(lessThanOrEqual(src[mid],src[mid+1])) {
151+
System.arraycopy(src,low,dest,low,high - low + 1);
152+
}
153+
154+
//src[low .. mid] + src[mid+1 .. high] -> dest[low .. high]
155+
merge(src,dest,low,mid,high);
156+
}
157+
158+
private <T> void merge(Comparable<T>[] src,Comparable<T>[] dest,int low,int mid,int high) {
159+
160+
for(int i = low,v = low,w = mid + 1; i <= high; i++) {
161+
if(w > high || v <= mid && lessThanOrEqual(src[v],src[w])) {
162+
dest[i] = src[v++];
163+
}else {
164+
dest[i] = src[w++];
165+
}
166+
}
167+
}
168+
```
169+
170+
数据太多,递归太深,会导致栈溢出。数据太多,数组太长,会导致OOM。
171+
172+
可见这两种方式不适用。
173+
174+
## 位图法
175+
176+
BitMap算法的核心思想是用bit数组来记录0-1两种状态,然后再将具体数据映射到这个比特数组的具体位置,这个比特位设置成0表示数据不存在,设置成1表示数据存在。
177+
178+
BitMap算在在大量数据查询、去重等应用场景中使用的比较多,这个算法具有比较高的空间利用率。
179+
180+
实现代码如下:
181+
182+
```csharp
183+
private BitSet bits;
184+
185+
public void perform(
186+
String largeFileName,
187+
int total,
188+
String destLargeFileName,
189+
Castor<Integer> castor,
190+
int readerBufferSize,
191+
int writerBufferSize,
192+
boolean asc) throws IOException {
193+
194+
System.out.println("BitmapSort Started.");
195+
long start = System.currentTimeMillis();
196+
bits = new BitSet(total);
197+
InputPart<Integer> largeIn = PartFactory.createCharBufferedInputPart(largeFileName, readerBufferSize);
198+
OutputPart<Integer> largeOut = PartFactory.createCharBufferedOutputPart(destLargeFileName, writerBufferSize);
199+
largeOut.delete();
200+
201+
Integer data;
202+
int off = 0;
203+
try {
204+
while (true) {
205+
data = largeIn.read();
206+
if (data == null)
207+
break;
208+
int v = data;
209+
set(v);
210+
off++;
211+
}
212+
largeIn.close();
213+
int size = bits.size();
214+
System.out.println(String.format("lines : %d ,bits : %d", off, size));
215+
216+
if(asc) {
217+
for (int i = 0; i < size; i++) {
218+
if (get(i)) {
219+
largeOut.write(i);
220+
}
221+
}
222+
}else {
223+
for (int i = size - 1; i >= 0; i--) {
224+
if (get(i)) {
225+
largeOut.write(i);
226+
}
227+
}
228+
}
229+
230+
largeOut.close();
231+
long stop = System.currentTimeMillis();
232+
long elapsed = stop - start;
233+
System.out.println(String.format("BitmapSort Completed.elapsed : %dms",elapsed));
234+
}finally {
235+
largeIn.close();
236+
largeOut.close();
237+
}
238+
}
239+
240+
private void set(int i) {
241+
bits.set(i);
242+
}
243+
244+
private boolean get(int v) {
245+
return bits.get(v);
246+
}
247+
```
248+
249+
## 外部排序
250+
251+
什么是外部排序?
252+
253+
> 1. 内存极少的情况下,利用分治策略,利用外存保存中间结果,再用多路归并来排序;
254+
255+
实现原理如下:
256+
257+
![](http://img.topjavaer.cn/img/5亿个数大文件排序2.png)
258+
259+
**1.分成有序的小文件**
260+
261+
内存中维护一个极小的核心缓冲区`memBuffer`,将大文件`bigdata`按行读入,搜集到`memBuffer`满或者大文件读完时,对`memBuffer`中的数据调用内排进行排序,排序后将**有序结果**写入磁盘文件`bigdata.xxx.part.sorted`.
262+
循环利用`memBuffer`直到大文件处理完毕,得到n个有序的磁盘文件:
263+
264+
![](http://img.topjavaer.cn/img/5亿个数大文件排序3.png)
265+
266+
**2.合并成1个有序的大文件**
267+
268+
现在有了n个有序的小文件,怎么合并成1个有序的大文件?
269+
270+
利用如下原理进行归并排序:
271+
272+
![](http://img.topjavaer.cn/img/5亿个数大文件排序1.png)
273+
274+
举个简单的例子:
275+
276+
> 文件1:**3**,6,9。
277+
> 文件2:**2**,4,8。
278+
> 文件3:**1**,5,7。
279+
>
280+
> 第一回合:
281+
> 文件1的最小值:3 , 排在文件1的第1行。
282+
> 文件2的最小值:2,排在文件2的第1行。
283+
> 文件3的最小值:1,排在文件3的第1行。
284+
> 那么,这3个文件中的最小值是:min(1,2,3) = 1。
285+
> 也就是说,最终大文件的当前最小值,是文件1、2、3的当前最小值的最小值。
286+
> 上面拿出了最小值1,写入大文件。
287+
288+
> 第二回合:
289+
> 文件1的最小值:3 , 排在文件1的第1行。
290+
> 文件2的最小值:2,排在文件2的第1行。
291+
> 文件3的最小值:5,排在文件3的第2行。
292+
> 那么,这3个文件中的最小值是:min(5,2,3) = 2。
293+
> 将2写入大文件。
294+
>
295+
> 也就是说,最小值属于哪个文件,那么就从哪个文件当中取下一行数据。(因为小文件内部有序,下一行数据代表了它当前的最小值)
296+
297+
感兴趣的小伙伴可以自己尝试去实现下~

0 commit comments

Comments
 (0)