数据结构
模块一:数组和链表
1. 数组内存模型
- 一维数组
获取一维数组元素的方式:
b a s e _ a d d r e s s + i n d e x ( 索 引 ) × d a t a _ s i z e ( 数 据 类 型 大 小 ) base\_address + index(索引)× data\_size(数据类型大小) base_address+index(索引)×data_size(数据类型大小)
例子:若data数组存放的是int类型数值,data[0]的地址为0x80000000,则data[4]的地址为 0x80000000+4*4=0x80000010 - 二维数组
声明一个二维数组:int[][] data=new int[2][3]
(1) 行优先存储
b a s e _ a d d r e s s + d a t a _ s i z e × ( i × n u m b e r _ o f _ c o l u m n + j ) base\_address + data\_size × (i × number\_of\_column + j) base_address+data_size×(i×number_of_column+j)
例:data[0][0]的地址为0x80000000,则data[0][1]的地址为: 0 x 80000000 + 4 × ( 0 × 3 + 1 ) = 0 x 80000004 0x80000000+4×(0×3+1)=0x80000004 0x80000000+4×(0×3+1)=0x80000004
(2) 列优先存储
b a s e _ a d d r e s s + d a t a _ s i z e × ( i + n u m b e r _ o f _ r o w × j ) base\_address + data\_size × (i + number\_of\_row × j) base_address+data_size×(i+number_of_row×j)
例:data[0][0]的地址为0x80000000,则data[0][1]的地址为: 0 x 80000000 + 4 × ( 0 + 2 × 1 ) = 0 x 80000008 0x80000000+4×(0+2×1)=0x80000008 0x80000000+4×(0+2×1)=0x80000008 - java集合add()函数源码
假设有ElementData数组,add()的源码:
public void add(int index, E element) {
this.rangeCheckForAdd(index);
++this.modCount;
int s;
Object[] elementData;
if ((s = this.size) == (elementData = this.elementData).length) {
elementData = this.grow();
}
//主要调用该函数
System.arraycopy(elementData, index, elementData, index + 1, s - index);
elementData[index] = element;
this.size = s + 1;
}
如果在ElementData要执行add(1,4)的操作,就要执行*arraycopy(ElementData,1,ElementData,2,6-1)*它的意思是将从 ElementData 数组 index 为 1 的地址开始,复制往后的 5 个元素到 ElementData 数组 index 为 2 的地址位置,如下图所示:
2. 位数组在Redis中的应用
拥有两个元素的 int 数组的内存模型
第i个比特位的位置为:
所在数组中的元素为: i / data_size,例:35/32=1
比特位在元素中的位置为:i % data_size,例35%32=3
- GetBit
核心算法:
boolean GetBit(int[] array, int index) {
...
int elementIndex = index / 32;
int position = index % 32;
long flag = 1;
flag = flag << position;
if ((array[elementIndex] & flag) != 0) {
return true;
} else {
return false;
}
}
GetBit(d, 35) 这条语句,将得到 elementIndex 为 1、position 为 3、flag 为 0x08,将 d[1] 和 0x08 进行位操作的与运算,最后可以得出一个非 0 的结果,所以这个函数返回 true。
而如果调用了 GetBit(d, 32) 这条语句,我们将得到 elementIndex 为 1、position 为 0、flag 为 0x1,将 d[1] 和 0x1 进行位操作的与运算,最后可以得出一个 0 的结果,所以这个函数返回 false。
- SetBit
核心算法:
void SetBit(int[] array, int index) {
...
int elementIndex = index / 32;
int position = index % 32;
long flag = 1;
flag = flag << position;
array[elementIndex] = array[elementIndex] | flag;
}
原数组为:
调用了 SetBit(d, 35) 这条语句,elementIndex=1,position=3,flag=0x08,进行或运算得到:
- ClearSetBit
核心算法:
void ClearBit(int[] array, int index) {
...
int elementIndex = index / 32;
int position = index % 32;
long flag = 1;
flag = ~(flag << position);
array[elementIndex] & flag;
}
原位数组:
调用了 ClearBit(d,32) 这条语句,得到elementIndex=1,position=3,flag=0xFFFFFFFE,位运算得到:
模块二:哈希表的应用
1. 哈希函数的本质及生成方式
String类型的哈希函数公式:
- 常用的哈希函数算法
SHA-1加密算法
运行“git commit”命令的时候,Git 会将所有的这些文件,外加一些元数据(Metadata)再做一次 SHA-1 运算来得到一个新的哈希值。
2. 哈希碰撞的本质及解决方式
-
开放寻址法
开放寻址法本质上是在数组中寻找一个还未被使用的位置,将新的值插入。这样做的好处是利用数组原本的空间而不用开辟额外的空间来保存值。最简单明了的方法就是沿着数组索引,往下一个一个地去寻找还未被使用的空间,这种方法叫做线性探测(Linear Probing)。
-
负载因子
负载因子可以被定义为是哈希表中保存的元素个数 / 哈希表中底层数组的大小 -
分离链接法