十大排序算法(中):冒泡排序,快速排序(递归和非递归)、归并排序(递归和非递归)

news2024/11/26 4:05:02

这篇文章,我们接着来讲剩下的排序算法:冒泡排序,快速排序(递归和非递归)、归并排序(递归和非递归)

目录

    • 3.3 交换排序
      • 3.3.1 冒泡排序
      • 3.3.2 快速排序
        • 递归
        • 优化
        • 非递归
        • 优化
    • 3.4 归并排序
      • 3.4.1 递归
      • 3.4.2 非递归
      • 3.4.3 海量数据的排序问题
  • 4. 排序算法复杂度及稳定性分析

3.3 交换排序

中心思想:

交换就是指根据序列中的两个元素的比较结果来对换这两个元素在序列中的位置,特点就是:将值较大的元素向序列尾部移动,将值较小的元素向序列前部移动

3.3.1 冒泡排序

public static void bubbleSort(int[] arr) {
    for(int i = 0; i <arr.length; i++) {
        boolean flag = false;
        for (int j = 0; j < arr.length - 1 - i; j++) {
            if(arr[j] > arr[j + 1]) {
                swap(arr, j, j + 1);
                flag = true;
            }
        }
        //优化,可加可不加
        if(!flag) {
            break;
        }
    }
}

特点

  1. 时间复杂度:不加优化O(N2),对数据不敏感;加了优化最好情况会达到O(N),对数据敏感
  2. 空间复杂度:O(1)
  3. 稳定

在这里插入图片描述

3.3.2 快速排序

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:
任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

递归

本篇文章是以左边元素作为基准值。

根据基本思想,我们可以列出快排的主框架

// 假设按照升序对array数组中[start, end]区间中的元素进行排序
public static void quickSort(int[] arr) {
    quick(arr, 0, arr.length - 1);
}
private static void quick(int[] arr, int start, int end) {
    if(start >= end) {
        return;
    }
    // 按照基准值对array数组的 [start, end]区间中的元素进行划分
    int pivot = partition(arr, start, end);
    // 划分成功后以pivot为边界形成了左右两部分 [start, div - 1] 和 [div + 1, end]
    // 递归排[start, div - 1]
    quick(arr, start, pivot - 1);
    // 递归排[div+1, end]
    quick(arr, pivot + 1, end);
}

与二叉树前序遍历规则非常像,我们在写递归框架时可想想二叉树前序遍历规则即可快速写出来,后续只需分析如何按照基准值来对区间中数据进行划分的方式即可。

将区间按照基准值划分为左右两半部分的常见方式有:

  1. Hoare版
    以 升序、基准值在左边为例
  • 先从右边开始遍历,遍历到比基准值小的数,停下
  • 再遍历左边的数,遇到比基准值大的数,停下
  • 交换这两个数
  • 循环以上三步,直到left和right相遇停下,此时,left和right相遇的位置就是基准值排好序的位置
  • 然后将基准值与left(或者right)下标的值进行交换,完成这个数的排序。
  • 然后返回基准值排好序的位置
    在这里插入图片描述
//基准值是左边的元素
private static int partition(int[] arr, int left, int right) {
    int index = left;//记录基准值的位置
    int n = arr[left];
    while(left < right) {
        //最先从右边开始比较
        //找出小于基准值的数
        //n <= arr[right] 必须取等于
        while(left < right && n <= arr[right]) {
            right--;
        }
        //找出大于基准值的数
        while(left < right && n >= arr[left]) {
            left++;
        }
        swap(arr, left, right);
    }
    //循环完毕之后,left和right的左边是小于
    swap(arr, index, left);//把基准值放在自己的位置上
    return left;
}

//基准值是右边的元素
private static int partition2(int[] arr, int left, int right) {
    int index = right;
    int n = arr[right];
    while(left < right) {
        while(left < right && n >= arr[left]) {
            left++;
        }
        while(left < right && n <= arr[right]) {
            right--;
        }
        swap(arr, left, right);
    }
    swap(arr, index, left);
    return left;
}

注意
如果基准值是左边的元素,那么一定要最先从右边开始比较,从右边开始移动,否则排序会出错;
反之,最先从左边开始比较,从左边开始移动

  1. 挖坑法
    以 升序、基准值在左边为例
  • 可以把基准值的位置想象成一个坑
  • 先遍历右边的元素,遇到比基准值小的元素,就把他放入left坑里,自己的地方形成个坑
  • 在遍历左边的元素,遇到比基准值大的元素,就把他放入right坑里,自己的地方形成个坑
  • 直到left和right相遇,然后把基准值放入left(right)坑里
  • 完成基准值的排序,返回基准值排好序的位置
    在这里插入图片描述
private static int partition3(int[] arr, int left, int right) {
    int n = arr[left];
    while(left < right) {
        while(left < right && n <= arr[right]) {
            right--;
        }
        arr[left] = arr[right];//将右边小于n的数放在左边
        while(left < right && n >= arr[left]) {
            left++;
        }
        arr[right] = arr[left];//将左边大于n的数放在右边
    }
    arr[right] = n;//基准值
    return left;//返回基准值下标
}
  1. 前后指针

写法一:

  • 初始时,prev指针指向序列开头,cur指针指向prev指针后一个位置.
  • 当cur值小于基准值key并且++prev的值不等于cur值的时候,交换cur和prev
  • 当cur的值大于等于key时,cur++,prev不动
  • 当cur小于基准值key并且++prev的值等于cur值的时候,cur++
  • 当cur大于right后结束循环,交换key和prev值

注意prev什么时候动,什么时候不动。

在这里插入图片描述

	//写法一
	private static int partition(int[] array, int left, int right) {
		int key = array[left];
	    int prev = left;
	    int cur = left+1;
	    while (cur <= right) {
	        if(array[cur] < key && array[++prev] != array[cur]) {
	            swap(array,cur,prev);
	        }
	        cur++;
	    }
	    swap(array,prev,left);
	    return prev;
	}
	
	//写法二
	private static int partition(int[] array, int left, int right) {
	    int d = left + 1;
	    int pivot = array[left];
	    for (int i = left + 1; i <= right; i++) {
	        if (array[i] < pivot) {
	            swap(array, i, d);
	            d++;
	        }
	    }
	    swap(array, d - 1, left);
	    return d - 1;
	}

这三种方法的常用顺序是:

挖坑 > Hoare > 前后指针

当我们用正序的10_0000数据测试时,发生了栈溢出错误,因为正序和逆序都会形成单分支的树,递归调用太多了。而乱序不会。

在这里插入图片描述

在这里插入图片描述

那我们该如何优化呢?

优化

有两个方法:

  1. 三数取中法。

    找到最左边,中间,最右边这三个数的中间值,将他和最左边或者最右边的数进行交换,尽可能将数据打乱,不让他形成单分支的树。

    举个例子:

    在这里插入图片描述

    private static int threeNum(int[] arr, int left, int right) {
        int mid = (left + right) / 2;
        if(arr[left] < arr[right]) {
            if(arr[mid] < arr[left]) {
                return left;
            } else if (arr[mid] > arr[right]) {
                return right;
            } else {
                return mid;
            }
        } else {
            if (arr[mid] < arr[right]) {
                return right;
            } else if (arr[mid] > arr[left]) {
                return left;
            } else {
                return mid;
            }
        }
    }
    

    当我们用100_0000的数据进行测试的时候,正序和乱序不会发生栈溢出错误了,但是逆序依旧会。因为逆序递归的次数比顺序递归的次数多了一倍。

    在这里插入图片描述

    在这里插入图片描述

  2. 递归到小的子区间时,可以考虑使用插入排序。根据二叉树的特点可知,二叉树的节点大多集中在后面几层,像下面这棵树

在这里插入图片描述

它4/5的结点都在最后两层,随着二叉树的层数变多,递归调用的次数就会越来越多,就可能会形成栈溢出,但是数据也会变得越来越有序,所以我们可以试着把后面数据排序变成插入排序,这样不仅可以节省空间,也能减少时间。

private static void insertSort2(int[] array, int left, int right) {
    for (int i = left; i <= right; i++) {
        int n = array[i];
        int j = i - 1;
        for (; j >= left; j--) {
            if(array[j] > n) {
                array[j + 1] = array[j];
            } else {
                break;
            }
        }
        array[j + 1] = n;
    }
}

加上这两种优化后,代码为:

public static void quickSort(int[] arr) {
    quick(arr, 0, arr.length - 1);
}
private static void insertSort2(int[] array, int left, int right) {
    for (int i = left; i <= right; i++) {
        int n = array[i];
        int j = i - 1;
        for (; j >= left; j--) {
            if(array[j] > n) {
                array[j + 1] = array[j];
            } else {
                break;
            }
        }
        array[j + 1] = n;
    }
}
private static void quick(int[] arr, int start, int end) {
    if(start >= end) {
        return;
    }

    if(end - start + 1 <= 20) {
        //直接插入排序
        insertSort2(arr, start, end);
        return;
    }

    //三数取中法
    int midIndex = threeNum(arr, start, end);
    swap(arr, start, midIndex);
    
    int pivot = partition3(arr, start, end);//挖坑法
    quick(arr, start, pivot - 1);
    quick(arr, pivot + 1, end);
}
private static int partition3(int[] arr, int left, int right) {
    int n = arr[left];
    while(left < right) {
        while(left < right && n <= arr[right]) {
            right--;
        }
        arr[left] = arr[right];
        while(left < right && n >= arr[left]) {
            left++;
        }
        arr[right] = arr[left];
    }
    arr[right] = n;
    return left;
}
private static int threeNum(int[] arr, int left, int right) {
    int mid = (left + right) / 2;
    if(arr[left] < arr[right]) {
        if(arr[mid] < arr[left]) {
            return left;
        } else if (arr[mid] > arr[right]) {
            return right;
        } else {
            return mid;
        }
    } else {
        if (arr[mid] < arr[right]) {
            return right;
        } else if (arr[mid] > arr[left]) {
            return left;
        } else {
            return mid;
        }
    }
}

测试100_0000的数据,逆序还是会栈溢出,直到数据到5_0000才没有溢出,为什么逆序递归的次数会比顺序多一倍,我现在也没有搞清楚,读者可以说出你自己的想法,欢迎在评论区留言!

在这里插入图片描述

特点

  1. 时间复杂度:好的情况下(完全二叉树)O(N*logN):一共有log(N)层,每层都会遍历n个数据;

    在这里插入图片描述

    最坏情况下(顺序或者逆序,单分支):O(N^2)

    一般统一是O(N*logN)

  2. 空间复杂度:好的情况下O(logN);最坏情况下O(N)

  3. 稳定性:不稳定

非递归

当数据量非常大的时候,递归快排一定会发生栈溢出,那么我们可以用非递归的方法来实现快排,从而减少函数开辟所占用的内存。

那么非递归该如何实现呢?他的中心思想又是什么呢?

先说用栈来实现:

把数队的start和end下标放入栈中,当栈不为空的时候,也就是说数据还没有都排完序,去找基准,将基准的数据放入他自己的位置后,放入 以基准为分界线的 左右两边数队的start和end坐标。

这里要注意的是,当基准的前面或者是后面只有一个数据的时候,就不用放了,因为这个数已经有序了。

然后再弹出一组数队的坐标,开始新一轮的找基准,以此类推,直到栈为空,说明这组数已经有序。

图示:

在这里插入图片描述

public static void quickSort2(int[] arr) {
    Stack<Integer> stack = new Stack<>();
    int start = 0;
    int end = arr.length - 1;

    int pivot = partition3(arr, start, end);
    //pivot的左边和右边的元素个数大于一个再放入栈中,否则没有意义。
    if (pivot > start + 1) {
        stack.push(start);
        stack.push(pivot - 1);
     }
    if (pivot < end - 1) {
        stack.push(pivot + 1);
        stack.push(end);
     }
    while (!stack.empty()) {
        //赋值新的
        end = stack.pop();
        start = stack.pop();
        pivot = partition3(arr,start,end);
        if (pivot > start + 1) {
            stack.push(start);
            stack.push(pivot - 1);
         }
        if (pivot < end - 1) {
            stack.push(pivot + 1);
            stack.push(end);
         }
     }
}

100_0000数字测试:

在这里插入图片描述

很明显,在100_0000数据下,用非递归的方法不会造成栈溢出,但是耗费的时间还是相当多的。

优化

于是我们对非递归代码进行了优化

public static void quickSort2(int[] arr) {
    Stack<Integer> stack = new Stack<>();
    int start = 0;
    int end = arr.length - 1;

    if(end - start + 1 <= 20) {
        //直接插入排序
        insertSort2(arr, start, end);
        return;
    }

    //三数取中法
    int midIndex = threeNum(arr, start, end);
    swap(arr, start, midIndex);

    int pivot = partition3(arr, start, end);
    if (pivot > start + 1) {
        stack.push(start);
        stack.push(pivot - 1);
    }
    if (pivot < end - 1) {
        stack.push(pivot + 1);
        stack.push(end);
    }
    while (!stack.empty()) {
        end = stack.pop();
        start = stack.pop();
        if(end - start + 1 <= 20) {
            //直接插入排序
            insertSort2(arr, start, end);
            //这里不能向上面一样return,因为如果直接return了的话,会导致其他区间的数值还没有被排序,程序就直接结束了,这样只会使一小区间被排序
        } else {
            //三数取中法
            midIndex = threeNum(arr, start, end);
            swap(arr, start, midIndex);
            pivot = partition3(arr,start,end);
            if (pivot > start + 1) {
                stack.push(start);
                stack.push(pivot - 1);
            }
            if (pivot < end - 1) {
                stack.push(pivot + 1);
                stack.push(end);
            }
        }
    }
}

测试100_0000的数据:

在这里插入图片描述

很明显!快了很多!占用的空间也变少了!

3.4 归并排序

归并排序的基本思想:

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤:

3.4.1 递归

归并排序核心步骤:

在这里插入图片描述

  1. 分解

    在这里插入图片描述

    每组分成[l,m]和[m+1,r]两部分,然后再递归这两部分。

    当l>=r的时候,"递"停止,"归"开始,合并开始。

    private static void mergeFunc(int[] arr, int left, int right) {
        //递归的结束条件
        if(left >= right) {
            return;
        }
        int mid = (left + right) / 2;
        //分组递归
        mergeFunc(arr, left, mid);
        mergeFunc(arr, mid + 1, right);
    
        //合并
        merge(arr, left, mid, right);
    }
    
  2. 合并

    当递归到只有一个数的时候,“递”就结束了,开始“归”,合并是在数的左右两边的数都遍历完了之后才开始的。

    以下面的图片为例,大家不要和我上面的大图搞混了,我上面的大图里的l,r,m是递的时候传的参数,而不是当前方法内的left和right和mid的值。

    在这里插入图片描述

    以这个为例,先申请一个大小为l - r + 1的新数组,然后定义s1,e1,s2,e2.

    int s1 = left;
    int e1 = mid;
    int s2 = mid + 1;
    int e2 = right;
    int[] tmpArr = new int[right - left + 1];
    

    在这里插入图片描述

怎样往新数组里面按顺序地放数字呢?

我们可以比较arr[s1]arr[s2]的值,把小的值放入数组,如上图,arr[s1] < arr[s2],所以我们将arr[s1]放入数组,s1++,循环以上步骤,当s1 > e1或者s2 > e2时中断循环。

int k = 0;

while(s1 <= e1 && s2 <= e2) {
    if(arr[s1] <= arr[s2]) {
        tmpArr[k++] = arr[s1++];
    } else {
        tmpArr[k++] = arr[s2++];
    }
}

循环终止后,可能仍然有没有放入新数组的数,这时我们就要判断,哪一组没有放完,继续放。

while(s1 <= e1) {
    tmpArr[k++] = arr[s1++];
}
while (s2 <= e2) {
    tmpArr[k++] = arr[s2++];
}

最后再将新数组的值赋给旧数组。

注意

这里坚决不能这样写:arr[i] = tmpArr[i]

因为你要对应原数组的坐标进行赋值:

在这里插入图片描述

以上图为例,tmpArr[0]赋给arr[4],我们可以这样写arr[i + left] = tmpArr[i],这样就可以复制给对应坐标了

for (int i = 0; i < k; i++) {
    arr[i + left] = tmpArr[i];
}

这里是完整代码

private static void merge(int[] arr, int left, int mid, int right) {
    int s1 = left;
    int e1 = mid;
    int s2 = mid + 1;
    int e2 = right;

    int[] tmpArr = new int[right - left + 1];
    int k = 0;

    while(s1 <= e1 && s2 <= e2) {
        if(arr[s1] <= arr[s2]) {
            tmpArr[k++] = arr[s1++];
        } else {
            tmpArr[k++] = arr[s2++];
        }
    }
    while(s1 <= e1) {
        tmpArr[k++] = arr[s1++];
    }
    while (s2 <= e2) {
        tmpArr[k++] = arr[s2++];
    }

    for (int i = 0; i < k; i++) {
        arr[i + left] = tmpArr[i];
    }

}
private static void mergeFunc(int[] arr, int left, int right) {
    if(left >= right) {
        return;
    }
    int mid = (left + right) / 2;
    mergeFunc(arr, left, mid);
    mergeFunc(arr, mid + 1, right);

    merge(arr, left, mid, right);

}
public static void mergeSort(int[] arr) {
    mergeFunc(arr, 0, arr.length - 1);
}

3.4.2 非递归

非递归的思路其实和递归的合并步骤思路一样,我们先假设的是数组已经递归完了,已经分成一个一个的数了,开始合并。

1个合并成2个,2个合并成4个,设当前合并的数字有gap个,定义left,right,mid,然后调用合并方法合并。

注意i的变化,i+=gap*2可以遍历其他组的数据并合并。

在这里插入图片描述

private static void merge(int[] arr, int left, int mid, int right) {
    int s1 = left;
    int e1 = mid;
    int s2 = mid + 1;
    int e2 = right;
    int[] tmpArr = new int[right - left + 1];
    int k = 0;
    while(s1 <= e1 && s2 <= e2) {
        if(arr[s1] <= arr[s2]) {
            tmpArr[k++] = arr[s1++];
        } else {
            tmpArr[k++] = arr[s2++];
        }
    }
    while(s1 <= e1) {
        tmpArr[k++] = arr[s1++];
    }
    while (s2 <= e2) {
        tmpArr[k++] = arr[s2++];
    }
    for (int i = 0; i < k; i++) {
        arr[i + left] = tmpArr[i];
    }
}
public static void mergeSort1(int[] arr) {
    int gap = 1;
    while(gap < arr.length) {
        for (int i = 0; i < arr.length; i+= gap * 2) {
            int left = i;//i不会越界
            int mid = left + gap - 1;
            int right = mid + gap;
            //注意:mid 和 right可能会发生越界,要判断并修正
            if (mid >= arr.length) {
                mid = arr.length - 1;
            }
            if(right >= arr.length) {
                right = arr.length - 1;
            }
            merge(arr, left, mid, right);
        }
        gap *= 2;
    }
}

特点

  1. 归并的缺点在于需要O(N)的空间复杂度。

  2. 时间复杂度:O(N*logN),对数据不敏感。

    递归logN层,每一层都要对n个数排序,遍历n个数,相乘为O(N*logN)

  3. 空间复杂度:O(N),开辟了额外的数组

  4. 稳定性:稳定

在这里插入图片描述

3.4.3 海量数据的排序问题

外部排序:排序过程需要在磁盘等外部存储进行的排序

前提:内存只有 1G,需要排序的数据有 100G

因为内存中因为无法把所有数据全部放下,所以需要外部排序,而归并排序是最常用的外部排序

  1. 先把文件切分成 200 份,每个 512 M,把每个小文件当中的数据读取到内存中进行排序。
  2. 分别对 512 M 排序,因为内存已经可以放的下,所以任意排序方式都可以,排序完之后再写入文件,此时这 512 M的每个文件就已经有序了。
  3. 进行2路归并,同时对 200 份有序文件做归并过程,最终结果就有序了。

在这里插入图片描述

4. 排序算法复杂度及稳定性分析

在这里插入图片描述

排序方法最好平均最坏空间复杂度稳定性
冒泡排序O(n)(加优化)O(n^2)O(n^2)O(1)稳定
插入排序O(n)O(n^2)O(n^2)O(1)稳定
选择排序O(n^2)O(n^2)O(n^2)O(1)不稳定
希尔排序O(n)O(n^1.3)O(n^1.5)O(1)不稳定
堆排序O(n * log(n))O(n * log(n))O(n * log(n))O(1)不稳定
快速排序O(n * log(n))O(n * log(n))O(n^2)O(log(n)) ~ O(n)不稳定
归并排序O(n * log(n))O(n * log(n))O(n * log(n))O(n)稳定

对于这些复杂度和稳定性,背是最容易忘的,我们要根据算法的原理来理解,并且自己要能推出来,这样是记的最牢的方法了💓
这篇文章就先到这里啦~如果有什么问题可以在评论区留言或者是私信我呦😊
下篇预告:十大排序算法(下):计数排序,基数排序,桶排序

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/538595.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Jmeter处理接口签名sign

写接口脚本的时候&#xff0c;很多接口涉及到签名&#xff0c;今天介绍下用Jmeter编写签名脚本的方法。 举个例子&#xff0c;开启红包接口&#xff0c;请求方式为post POST /v1/api/red/open json请求参数 { "red_id":1, "timestamp":"166703384…

springboot+vue学生成绩管理系统(源码+文档)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的学生成绩管理系统。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 &#x1f495;&#x1f495;作者&#xff1a;风…

例1:混合物压缩、冷却、分离

例&#xff1a;如图所示流程&#xff0c;一烃类混合物Feed含乙烷0.15&#xff08;摩尔分数&#xff0c;下同&#xff09;、丙烷0.20、异丁烷0.60和正丁烷0.05&#xff0c;温度10℃&#xff0c;压力101.3kPa&#xff0c;流量45kmol/h。将其压缩至345kPa后冷却至0℃&#xff0c;送…

博客园主题样式,添加背景音乐,鼠标点击等样式设置

文章目录 1)、 博客园主题样式设置1.1)、 主题文档及地址1.2)、获取文件1.3)、配置CSS1.4)、配置JS1.5)、配置Loading1.6)、其他配置1.7)、个性化配置1.8)、效果预览 2)、背景音乐设置2.1)、单曲添加2.2)、歌单添加2.3)、总结 3)、鼠标点击样式3.1)、Js3.2)、效果预览3.3)、更多…

西门子SCL编程指令状态信息

一、编程时需要考虑的状态信息 在逐步执行运动控制命令时&#xff0c;确保等待激活的命令执行完成后再启动新命令&#xff01; 使用运动控制指令的状态消息和工艺对象的“StatusBits”变量&#xff0c;可以检查激活的命令是否已完成。 在下例中&#xff0c;请按照所示顺序执行…

51单片机控制步进电机Protues仿真设计

一、概述 1.1步进电机简介 步进电机&#xff08;Stepper Motor&#xff09;是一种将电信号转换为机械运动的电动机&#xff0c;是一种专用于精密控制的电机。一般步进电机运行稳定&#xff0c;并且精度较高&#xff0c;因此常用于精密仪器、自动化设备、机器人等需要高精度的…

上传漏洞,后端黑白名单绕过(21)

文件上传常见的验证&#xff0c;&#xff1a;后缀名&#xff0c;类型&#xff0c;文件头等 后缀名指的是白名单和黑名单 文件类型&#xff1a;mime的信息 文件头&#xff1a;内容头信息 我们一个一个来说&#xff0c;这个后缀名&#xff0c;大部分可以上传的对方都不允许脚…

【TCP/IP】TCP报文段的首部格式和流量控制

TCP 报文段的首部格式 TCP 虽然是面向字节流的&#xff0c;但其传送的数据单元却是报文段。一个TCP报文段分为首部和数据两部分&#xff0c;而 TCP 的全部功能的体现在它首部中各字段的作用。 因此&#xff0c;弄清 TCP 报文段首部各字段的作用对掌握 TCP 的工作原理非常重要。…

排查jacoco覆盖率对反射问题的影响

最近业务部门开始推行&#xff0c;在全部后台应用中自动开启覆盖率测试。然而&#xff0c;不久后就有业务测试的同学反馈出现问题。 问题的现象如下&#xff1a; 我们的业务通过 HTTP 调用腾讯OSS的服务&#xff0c;结果得到了以上的报错信息。测试同学验证后发现&#xff0c…

【RocketMQ】004-Spring Boot 集成 RocketMQ

【RocketMQ】004-Spring Boot 集成 RocketMQ 文章目录 【RocketMQ】004-Spring Boot 集成 RocketMQ一、基本使用1、创建 Spring Boot 项目&#xff0c;并引入 RocketMQ 依赖2、application.yml 配置3、消息生产者4、消息消费者5、消息调用接口6、启动 RocketMQ7、启动项目&…

(1)LED

LED正负极&#xff1a;大红旗——负极&#xff0c;小红旗——正极 如何看原理图电阻/电容值&#xff1a; eg&#xff1a; 102 10 2 10 * 10 ^ 2 1000 473 47 3 47 * 10 ^ 3 47000单片机使用TTL电频&#xff1a;高电平&#xff08;逻辑1&#xff09;5V 低电平&#xff…

C语言操作符详解(上)

C语言操作符详解&#xff08;上&#xff09; 前言1. 算术操作符2. 移位操作符2.1 左移操作符(<<)2.2 右移操作符&#xff08;>>&#xff09; 3. 位操作符3.1 按位与&#xff08;&&#xff09;3.2 按位或&#xff08;|&#xff09;3.4 按位异或&#xff08;^&am…

(4)定时器

51单片机的定时器属于单片机的内部资源&#xff0c;其电路的连接和运转均在单片机内部完成 作用&#xff1a; 用于计时系统替代长时间Delay&#xff0c;提高运行效率和速度任务切换 STC89C52定时器资源&#xff1a; 定时器个数&#xff1a;3个&#xff08;T0,T1,T2&#xf…

【MySQL】MySQL 运算符

目录 一、运算符简述 二、运算符使用 1.算术运算符 1.1 加法运算符 1.2 减法运算符 1.3 乘法与除法运算符 1.4 求模&#xff08;求余&#xff09;运算符 2.比较运算符 2.1 等号运算符 2.2 安全等于运算符 2.3 不等于运算符 2.4 空运算符 2.5 非空运算符 2.6 最小…

深度剖析Mybatis-plus Injector SQL注入器

背景 在项目中需要同时操作Sql Server 以及 MySQL 数据库&#xff0c;可能平时直接使用 BaseMapper中提供的方法习惯 了&#xff0c;不用的话总感觉影响开发效率&#xff0c;但是两个数据库的SQL语法稍微有点差别&#xff0c;有些暴露的方法并不能直接使用&#xff0c;所以便想…

WebSocket的那些事(3-STOMP实操篇)

目录 一、序言二、STOMP详解1、STOMP简单介绍2、STOMP协议内容3、使用STOMP的好处 三、代码示例1、Maven依赖2、开启WebSocket消息代理3、控制器4、前端页面greeting.html 四、测试1、连接服务端2、发送消息 五、STOMP消息传播流程六、结语 一、序言 上节中我们在 WebSocket的…

(11)LCD1602液晶显示屏

LCD1602&#xff08;Liquid Crystal Display&#xff09;液晶显示屏是一种字符型液晶显示模块&#xff0c;可以显示ASCII码的标准字符和其它的一些内置特殊字符&#xff0c;还可以有8个自定义字符&#xff0c;自带芯片扫描 显示容量&#xff1a;162个字符&#xff0c;每个字符…

【C++】STL六大组件简介

STL(standard template libaray-标准模板库)&#xff1a;是C标准库的重要组成部分&#xff0c;不仅是一个可复用的组件库&#xff0c;而且是一个包罗数据结构与算法的软件框架。 1.STL的版本介绍 原始版本 Alexander Stepanov、Meng Lee 在惠普实验室完成的原始版本&#xff…

Unity里面CG和HLSL在写法上的一些区别

大家好&#xff0c;我是阿赵。这里继续讲URP相关的内容。 这次想讲的是CG和HLSL在写法上的一些区别。 一、为什么开始用HLSL 首先&#xff0c;基本上大家都知道的事情再说一遍。 三种Shader编程语言&#xff1a; 1、基于OpenGL的OpenGL Shading Language&#xff0c;缩写GLSL…

接口测试中postman环境和用例集

postman的环境使用 postman里有环境的设置&#xff0c;就是我们常说的用变量代替一个固定的值&#xff0c;这样做的好处是可以切换不同的域名、不同的环境变量&#xff0c;不同的线上线下账户等等场景。下面就看下怎么用吧。 创建一个Environment postman有一个envrionment&am…