位图排序

  • 输入:一个包含n个正整数的文件,其中每个正整数都小于n,在这里n = 10^7,其中n个整数都是不可重复的。
  • 输出:以升序排列的整数列表
  • 约束:最多1MB内存,无限内存,时间最多几分钟,10s就可认为不需要优化了。

思考:一个整数占用4字节,10^7的整数占用的内存是4*10^7.一次性读取到内存中显然是不可能的。所以我们可以使用10^7/2^20 = 39趟归并排序,也就需要40次的IO操作,IO无疑是瓶颈。

我们可以使用10位长的字符串表示一个所有元素小于10的非负整数集合(集合3大特性),如:{1,2,3,5,8}可表示为:

1
0111010010 # 如果对应的位是1表示处于该集合

以上的方法非常巧妙,本来需要4个字节表示的数,仅仅通过一个位就实现了,空间瞬间剩下的几十倍,有不有?

这样就可以使用10^7个位表示10^7个整数,占用空间为10^7/2^20 = 9.53M。python实现如下:

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
#!/usr/bin python
#coding:utf-8
# 生成10^7个乱序整数

import random

RANGE = 10000000
arr = [i for i in range(RANGE)]
random.shuffle(arr)

f = open('../test/input/bitSort.input','w')

for i in arr:
f.write(str(i) + '\n')
f.close()

print 'generator input file success!'

#!/use/bin python
#coding:utf-8
# 位排序

import time

start = time.time()

#1.init set to empty
RANGE = 10000000
arr = [0 for i in range(RANGE)]

#2.insert each present elements into the set
for line in open('../test/input/bitSort.input'):
num = int(line)
arr[num] = 1

#3.write sorted ouput
f = open('../test/output/bitSort.output','w')
for i in range(RANGE):
if arr[i] == 1:
f.write(str(i) + '\n')
f.close()

end = time.time()

print 'arrar is sorted and generator output file success! and take time:' + str(end - start)

位图数据结构:描述了一个有限定义域内的稠密集合,其中每个元素最多出现1次并且没有和其他任何数据与该元素关联。

随机算法

可以跳出局部最优解找到整体最优解
更快的运行速度
机器学习领域中许多算法都要用到随机的思想:随机森林,蒙特卡洛树

数据规模的概念

在1秒内解决问题

N^2 处理10^4
N 处理10^8
NlogN 处理10^7

许多主流语言使用抛出异常的方式处理错误的,但是可能传统的返回值可能更好。抛出异常的主要好处是可以将业务代码和错误处理代码分开,但是无形中改变了控制流。

office软件如此臃肿的原因是它的文件格式本身就是一个文件系统,二进制表示。相比标准的html文档少了lexing(词法分析)和parser(解析)的过程,速度提高了几个数量级。

a=i*5在C++中可能不是表示乘法的意思,因为可能运算符重载。等号和乘号都可能重载。

软件的原则:一个对象具有setter就一定需要提供getter。

标记清除垃圾回收算法基于有向图的多点可达性,能到达的对象被标记,从而回收不带标记的对象。对有向无环图进行dfs可以实现拓扑排序。而检测一个有向图中是否有环也可以用dfs。

java泛型擦除:早期的java没有泛型,所有的泛型将在运行时擦除。也就是说运行时List和List没有区别。C++中通过模板替换的方法实现泛型,java的泛型为了兼容老的系统,在运行时会将泛型擦除。也就是说在运行的时候:List,List,List没有区别。List不是List,前者转后者需要List objList = new ArrayList(intList)。

K路归并可以使用优先队列。

线程池由阻塞队列和线程两部分组成。

marker interface。一个空接口,例如序列化接口。

数据可靠性的保证:WAL机制(Write Ahead Log,数据过来之后先写日志)

操作系统

进程和线程

进程和线程都是操作系统需要管理的2个对象。32位和64位操作系统的区别就是在寻址空间。32位操作系统2^32=4G,则每个进程有4G的可用内存空间,但是并不是代表分配给了4G内存。OS是在程序和具体硬件之间的一个中间层次,我们在程序里看到的是各个进程自己的寻址空间,每个进程自己的内存都是独立的。但文件和网络句柄是所有进程共有的。OS实际运行的是线程,进程只是线程的容器,线程里面有3个主要部分:调用栈(保存每次调用的参数和返回地址,函数内部的局部变量)、PC(程序计数器,下一条执行指令的地址,放在内存中,每个线程都各自有一个PC)和TLS(Thread Local Storage,存放各个线程独有的数据)。现代计算机大多数是存储程序型的:数据和程序是存储在同一片内存中的,内存中既有所有的变量,又有程序,因此PC指针就是指向内存的。缓冲区溢出的漏洞实例:例如有个地方是输入用户名的,本来是用来存数据的地方,黑客把用户名输入得特别长(超过了我们为用户名分配的缓冲区,一直往后写写到存储程序的那部分内存中去了,从而黑客可以将他要运行的代码通过这种框植入进来,当然防止的方法是检查用户名长度不要超过缓冲区大小)

存储体系

计算机存储体系具有层次化。Google搜索之所以快的原因是它把大多数有用的网页放到了内存中,访问量非常低的才会放在硬盘中。OS是如何寻址的(给定一个地址,如何在层次性的存储结构中找到需要的数据):寻址空间:每个进程的中指针能够取到的地址的范围,和我们机器上到底装了多少物理内存是无关的,每个进程有自己独立的寻址空间(32位对应4G,如果我们的电脑是16G内存但是装了32位的OS,每个进程最多只能用到4G,因为指针大小只有32位,只能访问4G内存。64位OS,寻址空间为10^19Bytes)。64位的JVM可以使用更大内存,需要重新编译。

在一个运行良好的程序中,我们需要的数据可以直接在物理内存中找到,从而复制到寄存器,但是如果物理内存使用量过多的话就会发生频繁分页(找到物理内存中长时间不用的数据,将虚拟内存(硬盘上)中的一个页放入物理内存),从而大幅降低系统性能。

程序寻址示意图

网络

滑动窗口

用于维护发送方和接收方的缓冲区,解决TCP传输中网络不可靠的问题(丢包、重复包、包出错、包乱序)。在没有滑动窗口的前提下保证发送方到接收方的包每个都能收到并且能按次序,就需要发送方没法送一个包,接收方接收到之后返回一个确认包。这样网络的吞吐量会非常低。连续发送多个包而无需一个个确认,这样吞吐量虽然提高了,但是链路就变得不可靠了,所以依靠滑动窗口维护发送方和接收方的缓冲区。

三次握手

TCP 3次握手的过程:

    1. 客户端 发送 syn 到 服务端 发起握手;
    1. 服务端 收到 syn后回复syn+ack给 客户端;
    1. 客户端 收到 syn+ack 后,回复 服务端 一个ack表示收到了 服务端 的syn+ack 。
文章作者: consoles
文章链接: https://consoles.fun/2016/07/14/programming-pearls/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 雨碎江南