一、一致性哈希算法
现如今互联网数据量越来越大,以往单机系统技术不再符合需要,分布式越来越流行。nosql算是发展的非常快也很流行的一种技术了,尤其redis与memcached 这种缓存技术由于不需要像传统的关系型数据库那样要求高度数据一致性以及持久性,在分布式领域大展拳脚,集群是常见的做法。
像mysql与oracle这种数据库对高并发的读写性能进行弹性扩展的代价非常大,而部分场景下一些热点数据的更改速度很慢,比如网站频繁访问一些图片,软文,这些内容频繁读会对db造成极大压力,nosql集群可以很好解决这些问题,缓存起来不再频繁读数据库,完美解决了高并发问题。
集群代表着数据要在不同的节点上存储,而要保证最大利用化就应该让数据均匀落在不同节点上,哈希算法是一个很好的做法。例如集群中有8个redis节点,存储数据时hash(k)%8,算出应保存的节点位置。但集群的高性能带来的代价是可用性难以保证,这8个节点随时可能发生某些节点异常失效,此时集群的可用数目就发生了变化,那么这个算法导致的极端后果就是几乎所有的数据保存位置都发生了变化,数据大量迁移会极大消耗资源,造成难以预料的后果。
解决普通的哈希算法的缺陷的一个有效做法,应该尽可能让数据的存储位置保持不变,即使集群中某个节点失效,也应只让这个节点的数据进行迁移,而不是整体迁移。一致性哈希算法由此而来。
一致性哈希算法原理:
1、求出每个服务器的hash(服务器ip)值,将其配置到一个 0~2^n 的圆环上(n通常取32)。
2,用同样的方法求出待存储对象的主键 hash值,也将其配置到这个圆环上,然后从数据映射到的位置开始顺时针查找,将数据分布到找到的第一个服务器节点上。
我们可以分析一下这种做法,集群中存在8个节点,如果这8个节点均匀的分布在这个环上,那么环被分成了8个区,如图
大部分某个节点A失效时,那么这个节点的在环上位置与到前一个有效节点位置的数据都会迁移到下一个节点上,那么整体来看环上只有1/8的数据被迁移,其他位置的数据位置没有发生变化,最终相比普通的哈希算法,可用性提高了8倍,完美!!!
二、排序算法
1、冒泡排序
public int[] sort(int[] arr) {
int length = arr.length;
for (int i = 0; i < length; i++) {
for (int j = 0; j < length - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
return arr;
}
2、插入排序
空间复杂度:O(1)
时间复杂度: 最好O(N) 最坏与平均:O(N2)
public void insertSort(int[] array) {
for (int index = 1; index < array.length; index++) {// 外层向右的index,即作为比较对象的数据的index
int temp = array[index];// 用作比较的数据
int leftindex = index - 1;
while (leftindex >= 0 && array[leftindex] > temp) {// 当比到最左边或者遇到比temp小的数据时,结束循环
array[leftindex + 1] = array[leftindex];
leftindex--;
}
array[leftindex + 1] = temp;// 把temp放到空位上
}
}
3、选择排序
空间复杂度:O(1)
时间复杂度: 最好O(1) 最坏与平均都是 O(N2)
public void selectSort(int[] ins) {
int n = ins.length;// 经过n-1次提取最小最大值
for (int i = 0; i < n; i++) {// 控制选择的次数
// 最小值下标暂取值
int minIndex = i;
// 从下一个元素开始
for (int j = i + 1; j < n; j++) {
// i+1 ~ n-1 中选出一个最小的元素下标
if (ins[j] < ins[minIndex]) {
minIndex = j;
}
}
// 若最小值下标不是起始点则与起始点交换位置
if (min_index != i) {
int temp = ins[i];
ins[i] = ins[minIndex];
ins[minIndex] = temp;
}
}
}
4、快速排序
空间复杂度(不稳定):O(
log
2
n
\log_2n
log2n)~O(n)
时间复杂度:最坏O(N2) , 最好与平均:O(N* l o g 2 N log_2N log2N)
public void quickSort(int[] arr, int low, int high) {
int i, j, temp, t;
if (low > high) {
return;
}
i = low;
j = high;
// temp就是基准位
temp = arr[low];
while (i < j) {
// 先看右边,依次往左递减 (1)
while (temp <= arr[j] && i < j) {
j--;
}
// 再看左边,依次往右递增(2)
while (temp >= arr[i] && i < j) {
i++;
}
// 如果满足条件则交换
if (i < j) {
t = arr[j];
arr[j] = arr[i];
arr[i] = t;
}
}
// 最后将基准为与i和j相等位置的数字交换 (1) (2) 顺序不能颠倒
arr[low] = arr[i];
arr[i] = temp;
// 递归调用左半数组
quickSort(arr, low, j - 1);
// 递归调用右半数组
quickSort(arr, j + 1, high);
}
5、归并排序
空间复杂度 O(n)
时间复杂度(稳定不变):O(N*
log
2
N
\log_2N
log2N)
//第一步 分解数组
public void mergeSort(int[] data, int start, int end) {
if (start < end) {
int mid = (start + end) / 2;
mergeSort(data, start, mid);
mergeSort(data, mid + 1, end);
mergeSort(data, start, mid, end);
}
}
//第二步 合并两个数组 保持有序
public void mergeSort(int[] d, int start, int mid, int end) {
int[] res = new int[end - start + 1];
int i = 0, j = start, k = mid + 1;
while (j <= mid && k <= end) {
if (d[j] < d[k]) {
res[i++] = d[j++];
} else {
res[i++] = d[k++];
}
}
while (j <= mid) {
res[i++] = d[j++];
}
while (k <= end) {
res[i++] = d[k++];
}
System.arraycopy(res, 0, d, start, res.length);
}
6、堆排序
7、希尔排序
public int[] xSort(int[] ins) {
int n = ins.length;
int gap = n / 2;
while (gap > 0) {
for (int j = gap; j < n; j++) {
int i = j;
while (i >= gap && ins[i - gap] > ins[i]) {
int temp = ins[i - gap] + ins[i];
ins[i - gap] = temp - ins[i - gap];
ins[i] = temp - ins[i - gap];
i -= gap;
}
}
gap = gap / 2;
}
return ins;
}
三、哈希算法
哈希碰撞处理方法
- 开放地址法
- 再哈希法
- 链地址法
- 建立一个公共溢出区
四、二叉排序树、B树、B+树、Hash树的使用场景
二叉排序树:左子节点不大于根节点,右子节点不小于根节点,
极端情况下比如有序数列会退化成链表,不利于查找
优化措施:调整树,尽可能保持节点均匀分布,降低树的高度,例如红黑树
B树:多路搜索树,每个节点可以拥有多于两个孩子节点,M路的B树最多能拥有M个孩子节点
极端情况:路数过多则退化为有序数组
用途:文件系统,理由:数据量过大,不能一次性加载到内存,B树可以每次加载进一个节点进行查找
B+树:以B树为基础,同时叶子节点之间加了指针形成链表
设计理由:数据库中使用较多,select时经常选择多条,此时B树需要局部遍历,可能要跨层访问,而B+树所有数据都在叶子节点,不用跨层,同时由于链表结构,只需要找到首尾就能取出所有数据
优点:一次性查找多条数据时比Hash树快,因为它索引有序并且有链表相连
Hash树:查找更快,O(1)复杂度,,但选择多条数据时不如B+树,