专栏首页JimoerJava实现十个经典排序算法(带动态效果图)

Java实现十个经典排序算法(带动态效果图)

前言

排序算法是老生常谈的了,但是在面试中也有会被问到,例如有时候,在考察算法能力的时候,不让你写算法,就让你描述一下,某个排序算法的思想以及时间复杂度或空间复杂度。我就遇到过,直接问快排的,所以这次我就总结梳理一下经典的十大排序算法以及它们的模板代码。

算法分析

一个排序算法的好坏,一般是通过下面几个关键信息来分析的,下面先介绍一下这几个关键信息,然后再将常见的排序算法的这些关键信息统计出来。

名词介绍

  • 时间复杂度:指对数据操作的次数(或是简单的理解为某段代码的执行次数)。举例:O(1):常数时间复杂度;O(log n):对数时间复杂度;O(n):线性时间复杂度。
  • 空间复杂度:某段代码每次执行时需要开辟的内存大小。
  • 内部排序:不依赖外部的空间,直接在数据内部进行排序;
  • 外部排序:数据的排序,不能通过内部空间来完成,需要依赖外部空间。
  • 稳定排序:若两个元素相等:a=b,排序前a排在b前面,排序后a仍然在b后面,称为稳定排序。
  • 不稳定排序:若两个元素相等:a=b,排序前a排在b前面,排序后a有可能出现在b后面,称为不稳定排序。

常见的排序算法的这几个关键信息如下:

冒泡排序

冒泡排序是一种简单直观的排序算法,它需要多次遍历数据; 主要有这么几步:

  • 将相邻的两个元素进行比较,如果前一个元素比后一个元素大那么就交换两个元素的位置,经过这样一次遍历后,最后一个元素就是最大的元素了;
  • 然后再将除最后一个元素的剩下的元素,重复执行上面相邻两元素比较的步骤。
  • 每次对越来越少的元素重复上面的步骤,直到就剩一个数字需要比较。

这样最终达到整体数据的一个有序性了。

动图演示

代码模板

/**
 * 冒泡排序
 * @param array
 */
public static void bubbleSort(int[] array){
    if(array.length == 0){
        return;
    }
    for(int i=0;i<array.length;i++){
        // 每一次都减少i个元素
        for(int j=0;j<array.length-1-i;j++){
            // 若相邻的两个元素比较后,前一个元素大于后一个元素,则交换位置。
            if(array[j]>array[j+1]){
                int temp = array[j];
                array[j] = array[j+1];
                array[j+1] = temp;
            }
        }
    }
}

冒泡排序总结

当数组中的元素已经是正序时,执行效率最高。 当数组中的元素是一个倒序时,执行效率最低,相邻的元素每次比较都需要交换位置。 而且冒泡排序是完全在数据内部进行的,不需要额外的空间,所以空间复杂度是O(1)。

选择排序

选择排序是一种简单粗暴的排序方式,每次都从数据中选出最大或最小的元素,最终选完了,那么选出来的数据就是排好序的了。

主要步骤:

  • 先从全部数据中选出最小的元素,放到第一个元素的位置(选出最小元素和第一位位置交换位置);
  • 然后再从除了第一个元素的剩余元素中再选出最小的元素,然后放到数组的第二个位置上。
  • 循环重复上面的步骤,最终选出来的数据都放前面了,数据就排好序了。

动图演示

代码模板

public static void selectSort(int[] array){

    for(int i=0;i<array.length;i++){
        // 先以i为最小值的下标
        int min = i;
        // 然后从后面的数据中找出比array[min] 还小的值,就替换min为当前下标。
        for(int j=i+1;j<array.length;j++){
            if(array[j]<array[min]){
                min = j;
            }
        }
        // 最终如果最小值的下标不等于i了,那么将最小值与i位置的数据替换,即将最小值放到数组前面来,然后循环整个操作。
        if(min != i){
            int temp = array[i];
            array[i] = array[min];
            array[min] = temp;
        }
    }
}

选择排序总结

所有的数据经过选择排序,时间复杂度都是O(n^2);所以需要排序的数据量越小选择排序的效率越高。

插入排序

插入排序也是一种比较直观和容易理解的排序算法,通过构建有序序列,将未排序中的数据插入到已排序中序列,最终未排序全部插入到有序序列,达到排序效果。

主要步骤:

  • 将原始数据的第一个元素当成已排序序列,然后将除了第一个元素的后面元素当成未排序序列。
  • 从后面未排序元素中从前到后扫描,挨个取出元素,在已排序的序列中从后往前扫描,将从未排序序列中取出的元素插入到已排序序列的指定位置。
  • 当未排序元素数量为0时,则排序完成。

动图演示

代码模板

public static void insertSort(int[] array){
     // 第一个元素被认为默认有序,所以遍历无序元素从i1开始。
     for(int i=1;i<array.length;i++){

         int sortItem = array[i];
         int j = i;
         // 将当前元素插入到前面的有序元素里,将当前元素与前面有序元素从后往前挨个对比,然后将元素插入到指定位置。
         while (j>0 && sortItem < array[j-1]){
             array[j] = array[j-1];
             j--;
         }
         // 若当前元素在前面已排序里面不是最大的,则将它插入到前面已经确定了位置里。
         if(j !=i){
             array[j] = sortItem;
         }
     }
 }

插入排序总结

插入排序也是采用的内部排序,所以空间复杂度是O(1),但是时间复杂度就是O(n^2),因为基本上每个元素都要处理多次,需要反复将已排序元素移动,然后将数据插入到指定的位置。

希尔排序

希尔排序是插入排序的一个升级版,它主要是将原先的数据分成若干个子序列,然后将每个子序列进行插入排序,然后每次拆得子序列数量逐次递减,直到拆的子序列的长度等于原数据长度。然后再将数据整体来依次插入排序。

主要步骤: 选择一个增量序列 t1,t2,……,tk,其中 ti > tj, tk = 1; 按增量序列个数 k,对序列进行 k 趟排序; 每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

过程演示

原始未排序的数据。

经过初始增量gap=array.length/2=5分组后,将原数据分为了5组,[12,1]、[29,30]、[5,45]、[16,26]、[15,32]。

将分组后的数据,每一组数据都直接执行插入排序,这样数据已经慢慢有序起来了,然后再缩小增量gap=5/2=2,将数据分为2组:[1,5,15,30,26]、[29,16,12,45,32]。

对上面已经分好的两组进行插入排序,整个数据就更加趋向有序了,然后再缩小增量gap=2/2=1,整个数据成为了1组,整个序列作为了表来处理,然后再执行一次插入排序,数据最终达到了有序。

代码模板

/**
 * 希尔排序
 * @param array
 */
public static void shellSort(int[] array){
    int len = array.length;
    int temp, gap = len / 2;
    while (gap > 0) {
        for (int i = gap; i < len; i++) {
            temp = array[i];
            int preIndex = i - gap;
            while (preIndex >= 0 && array[preIndex] > temp) {
                array[preIndex + gap] = array[preIndex];
                preIndex -= gap;
            }
            array[preIndex + gap] = temp;
        }
        gap /= 2;
    }
}

归并排序

归并排序是采用的分而治之的递归方式来完成数据排序的,主要是将已有序的两个子序列,合并成一个新的有序子序列。先将子序列分段有序,然后再将分段后的子序列合并成,最终完成数据的排序。

主要步骤:

  • 将数据的长度从中间一分为二,分成两个子序列,执行递归操作,直到每个子序列就剩两个元素。
  • 然后分别对这些拆好的子序列进行归并排序。
  • 将排序好的子序列再两两合并,最终合并成一个完整的排序序列。

动图演示

代码模板

/**
     * 归并排序
     * @param array     数组
     * @param left      0
     * @param right     array.length-1
     */
    public static void mergeSort(int[] array,int left,int right){
        
        if (right <= left){
            return;
        }
        // 一分为二
        int mid = (left + right)/2;
        // 对前半部分执行归并排序
        mergeSort(array, left, mid);
        // 对后半部分执行归并排序
        mergeSort(array, mid + 1, right);
        // 将分好的每个子序列,执行排序加合并操作
        merge(array, left, mid, right);
    }

    /**
     * 合并加排序
     * @param array
     * @param left
     * @param middle
     * @param right
     */
    public static void merge(int[] array,int left,int middle,int right){
     // 中间数组
     int[] temp = new int[right - left + 1]; 
     int i = left, j = middle + 1, k = 0;
     while (i <= middle && j <= right) {
         // 若前面数组的元素小,就将前面元素的数据放到中间数组中
         if(array[i] <= array[j]){
             temp[k++] = array[i++];
         }else {
             // 若后面数组的元素小,就将后面数组的元素放到中间数组中
             temp[k++] = array[j++];
         }
     }
     // 若经过上面的比较合并后,前半部分的数组还有数据,则直接插入中间数组后面
     while (i <= middle){
         temp[k++] = array[i++];
     }
     // 若经过上面的比较合并后,后半部分的数组还有数据,则直接插入中间数组后面
     while (j <= right){
         temp[k++] = array[j++];
     }
     // 将数据从中间数组中复制回原数组
     for (int p = 0; p < temp.length; p++) {
         array[left + p] = temp[p];
     }
 }

归并排序总结

归并排序效率很高,时间复杂度能达到O(nlogn),但是依赖额外的内存空间,而且这种分而治之的思想很值得借鉴,很多场景都是通过简单的功能,组成了复杂的逻辑,所以只要找到可拆分的最小单元,就可以进行分而治之了。

快速排序

快速排序,和二分查找的思想很像,都是先将数据一份为二然后再逐个处理。快速排序也是最常见的排序算法的一种,面试被问到的概率还是比较大的。

主要步骤:

  • 从数据中挑选出一个元素,称为 "基准"(pivot),一般选第一个元素或最后一个元素。
  • 然后将数据中,所有比基准元素小的都放到基准元素左边,所有比基准元素大的都放到基准元素右边。
  • 然后再将基准元素前面的数据集合和后面的数据集合重复执行前面两步骤。

动图演示

代码模板

/**
 * 快速排序
 * @param array 数组
 * @param begin 0
 * @param end   array.length-1
 */
public static void quickSort(int[] array, int begin, int end) {
    if (end <= begin) return;
    int pivot = partition(array, begin, end);
    quickSort(array, begin, pivot - 1);
    quickSort(array, pivot + 1, end);
}

/**
 * 分区
 * @param array
 * @param begin
 * @param end
 * @return
 */
public static int partition(int[] array, int begin, int end) {
    // pivot: 标杆位置,counter: 小于pivot的元素的个数
    int pivot = end, counter = begin;
    for (int i = begin; i < end; i++) {
        if (array[i] < array[pivot]) {
           // 替换,将小于标杆位置的数据放到开始位置算起小于标杆数据的第counter位
            int temp = array[counter];
            array[counter] = array[i];
            array[i] = temp;
            counter++;
        }
    }
    // 将标杆位置的数据移动到小于标杆位置数据的下一个位。
    int temp = array[pivot];
    array[pivot] = array[counter];
    array[counter] = temp;
    return counter;
}

快速排序总结

我找的快速排序的模板代码,是比较巧妙的,选择了最后一个元素作为了基准元素,然后小于基准元素的数量,就是基准元素应该在的位置。这样看起来是有点不好懂,但是看明白之后,就会觉得这个模板写的还是比较有意思的。

堆排序

堆排序其实是采用的堆这种数据结构来完成的排序,一般堆排序的方式都是采用的一种近似完全二叉树来实现的堆的方式完成排序,但是堆的实现方式其实不止有用二叉树的方式,其实还有斐波那契堆。 而根据排序的方向又分为大顶堆和小顶堆:

  • 大顶堆:每个节点值都大于或等于子节点的值,在堆排序中用做升序排序。
  • 小顶堆:每个节点值都小于或等于子节点的值,在堆排序中用做降序排序。

像Java中的PriorityQueue就是小顶堆。

主要步骤:

  1. 创建一个二叉堆,参数就是无序序列[0~n];
  2. 把堆顶元素和堆尾元素互换;
  3. 调整后的堆顶元素,可能不是最大或最小的值,所以还需要调整此时堆顶元素的到正确的位置,这个调整位置的过程,主要是和二叉树的子元素的值对比后找到正确的位置。
  4. 重复步骤2、步骤3,直至整个序列的元素都在二叉堆的正确位置上了。

动图演示

模板代码

/**
 * 堆排序
 * @param array
 */
public static int[] heapSort(int[] array){
    int size = array.length;
    // 先将数据放入堆中
    for (int i = (int) Math.floor(size / 2); i >= 0; i--) {
        heapTopMove(array, i, size);
    }
    // 堆顶位置调整
    for(int i = size - 1; i > 0; i--) {
        swapNum(array, 0, i);
        size--;
        heapTopMove(array, 0,size);
    }
    return array;
}

/**
 * 堆顶位置维护
 * @param array
 * @param i
 * @param size
 */
public static void heapTopMove(int[] array,int i,int size){

    int left = 2 * i + 1;
    int right = 2 * i + 2;
    int largest = i;

    if (left < size && array[left] > array[largest]) {
        largest = left;
    }

    if (right < size && array[right] > array[largest]) {
        largest = right;
    }

    if (largest != i) {
        swapNum(array, i, largest);
        heapTopMove(array, largest, size);
    }
}

/**
 * 比较交换
 * @param array
 * @param left
 * @param right
 */
public static void swapNum(int[] array,int left,int right){
    int temp = array[left];
    array[left] = array[right];
    array[right] = temp;
}

堆排序总结

堆排序的时间复杂度也是O(nlogn),这个也是有一定的概率在面试中被考察到,其实如果真实在面试中遇到后,可以在实现上不用自己去维护一个堆,而是用Java中的PriorityQueue来实现,可以将无序数据集合放入到PriorityQueue中,然后再依次取出堆顶数据,取出堆顶数据时要从堆中移除取出的这个元素,这样每次取出的就都是现有数据中最小的元素了。

计数排序

计数排序是一种线性时间复杂度的排序算法,它主要的逻辑时将数据转化为键存储在额外的数组空间里。计数排序有一定的局限性,它要求输入的数据,必须是有确定范围的整数序列。

主要步骤:

  • 找出待排序的数组中最大和最小的元素;
  • 统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
  • 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
  • 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。

动图演示

代码模板

/**
 * 计数排序
 * @param array
 */
public static void countSort(int[] array){
    int bucketLen = getMaxValue(array) + 1;
    int[] bucket = new int[bucketLen];
    // 统计每个值出现的次数
    for (int value : array) {
        bucket[value]++;
    }
    // 反向填充数组
    int sortedIndex = 0;
    for (int j = 0; j < bucketLen; j++) {
        while (bucket[j] > 0) {
            array[sortedIndex++] = j;
            bucket[j]--;
        }
    }
}

/**
 * 获取最大值
 * @param arr
 * @return
 */
private static int getMaxValue(int[] arr) {
    int maxValue = arr[0];
    for (int value : arr) {
        if (maxValue < value) {
            maxValue = value;
        }
    }
    return maxValue;
}

桶排序

桶排序算是计数排序的一个加强版,它利用特定函数的映射关系,将属于一定范围内的数据,放到一个桶里,然后对每个桶中的数据进行排序,最后再将排序好的数据拼接起来。 主要步骤:

  • 设置一个合适长度的数组作为空桶;
  • 遍历数据,将数据都放到指定的桶中,分布的越均匀越好;
  • 对每个非空的桶里的数据进行排序;
  • 将每个桶中排序好的数据拼接在一起。

动图演示

代码模板

 /**
  * 桶排序
  * @param arr
  * @param bucketSize
  * @return
  */
 private static int[] bucketSort(int[] arr, int bucketSize){
     if (arr.length == 0) {
         return arr;
     }
     int minValue = arr[0];
     int maxValue = arr[0];
     // 计算出最大值和最小值
     for (int value : arr) {
         if (value < minValue) {
             minValue = value;
         } else if (value > maxValue) {
             maxValue = value;
         }
     }
     // 根据桶的长度以及数据的最大值和最小值,计算出桶的数量
     int bucketCount = (int) Math.floor((maxValue - minValue) / bucketSize) + 1;
     int[][] buckets = new int[bucketCount][0];

     // 利用映射函数将数据分配到各个桶中
     for (int i = 0; i < arr.length; i++) {
         int index = (int) Math.floor((arr[i] - minValue) / bucketSize);
         // 将数据填充到指定的桶中
         buckets[index] = appendBucket(buckets[index], arr[i]);
     }
     int arrIndex = 0;
     for (int[] bucket : buckets) {
         if (bucket.length <= 0) {
             continue;
         }
         // 对每个桶进行排序,这里使用了插入排序
         InsertSort.insertSort(bucket);
         for (int value : bucket) {
             arr[arrIndex++] = value;
         }
     }
     return arr;
 }

 /**
  * 扩容,并追加数据
  *
  * @param array
  * @param value
  */
 private static int[] appendBucket(int[] array, int value) {
     array = Arrays.copyOf(array, array.length + 1);
     array[array.length - 1] = value;

     return array;
 }

基数排序

基数排序是一种非比较型排序,主要逻辑时将整数按位拆分成不同的数字,然后再按照位数排序,先按低位排序,进行收集,再按高位排序,再进行收集,直到最高位。 主要步骤:

  • 获取原始数据中的最大值以及最高位;
  • 在原始数组中,从最低位开始取每个位组成基数数组;
  • 基数数组进行计数排序(利用计数排序适用于小范围数的特点);

动图演示

代码模板

/**
 * 基数排序
 * @param array
 */
public static void radixSort(int[] array){
    // 获取最高位
    int maxDigit = getMaxDigit(array);
    int mod = 10;
    int dev = 1;

    for (int i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {
        // 考虑负数的情况,这里扩展一倍队列数,其中 [0-9]对应负数,[10-19]对应正数 (bucket + 10)
        int[][] counter = new int[mod * 2][0];
        // 计数排序
        for (int j = 0; j < array.length; j++) {
            int bucket = ((array[j] % mod) / dev) + mod;
            counter[bucket] = appendBucket(counter[bucket], array[j]);
        }
        // 反向填充数组
        int pos = 0;
        for (int[] bucket : counter) {
            for (int value : bucket) {
                array[pos++] = value;
            }
        }
    }
}

/**
 * 获取最高位数
 */
private static int getMaxDigit(int[] arr) {
    int maxValue = getMaxValue(arr);
    return getNumLength(maxValue);
}

/**
 * 获取最大值
 * @param arr
 * @return
 */
private static int getMaxValue(int[] arr) {
    int maxValue = arr[0];
    for (int value : arr) {
        if (maxValue < value) {
            maxValue = value;
        }
    }
    return maxValue;
}

/**
 * 获取整数的位数
 * @param num
 * @return
 */
protected static int getNumLength(long num) {
    if (num == 0) {
        return 1;
    }
    int lenght = 0;
    for (long temp = num; temp != 0; temp /= 10) {
        lenght++;
    }
    return lenght;
}

/**
 * 扩容,并追加数据
 *
 * @param array
 * @param value
 */
private static int[] appendBucket(int[] array, int value) {
    array = Arrays.copyOf(array, array.length + 1);
    array[array.length - 1] = value;
    return array;
}

基数排序总结

计数排序、桶排序、基数排序这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:

  • 基数排序:根据键值的每位数字来分配桶;
  • 计数排序:每个桶只存储单一键值;
  • 桶排序:每个桶存储一定范围的数值;

总结

这次总结了10个经典的排序算法,也算是给自己早年偷的懒补一个补丁吧。一些常用的算法在面试中也算是一个考察方向,但是一般考察都是时间复杂度低的那几个即时间复杂度为O(nlogn)的:快速排序、堆排序、希尔排序。所以这几个要熟练掌握,起码要知道是怎样的实现逻辑(毕竟面试也有口述算法的时候)。

画图:AlgorithmMan

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 十大经典排序算法动图演示+Python实现

    而今天这篇文章,转自 Github 上一个项目,此项目整理了 10 个常见排序算法的原理、演示和多种语言的实现。这里我们摘录其中 Python 的实现,分享给大...

    Crossin先生
  • 如何用 Java 实现十大经典排序算法?

    最近几天在研究排序算法,看了很多博客,发现网上有的文章中对排序算法解释的并不是很透彻,而且有很多代码都是错误的,例如有的文章中在“桶排序”算法中对每个桶进行排序...

    一个优秀的废人
  • 十大经典排序算法最强总结(含JAVA代码实现)

    最近几天在研究排序算法,看了很多博客,发现网上有的文章中对排序算法解释的并不是很透彻,而且有很多代码都是错误的,例如有的文章中在“桶排序”算法中对每个桶进行排...

    郭耀华
  • 十大经典排序算法最强总结(含Java代码实现)

    常见的快速排序、归并排序、堆排序、冒泡排序等属于比较排序。在排序的最终结果里,元素之间的次序依赖于它们之间的比较。每个数都必须和其他数进行比较,才能确定自己的位...

    Java团长
  • 十大经典排序算法最强总结(含Java、Python码实现)

    所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。排序算法,就是如何使得记录按照要求排列的方法。排序算法在很多领域得到相当...

    郭耀华
  • 码农也要学算法

    当“人工智能”、“AlphaGo”、“无人驾驶”、“智能投顾”等词语不断在人们视野中出现的时候,意味着我们正步入一个算法的时代。计算机通过提供给人类每天要面临的...

    程序员宝库
  • 极客算法训练笔记(九),十大经典排序之桶排序,实习第一个业务就是分桶实现的

    如上图所示(图来自于极客时间算法训练营超哥的资料),我之前写的七大排序算法,都是比较类排序,最后三种是时间复杂度是O(n)的非比较类排序算法:桶排序、计数排序、...

    阿甘的码路
  • 原创 | 初学者友好!最全算法学习资源汇总(附链接)

    在计算机发展飞速的今天,也许有人会问,“今天计算机这么快,算法还重要吗?”其实永远不会有太快的计算机,因为我们总会想出新的应用。虽然在摩尔定律的作用下,计算机的...

    数据派THU
  • hadoop性能调优

    从三个方面着手优化 : 1. hadoop配置 2. 设计mapred/job 3. 代码级别. 4. 改造hadoop 一. con...

    闵开慧
  • 【干货】哇塞的 Java 知识总结项目大汇总!

    大家好,我是鱼皮,今天分享 十几个 让人直呼 “哇塞” 的 Java 知识总结项目!

    程序员鱼皮
  • Java 知识总结大汇总!看完哪个都变大佬!

    大家好,我是鱼皮,今天分享 十几个 让人直呼 “哇塞” 的 Java 知识总结项目!

    程序员鱼皮
  • 当今世界最为经典的十大算法 博客分类: 经典文章转载 算法数据结构网络应用数据挖掘J#

    本文转载自July CSDN博客:http://blog.csdn.net/v_JULY_v/archive/2011/03/07/6228235.aspx

    chroya
  • 一周极客热文:程序员必须知道的10大基础实用算法及其讲解

    程序员必须知道的10大基础实用算法及其讲解,包括: 快速排序算法; 堆排序算法(Heapsort):是指利用堆这种数据结构所设计的一种排序算法; 归并排序(Me...

    钱曙光
  • JS排序算法

    版权声明:本文为博主原创文章,未经博主允许不得转载。 ...

    用户1212940
  • P8架构师倾情讲述,看源码是Java高级程序员内功修炼必经之路

    笔者前两天总结了一些Java程序员进阶必备的技术要点,并且做出了分享,可以参见前篇文章《Java程序员年薪40W,他1年走了别人5年的路(技术提炼建议收藏)》。

    慕容千语
  • JavaScript排序算法详解

    JS家的排序算法 引子 有句话怎么说来着: 雷锋推倒雷峰塔,Java implements JavaScript. 当年,想凭借抱Java大腿火一把...

    用户1212940
  • 动态可视化告诉你,从1965年开始哪些编程语言曾最受欢迎?

    学习Python这么久,大家都知道 Python 是一个 “胶水语言”,应用范围非常广,上到人工智能、机器学习、数据分析,下到各种脚本处理日常。

    Python数据科学
  • 2021年春招,Java后端最全面试攻略,吃透25个技术栈

    小编分享的这份春招Java后端开发面试总结包含了JavaOOP、Java集合容器、Java异常、并发编程、Java反射、Java序列化、JVM、Redis、Sp...

    Java程序猿
  • 遇到瓶颈如何提升自己?Android 最全知识体系总结及学习路线分享给你!

    1.首先要确立你的技术方向和目标,然后朝着这个方向和目标走下去,最重要的是做好职业规划

    Android技术干货分享

扫码关注云+社区

领取腾讯云代金券