## 排序
[TOC]
### 1\. 选择排序(Selection Sort)
选择排序(Selection Sort)是一种简单直观的排序算法。它的主要思路是每次从未排序的部分中选出最小(或最大)的元素,放到已排序部分的末尾,直到所有元素都被排序为止。
### 选择排序的逻辑
1. **初始状态:** 将数组分为已排序部分和未排序部分。开始时,已排序部分为空,未排序部分包含整个数组。
2. **选择最小值:** 在未排序部分中找到最小值。
3. **交换位置:** 将这个最小值与未排序部分的第一个元素交换位置,使其进入已排序部分。
4. **重复步骤:** 对未排序部分重复上述步骤,直到未排序部分为空。
### 时间复杂度
选择排序的时间复杂度为 O(n2),其中 n是数组的长度。它在每次选择最小值时需要扫描未排序部分,导致时间复杂度较高。
### 代码示例
~~~
public class SelectionSort {
// 主函数:用于演示选择排序的实现
public static void main(String[] args) {
int[] arr = {64, 25, 12, 22, 11}; // 定义待排序的数组
selectionSort(arr); // 调用选择排序算法
printArray(arr); // 打印排序后的数组
}
// 选择排序算法的实现
public static void selectionSort(int[] arr) {
int n = arr.length; // 获取数组长度
// 遍历数组,依次找到最小元素并放到已排序部分的末尾
for (int i = 0; i < n - 1; i++) {
int minIndex = i; // 假设当前未排序部分的第一个元素是最小值
for (int j = i + 1; j < n; j++) { // 遍历未排序部分
if (arr[j] < arr[minIndex]) { // 如果找到比假设最小值更小的元素
minIndex = j; // 更新最小值的索引
}
}
// 交换最小值和未排序部分第一个元素的位置
int temp = arr[minIndex]; // 临时变量保存最小值
arr[minIndex] = arr[i]; // 将未排序部分第一个元素放到最小值的位置
arr[i] = temp; // 将最小值放到已排序部分的末尾
}
}
// 辅助函数:打印数组内容
public static void printArray(int[] arr) {
for (int i = 0; i < arr.length; i++) { // 遍历数组的每个元素
System.out.print(arr[i] + " "); // 打印元素
}
System.out.println(); // 换行,便于下一次输出
}
}
~~~
### 代码说明
* **selectionSort方法:** 该方法实现了选择排序算法。首先,外层循环从头到尾遍历数组,每次选择出一个最小值放到已排序部分的末尾。内层循环在未排序部分找到最小值,并记录其位置,最后通过交换将最小值放置到正确的位置。
* **printArray方法:** 用于打印数组内容。排序后调用这个方法可以看到排序结果。
选择排序的优点是算法简单易懂,代码量少,适用于数据量较小的场景。缺点是时间复杂度较高,不适合处理大量数据。
### 2\. 插入排序(Insertion Sort)
插入排序从左到右进行,每次都将当前元素插入到左侧已经排序的数组中,使得插入之后左部数组依然有序。
第 j 元素是通过不断向左比较并交换来实现插入过程:当第 j 元素小于第 j - 1 元素,就将它们的位置交换,然后令 j 指针向左移动一个位置,不断进行以上操作。
[![](https://camo.githubusercontent.com/5e6dcf3d502a864805ecd49fefac164dd91e4b49/68747470733a2f2f696d61676573323031372e636e626c6f67732e636f6d2f626c6f672f3834393538392f3230313731302f3834393538392d32303137313031353232353634353237372d313135313130303030302e676966)](https://camo.githubusercontent.com/5e6dcf3d502a864805ecd49fefac164dd91e4b49/68747470733a2f2f696d61676573323031372e636e626c6f67732e636f6d2f626c6f672f3834393538392f3230313731302f3834393538392d32303137313031353232353634353237372d313135313130303030302e676966)
**代码实现**
~~~java
public static void sort(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
for (int j = i + 1; j > 0; j--) {
if (arr[j] < arr[j - 1])
swap(arr, j, j - 1); // 大量的交换会消耗时间
else
break;
}
}
}
// 改进版插入排序(减少了数组元素的操作次数)
public static void better_sort(int[] arr) {
for (int i = 0; i < arr.length; i++) {
int e = arr[i];
int j = i;
for (; j > 0; j--) {
if (e < arr[j - 1])
arr[j] = arr[j - 1];
else
break;
}
arr[j] = e;
}
}
private static void swap(int[] arr, int i, int j) {
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
~~~
**算法分析**
插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
### 3\. 冒泡排序(Bubble Sort)
通过从左到右不断交换相邻逆序的相邻元素,在一轮的交换之后,可以让未排序的元素上浮到右侧。
在一轮循环中,如果没有发生交换,就说明数组已经是有序的,此时可以直接退出。
[![](https://camo.githubusercontent.com/adaf3658c694ce232520c3f2b677b3273e0e6c1a/68747470733a2f2f696d61676573323031372e636e626c6f67732e636f6d2f626c6f672f3834393538392f3230313731302f3834393538392d32303137313031353232333233383434392d323134363136393139372e676966)](https://camo.githubusercontent.com/adaf3658c694ce232520c3f2b677b3273e0e6c1a/68747470733a2f2f696d61676573323031372e636e626c6f67732e636f6d2f626c6f672f3834393538392f3230313731302f3834393538392d32303137313031353232333233383434392d323134363136393139372e676966)
**代码实现**
~~~java
private static void sort(int[] arr) {
for (int i = arr.length - 1; i > 0; i--) { // 从最后一位开始确定
boolean swapped = false;
for (int j = 0; j < i; j++) {
if(arr[j] > arr[j+1]){
swapped = true;
swap(arr,j,j+1);
}
}
if(!swapped)
return;
}
}
private static void swap(int[] arr, int i, int j) {
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
~~~
### 4\. 希尔排序(Shell Sort)
1959年Shell发明,第一个突破O(n2)的排序算法,是简单插入排序的改进版。它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫**缩小增量排序**。
**算法描述**
先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述:
* 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
* 按增量序列个数k,对序列进行k 趟排序;
* 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
[![](https://camo.githubusercontent.com/fda52e27af7d3624e110e94109c28fd27fa44871/68747470733a2f2f696d61676573323031382e636e626c6f67732e636f6d2f626c6f672f3834393538392f3230313830332f3834393538392d32303138303333313137303031373432312d3336343530363037332e676966)](https://camo.githubusercontent.com/fda52e27af7d3624e110e94109c28fd27fa44871/68747470733a2f2f696d61676573323031382e636e626c6f67732e636f6d2f626c6f672f3834393538392f3230313830332f3834393538392d32303138303333313137303031373432312d3336343530363037332e676966)
**代码实现**
~~~java
// 希尔排序
public static void sort(int[] arr) {
int n = arr.length;
for (int h = n / 2; h > 0; h = h / 2) {
// 内部是一个插入排序
for (int i = 0; i < n; i = i + h) {
int e = arr[i];
int j = i;
for (; j > 0; j = j - h) {
if (e < arr[j - h])
arr[j] = arr[j - h];
else
break;
}
arr[j] = e;
}
}
}
// 希尔排序2
public static void sort2(int[] arr) {
int n = arr.length;
// 计算 increment sequence: 1, 4, 13, 40, 121, 364, 1093...
int h = 1;
while (h < n / 3) h = 3 * h + 1;
System.out.println(h);
while (h >= 1) {
// h-sort the array
for (int i = h; i < n; i++) {
// 对 arr[i], arr[i-h], arr[i-2*h], arr[i-3*h]... 使用插入排序
int e = arr[i];
int j = i;
for (; j >= h && e < arr[j - h]; j -= h)
arr[j] = arr[j - h];
arr[j] = e;
}
h /= 3;
}
}
~~~
**算法分析**
对于大规模的数组,插入排序很慢,因为它只能交换相邻的元素,每次只能将逆序数量减少 1。
希尔排序的出现就是为了改进插入排序的这种局限性,它通过交换不相邻的元素,每次可以将逆序数量减少大于 1。
希尔排序使用插入排序对间隔 h 的序列进行排序。通过不断减小 h,最后令 h=1,就可以使得整个数组是有序的。
### 5\. 归并排序(Merge Sort)
归并排序的思想是将数组分成两部分,分别进行排序,然后归并起来。把长度为n的输入序列分成两个长度为n/2的子序列;对这两个子序列分别采用归并排序;将两个排序好的子序列合并成一个最终的排序序列。
[![](https://camo.githubusercontent.com/207472a2903254f1706938a09fc3fa854ad23273/68747470733a2f2f696d61676573323031372e636e626c6f67732e636f6d2f626c6f672f3834393538392f3230313731302f3834393538392d32303137313031353233303535373034332d33373337353031302e676966)](https://camo.githubusercontent.com/207472a2903254f1706938a09fc3fa854ad23273/68747470733a2f2f696d61676573323031372e636e626c6f67732e636f6d2f626c6f672f3834393538392f3230313731302f3834393538392d32303137313031353233303535373034332d33373337353031302e676966)
**代码实现**
> 1.归并方法
>
> 归并方法将数组中两个已经排序的部分归并成一个。
~~~java
private static void sort(int[] arr) {
__MergeSort(arr, 0, arr.length - 1);
}
private static void __MergeSort(int[] arr, int l, int r) {
if (l >= r)
return;
int mid = (l + r) / 2;
__MergeSort(arr, l, mid);
__MergeSort(arr, mid + 1, r);
merge(arr, l, mid, r);
}
// 将arr[l...mid]和arr[mid+1...r]两部分进行归并
private static void merge(int[] arr, int l, int mid, int r) {
int[] aux = Arrays.copyOfRange(arr, l, r + 1);
// 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1
int i = l, j = mid + 1;
for (int k = l; k <= r; k++) {
if (i > mid) { // 如果左半部分元素已经全部处理完毕
arr[k] = aux[j - l];
j++;
} else if (j > r) { // 如果右半部分元素已经全部处理完毕
arr[k] = aux[i - l];
i++;
} else if (aux[i - l] < aux[j - l]) { // 左半部分所指元素 < 右半部分所指元素
arr[k] = aux[i - l];
i++;
} else { // 左半部分所指元素 >= 右半部分所指元素
arr[k] = aux[j - l];
j++;
}
}
}
~~~
> 2.自底向上归并排序
~~~java
private static void sort(int[] arr) {
int N = arr.length;
int[] aux = new int[N];
for (int sz = 1; sz < N; sz += sz)
for (int i = 0; i + sz < N; i += sz + sz)
merge(arr, i, i + sz - 1, Math.min(i + sz + sz - 1, N - 1));
}
~~~
### 6\. 快速排序(Quick Sort)
快速排序可以说是20世纪最伟大的算法之一了。相信都有所耳闻,它的速度也正如它的名字那样,是一个非常快的算法了。当然它也后期经过了不断的改进和优化,才被公认为是一个值得信任的非常优秀的算法。
[![](https://camo.githubusercontent.com/4a10c066718dc2584d2b1682f8b2085920db0123/68747470733a2f2f696d61676573323031372e636e626c6f67732e636f6d2f626c6f672f3834393538392f3230313731302f3834393538392d32303137313031353233303933363337312d313431333532333431322e676966)](https://camo.githubusercontent.com/4a10c066718dc2584d2b1682f8b2085920db0123/68747470733a2f2f696d61676573323031372e636e626c6f67732e636f6d2f626c6f672f3834393538392f3230313731302f3834393538392d32303137313031353233303933363337312d313431333532333431322e676966)
**代码实现**
#### 1\. 普通快速排序
~~~java
// 递归使用快速排序,对arr[l...r]的范围进行排序
public static void QuickSort(int[] arr,int l,int r){
if(l>=r)
return;
int p = partition(arr,l,r);
QuickSort(arr,l,p-1);
QuickSort(arr,p+1,r);
}
// 将数组通过p分割成两部分
// 对arr[l...r]部分进行partition操作
// 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]
public static int partition(int[] arr, int l, int r) {
swap(arr, l, (int) (Math.random() % (r - l + 1)) + l); // 加入这一行变成随机快速排序
int v = arr[l];
int j = l;
for(int i = j +1;i<=r;i++){
if(arr[i] < v){
j++;
swap(arr,i,j);
}
}
swap(arr,l,j);
return j;
}
public static void swap(int[] arr,int i,int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
~~~
快速排序是原地排序,不需要辅助数组,但是递归调用需要辅助栈。
快速排序最好的情况下是每次都正好能将数组对半分,这样递归调用次数才是最少的。这种情况下比较次数为 CN=2CN/2+N,复杂度为 O(NlogN)。
最坏的情况下,第一次从最小的元素切分,第二次从第二小的元素切分,如此这般。因此最坏的情况下需要比较 N2/2。为了防止数组最开始就是有序的,在进行快速排序时需要随机打乱数组。
#### 2\. 双路快速排序
若果数组中含有大量重复的元素,则partition很可能把数组划分成两个及其不平衡的两部分,时间复杂度退化成O(n²)。这时候应该把小于v和大于v放在数组两端。
[![](https://github.com/frank-lam/fullstack-tutorial/raw/master/notes/pics/partition2.jpg)](https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/pics/partition2.jpg)
~~~java
// 双路快速排序的partition
// 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]
private static int partition(int[] arr, int l, int r) {
// 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot
// swap(arr, l, (int) (Math.random() % (r - l + 1)) + l);
int v = arr[l];
// arr[l+1...i) <= v; arr(j...r] >= v
int i = l + 1, j = r;
while (true) {
// 注意这里的边界, arr[i].compareTo(v) < 0, 不能是arr[i].compareTo(v) <= 0
// 思考一下为什么?
while (i <= r && arr[i] < v)
i++;
// 注意这里的边界, arr[j].compareTo(v) > 0, 不能是arr[j].compareTo(v) >= 0
// 思考一下为什么?
while (j >= l + 1 && arr[j] > v)
j--;
// 对于上面的两个边界的设定, 有的同学在课程的问答区有很好的回答:)
// 大家可以参考: http://coding.imooc.com/learn/questiondetail/4920.html
if (i > j)
break;
swap(arr, i, j);
i++;
j--;
}
swap(arr, l, j);
return j;
}
// 递归使用快速排序,对arr[l...r]的范围进行排序
private static void QuickSort2Ways(int[] arr, int l, int r) {
// 对于小规模数组, 使用插入排序
if (l >= r) return;
int p = partition(arr, l, r);
QuickSort2Ways(arr, l, p - 1);
QuickSort2Ways(arr, p + 1, r);
}
~~~
#### 3\. 三路快速排序
数组分成三个部分,大于v 等于v 小于v
在具有大量重复键值对的情况下使用三路快排
[![](https://github.com/frank-lam/fullstack-tutorial/raw/master/notes/pics/partition3.jpg)](https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/pics/partition3.jpg)
~~~java
// 递归使用快速排序,对arr[l...r]的范围进行排序
private static void QuickSort3Ways(int[] arr, int l, int r){
// 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot
swap( arr, l, (int)(Math.random()*(r-l+1)) + l );
int v = arr[l];
int lt = l; // arr[l+1...lt] < v
int gt = r + 1; // arr[gt...r] > v
int i = l+1; // arr[lt+1...i) == v
while( i < gt ){
if( arr[i] < v){
swap( arr, i, lt+1);
i ++;
lt ++;
}
else if( arr[i] > v ){
swap( arr, i, gt-1);
gt --;
}
else{ // arr[i] == v
i ++;
}
}
swap( arr, l, lt );
QuickSort3Ways(arr, l, lt-1);
QuickSort3Ways(arr, gt, r);
}
~~~
### 7\. 堆排序(Heap Sort)
#### 1\. 堆
堆的某个节点的值总是大于等于子节点的值,并且堆是一颗完全二叉树。
堆可以用数组来表示,因为堆是完全二叉树,而完全二叉树很容易就存储在数组中。位置 k 的节点的父节点位置为 k/2,而它的两个子节点的位置分别为 2k 和 2k+1。这里不使用数组索引为 0 的位置,是为了更清晰地描述节点的位置关系。
[![](https://github.com/frank-lam/fullstack-tutorial/raw/master/notes/pics/heap.png)](https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/pics/heap.png)
#### 2\. 上浮和下沉
在堆中,当一个节点比父节点大,那么需要交换这个两个节点。交换后还可能比它新的父节点大,因此需要不断地进行比较和交换操作,把这种操作称为**上浮(ShiftUp)**。
[![](https://github.com/frank-lam/fullstack-tutorial/raw/master/notes/pics/shiftup_heap.png)](https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/pics/shiftup_heap.png)
~~~java
private void shiftUp(int k){
while( k > 1 && data[k/2] < data[k])){
swap(k, k/2);
k /= 2;
}
}
~~~
类似地,当一个节点比子节点来得小,也需要不断地向下进行比较和交换操作,把这种操作称为**下沉(Shift Down)**。一个节点如果有两个子节点,应当与两个子节点中最大那么节点进行交换。
[![](https://github.com/frank-lam/fullstack-tutorial/raw/master/notes/pics/shiftdown_heap.png)](https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/pics/shiftdown_heap.png)
~~~java
private void shiftDown(int k){
while( 2*k <= count ){ // 当前结点有左孩子
int j = 2*k; // 在此轮循环中,data[k]和data[j]交换位置
if( j+1 <= count && data[j+1] > data[j] )
j ++;
// data[j] 是 data[2*k]和data[2*k+1]中的最大值
if( data[k] >= data[j] )
break;
swap(k, j);
k = j;
}
}
~~~
#### 3.插入元素
将新元素放到数组末尾,然后上浮到合适的位置。
~~~java
// 向最大堆中插入一个新的元素 item
public void insert(Item item){
assert count + 1 <= capacity;
data[count+1] = item;
count ++;
shiftUp(count);
}
~~~
#### 4\. 删除最大元素
~~~java
// 从最大堆中取出堆顶元素, 即堆中所存储的最大数据
public Item extractMax(){
assert count > 0;
Item ret = data[1];
swap( 1 , count );
count --;
shiftDown(1);
return ret;
}
~~~
#### 5\. 堆排序
由于堆可以很容易得到最大的元素并删除它,不断地进行这种操作可以得到一个递减序列。如果把最大元素和当前堆中数组的最后一个元素交换位置,并且不删除它,那么就可以得到一个从尾到头的递减序列,从正向来看就是一个递增序列。因此很容易使用堆来进行排序。并且堆排序是原地排序,不占用额外空间。
~~~java
// 不使用一个额外的最大堆, 直接在原数组上进行原地的堆排序
public class HeapSort {
// 对整个arr数组使用HeapSort1排序
// HeapSort1, 将所有的元素依次添加到堆中, 在将所有元素从堆中依次取出来, 即完成了排序
// 无论是创建堆的过程, 还是从堆中依次取出元素的过程, 时间复杂度均为O(nlogn)
// 整个堆排序的整体时间复杂度为O(nlogn)
public static void sort1(Comparable[] arr){
int n = arr.length;
MaxHeap<Comparable> maxHeap = new MaxHeap<Comparable>(n);
for( int i = 0 ; i < n ; i ++ )
maxHeap.insert(arr[i]);
for( int i = n-1 ; i >= 0 ; i -- )
arr[i] = maxHeap.extractMax();
}
// 只通过shiftDown操作进行排序
public static void sort2(Comparable[] arr){
int n = arr.length;
// 注意,此时我们的堆是从0开始索引的
// 从(最后一个元素的索引-1)/2开始
// 最后一个元素的索引 = n-1
for( int i = (n-1-1)/2 ; i >= 0 ; i -- )
shiftDown2(arr, n, i);
for( int i = n-1; i > 0 ; i-- ){ // 这个的目的是让序列从小到大排序
swap( arr, 0, i);
shiftDown2(arr, i, 0);
}
}
// 交换堆中索引为i和j的两个元素
private static void swap(Object[] arr, int i, int j){
Object t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
// 原始的shiftDown过程
private static void shiftDown(Comparable[] arr, int n, int k){
while( 2*k+1 < n ){
int j = 2*k+1;
if( j+1 < n && arr[j+1].compareTo(arr[j]) > 0 )
j += 1;
if( arr[k].compareTo(arr[j]) >= 0 )break;
swap( arr, k, j);
k = j;
}
}
// 优化的shiftDown过程, 使用赋值的方式取代不断的swap,
// 该优化思想和我们之前对插入排序进行优化的思路是一致的
private static void shiftDown2(Comparable[] arr, int n, int k){
Comparable e = arr[k];
while( 2*k+1 < n ){
int j = 2*k+1;
if( j+1 < n && arr[j+1].compareTo(arr[j]) > 0 )
j += 1;
if( e.compareTo(arr[j]) >= 0 )
break;
arr[k] = arr[j];
k = j;
}
arr[k] = e;
}
// 测试 HeapSort
public static void main(String[] args) {
Integer[] arr = {10, 91, 8, 7, 6, 5, 4, 3, 2, 1};
HeapSort.sort2(arr);
PrintHelper.printArray(arr);
}
}
~~~
#### 6\. 堆排序的应用——Top K问题
例如,有1亿个浮点数,如何找出其中最大的10000个?(B326)
###8\. 计数排序
[https://www.cnblogs.com/freedom314/p/5847092.html](https://www.cnblogs.com/freedom314/p/5847092.html)
### 9\. 排序算法总结
| | 平均时间复杂度 | 原地排序 | 额外空间 | 稳定排序 |
| :-: | :-: | :-: | :-: | :-: |
| 插入排序 | O(n^2) | √ | O(1) | √ |
| 归并排序 | O(nlogn) | × | O(n) | √ |
| 快速排序 | O(nlogn) | √ | O(logn) | × |
| 堆排序 | O(nlogn) | √ | O(1) | × |
稳定排序:对于相等的元素,在排序后,原来靠前的元素依然靠前。相等元素的相对位置没有发生变化。
~~~java
// 可以通过⾃自定义⽐比较函数,让排序算法不不存在稳定性的问题。
bool operator<(const Student& otherStudent){
return score != otherStudent.score ?
score > otherStudent.score :
name < otherStudent.name;
}
~~~
[![](https://github.com/frank-lam/fullstack-tutorial/raw/master/notes/pics/sort_algorithm_analyze.png)](https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/pics/sort_algorithm_analyze.png)
- 一.JVM
- 1.1 java代码是怎么运行的
- 1.2 JVM的内存区域
- 1.3 JVM运行时内存
- 1.4 JVM内存分配策略
- 1.5 JVM类加载机制与对象的生命周期
- 1.6 常用的垃圾回收算法
- 1.7 JVM垃圾收集器
- 1.8 CMS垃圾收集器
- 1.9 G1垃圾收集器
- 2.面试相关文章
- 2.1 可能是把Java内存区域讲得最清楚的一篇文章
- 2.0 GC调优参数
- 2.1GC排查系列
- 2.2 内存泄漏和内存溢出
- 2.2.3 深入理解JVM-hotspot虚拟机对象探秘
- 1.10 并发的可达性分析相关问题
- 二.Java集合架构
- 1.ArrayList深入源码分析
- 2.Vector深入源码分析
- 3.LinkedList深入源码分析
- 4.HashMap深入源码分析
- 5.ConcurrentHashMap深入源码分析
- 6.HashSet,LinkedHashSet 和 LinkedHashMap
- 7.容器中的设计模式
- 8.集合架构之面试指南
- 9.TreeSet和TreeMap
- 三.Java基础
- 1.基础概念
- 1.1 Java程序初始化的顺序是怎么样的
- 1.2 Java和C++的区别
- 1.3 反射
- 1.4 注解
- 1.5 泛型
- 1.6 字节与字符的区别以及访问修饰符
- 1.7 深拷贝与浅拷贝
- 1.8 字符串常量池
- 2.面向对象
- 3.关键字
- 4.基本数据类型与运算
- 5.字符串与数组
- 6.异常处理
- 7.Object 通用方法
- 8.Java8
- 8.1 Java 8 Tutorial
- 8.2 Java 8 数据流(Stream)
- 8.3 Java 8 并发教程:线程和执行器
- 8.4 Java 8 并发教程:同步和锁
- 8.5 Java 8 并发教程:原子变量和 ConcurrentMap
- 8.6 Java 8 API 示例:字符串、数值、算术和文件
- 8.7 在 Java 8 中避免 Null 检查
- 8.8 使用 Intellij IDEA 解决 Java 8 的数据流问题
- 四.Java 并发编程
- 1.线程的实现/创建
- 2.线程生命周期/状态转换
- 3.线程池
- 4.线程中的协作、中断
- 5.Java锁
- 5.1 乐观锁、悲观锁和自旋锁
- 5.2 Synchronized
- 5.3 ReentrantLock
- 5.4 公平锁和非公平锁
- 5.3.1 说说ReentrantLock的实现原理,以及ReentrantLock的核心源码是如何实现的?
- 5.5 锁优化和升级
- 6.多线程的上下文切换
- 7.死锁的产生和解决
- 8.J.U.C(java.util.concurrent)
- 0.简化版(快速复习用)
- 9.锁优化
- 10.Java 内存模型(JMM)
- 11.ThreadLocal详解
- 12 CAS
- 13.AQS
- 0.ArrayBlockingQueue和LinkedBlockingQueue的实现原理
- 1.DelayQueue的实现原理
- 14.Thread.join()实现原理
- 15.PriorityQueue 的特性和原理
- 16.CyclicBarrier的实际使用场景
- 五.Java I/O NIO
- 1.I/O模型简述
- 2.Java NIO之缓冲区
- 3.JAVA NIO之文件通道
- 4.Java NIO之套接字通道
- 5.Java NIO之选择器
- 6.基于 Java NIO 实现简单的 HTTP 服务器
- 7.BIO-NIO-AIO
- 8.netty(一)
- 9.NIO面试题
- 六.Java设计模式
- 1.单例模式
- 2.策略模式
- 3.模板方法
- 4.适配器模式
- 5.简单工厂
- 6.门面模式
- 7.代理模式
- 七.数据结构和算法
- 1.什么是红黑树
- 2.二叉树
- 2.1 二叉树的前序、中序、后序遍历
- 3.排序算法汇总
- 4.java实现链表及链表的重用操作
- 4.1算法题-链表反转
- 5.图的概述
- 6.常见的几道字符串算法题
- 7.几道常见的链表算法题
- 8.leetcode常见算法题1
- 9.LRU缓存策略
- 10.二进制及位运算
- 10.1.二进制和十进制转换
- 10.2.位运算
- 11.常见链表算法题
- 12.算法好文推荐
- 13.跳表
- 八.Spring 全家桶
- 1.Spring IOC
- 2.Spring AOP
- 3.Spring 事务管理
- 4.SpringMVC 运行流程和手动实现
- 0.Spring 核心技术
- 5.spring如何解决循环依赖问题
- 6.springboot自动装配原理
- 7.Spring中的循环依赖解决机制中,为什么要三级缓存,用二级缓存不够吗
- 8.beanFactory和factoryBean有什么区别
- 九.数据库
- 1.mybatis
- 1.1 MyBatis-# 与 $ 区别以及 sql 预编译
- Mybatis系列1-Configuration
- Mybatis系列2-SQL执行过程
- Mybatis系列3-之SqlSession
- Mybatis系列4-之Executor
- Mybatis系列5-StatementHandler
- Mybatis系列6-MappedStatement
- Mybatis系列7-参数设置揭秘(ParameterHandler)
- Mybatis系列8-缓存机制
- 2.浅谈聚簇索引和非聚簇索引的区别
- 3.mysql 证明为什么用limit时,offset很大会影响性能
- 4.MySQL中的索引
- 5.数据库索引2
- 6.面试题收集
- 7.MySQL行锁、表锁、间隙锁详解
- 8.数据库MVCC详解
- 9.一条SQL查询语句是如何执行的
- 10.MySQL 的 crash-safe 原理解析
- 11.MySQL 性能优化神器 Explain 使用分析
- 12.mysql中,一条update语句执行的过程是怎么样的?期间用到了mysql的哪些log,分别有什么作用
- 十.Redis
- 0.快速复习回顾Redis
- 1.通俗易懂的Redis数据结构基础教程
- 2.分布式锁(一)
- 3.分布式锁(二)
- 4.延时队列
- 5.位图Bitmaps
- 6.Bitmaps(位图)的使用
- 7.Scan
- 8.redis缓存雪崩、缓存击穿、缓存穿透
- 9.Redis为什么是单线程、及高并发快的3大原因详解
- 10.布隆过滤器你值得拥有的开发利器
- 11.Redis哨兵、复制、集群的设计原理与区别
- 12.redis的IO多路复用
- 13.相关redis面试题
- 14.redis集群
- 十一.中间件
- 1.RabbitMQ
- 1.1 RabbitMQ实战,hello world
- 1.2 RabbitMQ 实战,工作队列
- 1.3 RabbitMQ 实战, 发布订阅
- 1.4 RabbitMQ 实战,路由
- 1.5 RabbitMQ 实战,主题
- 1.6 Spring AMQP 的 AMQP 抽象
- 1.7 Spring AMQP 实战 – 整合 RabbitMQ 发送邮件
- 1.8 RabbitMQ 的消息持久化与 Spring AMQP 的实现剖析
- 1.9 RabbitMQ必备核心知识
- 2.RocketMQ 的几个简单问题与答案
- 2.Kafka
- 2.1 kafka 基础概念和术语
- 2.2 Kafka的重平衡(Rebalance)
- 2.3.kafka日志机制
- 2.4 kafka是pull还是push的方式传递消息的?
- 2.5 Kafka的数据处理流程
- 2.6 Kafka的脑裂预防和处理机制
- 2.7 Kafka中partition副本的Leader选举机制
- 2.8 如果Leader挂了的时候,follower没来得及同步,是否会出现数据不一致
- 2.9 kafka的partition副本是否会出现脑裂情况
- 十二.Zookeeper
- 0.什么是Zookeeper(漫画)
- 1.使用docker安装Zookeeper伪集群
- 3.ZooKeeper-Plus
- 4.zk实现分布式锁
- 5.ZooKeeper之Watcher机制
- 6.Zookeeper之选举及数据一致性
- 十三.计算机网络
- 1.进制转换:二进制、八进制、十六进制、十进制之间的转换
- 2.位运算
- 3.计算机网络面试题汇总1
- 十四.Docker
- 100.面试题收集合集
- 1.美团面试常见问题总结
- 2.b站部分面试题
- 3.比心面试题
- 4.腾讯面试题
- 5.哈罗部分面试
- 6.笔记
- 十五.Storm
- 1.Storm和流处理简介
- 2.Storm 核心概念详解
- 3.Storm 单机版本环境搭建
- 4.Storm 集群环境搭建
- 5.Storm 编程模型详解
- 6.Storm 项目三种打包方式对比分析
- 7.Storm 集成 Redis 详解
- 8.Storm 集成 HDFS 和 HBase
- 9.Storm 集成 Kafka
- 十六.Elasticsearch
- 1.初识ElasticSearch
- 2.文档基本CRUD、集群健康检查
- 3.shard&replica
- 4.document核心元数据解析及ES的并发控制
- 5.document的批量操作及数据路由原理
- 6.倒排索引
- 十七.分布式相关
- 1.分布式事务解决方案一网打尽
- 2.关于xxx怎么保证高可用的问题
- 3.一致性hash原理与实现
- 4.微服务注册中心 Nacos 比 Eureka的优势
- 5.Raft 协议算法
- 6.为什么微服务架构中需要网关
- 0.CAP与BASE理论
- 十八.Dubbo
- 1.快速掌握Dubbo常规应用
- 2.Dubbo应用进阶
- 3.Dubbo调用模块详解
- 4.Dubbo调用模块源码分析
- 6.Dubbo协议模块