2021-2022秋招备战java个人珍藏版复习资料(最全)

2021-2022秋招备战java个人珍藏版复习资料(最全)

提前批你不得不掌握的点(一)


前言

22届的小伙伴们估计正在疯狂准备暑期实习网申、笔试、面试吧?告诉你们一个最新消息,2022秋招提前批已经开启了!下面,快来和万能班长一起看看吧~为了提前抢到22届人才,各家大厂的招聘节奏越来越快!华为、华泰证券、三一集团、国泰君安、招行、毕马威、嘉实基金、小鹏汽车等早就把目光瞄准22届同学,开启了抢人模式!
在这里插入图片描述
提前批的重要性我就不说了,但是要强调一点的是,这相当于你多了一次机会,如果放弃这次机会,那么就只能等秋招了。
话不多说,我们来讲讲怎么去复习

一、数据结构与算法是基础

1.1 数据结构部分

在这里插入图片描述
推荐阅读:数据结构最全讲解
数据结构概念:数据结构分类细讲
数据结构java源码:经典数据结构及算法-Java实现-附源代码(可下载)

1.2 经典算法部分

推荐:
十大经典算法:https://www.cnblogs.com/ice-line/p/11753852.html

二、手写代码是能力

2.1.排序算法手写

  1. 选择排序(Selection Sort)

代码实现:

import java.util.Arrays;

public class Demo {
    
    
    //选择排序
    /**
     *1.从待排序序列中,找到关键字最小的元素;
     * 2.如果最小元素不是待排序序列的第一个元素,将其和第二个元素交换;
     * 3.从余下的N-1个元素中,找出关建字最小的元素,重复1.2步骤,直到排序排序结束
     * 当增量因子微一时,整个序列长度为原来序列的长度;
     */

    public static void main(String[] args) {
    
    
        int[] arr  = {
    
    25,89,56,88,21,3,56,66,8,663,55};
        Selectionchose(arr);
        System.out.println("========================");
        int[] res = Selectchose1(arr);
        System.out.println(Arrays.toString(res));

    }
//展示选择的过程
    public  static  void Selectionchose(int[] arr){
    
    
        if(arr==null||arr.length==0) return;
        //开始遍历,寻找最小的元素,打印出来
        for(int i=0;i<arr.length-1;i++){
    
    
            int min = i;
            for(int j=i+1;j<arr.length;j++){
    
    
                if(arr[j]<arr[min]){
    
    
                    min =j;
                }
            }
            if(min!=i){
    
    
                int temp = arr[min];
                arr[min] = arr[i];
                arr[i] = temp;
                System.out.println(Arrays.toString(arr));
            }
        }
    }
//直接返回最终结果
    public static  int[] Selectchose1(int[] arr){
    
    
        if(arr==null||arr.length==0){
    
    
            System.out.println("序列不合法!");
            return null;
        }
        for(int i =0;i<arr.length-1;i++){
    
    
            int min = i;
            for(int j =i+1;j<arr.length;j++){
    
    
                if(arr[min]>arr[j]){
    
    
                    min =j;
                }
            }
            if(min!=i){
    
    
                int res = arr[i];
                arr[i] = arr[min];
                arr[min] = res;
            }
        }
       return arr;
    }
}

在这里插入图片描述

2、插入排序(Insertion Sort)

代码实现:

import java.util.Arrays;

public class Deom {
    
    
    /**
     * 插入排序
     */

    public static void main(String[] args) {
    
    
        int[] arr = {
    
    25,89,56,88,21,3,56,66,8,663,55};
        //insertionSort(arr);
        insertionSort1(arr);
    }
    //序列较少的情况下:
    public  static  void insertionSort(int[] arr){
    
    
       if(arr==null||arr.length==0) return;
       for(int i =1;i<arr.length;i++){
    
    
           int temp = arr[i]; //取出第二个元素;
           for(int j =i;j>=0;j--){
    
    
              if(j>0&&arr[j-1]>temp){
    
    //j-1为当前元素的前一个元素的下标,如果前一个数大于当前的元素,那么两个数据进行交换
                  arr[j] = arr[j-1];
                  System.out.println("inserting"+Arrays.toString(arr));
              }else{
    
    
                  arr[j]=temp;//如果大于或等于,那么保存当前数,并且进入下一次插入;
                  System.out.println("Sorting"+Arrays.toString(arr));
                  break;
              }

           }
       }
    }
//数据量比较多的时候
    public  static  void insertionSort1(int[] arr){
    
    
        if(arr.length==0||arr==null) return;
        for(int i =0;i<arr.length-1;i++){
    
    
            for(int j = i+1;j>0;j--){
    
    
                if(arr[j-1]<arr[j]){
    
    
                    break;  //如果前一个元素小于当前元素,不做处理
                }
                int temp = arr[j];  //如果前一个元素大于或等于当前元素时,交换数值,然后进入循环。
                arr[j] = arr[j-1];
                arr[j-1] = temp;
            }
        }
        System.out.println(Arrays.toString(arr));
    }
}

在这里插入图片描述

3、冒泡排序(Bubble Sort)

代码实现:

import java.util.Arrays;

public class Deom {
    
    
    public static void main(String[] args) {
    
    
        int[] arr = {
    
    25,89,56,88,21,3,56,66,8,663,55};
         BubbleSort(arr);
    }
    public  static  void BubbleSort(int[] arr){
    
    
        if(arr.length==0||arr==null) return;
        for(int i =0;i<arr.length;i++){
    
      //表示第一个元素
            for(int j = i+1;j<arr.length;j++){
    
     //表示第二个元素
                if(arr[i]>=arr[j]){
    
     //如果第一个元素大于等于第二个元素,那么两个元素进行交换,直到不满条件;
                    int temp = arr[i];
                    arr[i] =arr[j];
                    arr[j] = temp;
                }
            }
        }
        System.out.println(Arrays.toString(arr));
    }
}

在这里插入图片描述

4、快速排序(Quick Sort)

代码实现:(递归方法)

import java.util.Arrays;

/**
 * 用伪代码描述如下:
 *
 * ①. i = L; j = R; 将基准数挖出形成第一个坑a[i]。
 * ②.j--,由后向前找比它小的数,找到后挖出此数填前一个坑a[i]中。
 * ③.i++,由前向后找比它大的数,找到后也挖出此数填到前一个坑a[j]中。
 * ④.再重复执行②,③二步,直到i==j,将基准数填入a[i]中
 * ————————————————
 *
 */

public class Deom {
    
    
    public static void main(String[] args) {
    
    
        int[] arr = {
    
    25,89,56,88,21,3,56,66,8,663,55};
        QuickSort(arr,0,arr.length-1);//以第一个数为基准
    }
    public static void QuickSort(int[] arr ,int low,int high){
    
    
        if(arr==null||arr.length==0) return;
        if(low>=high) return;
        int left = low;
        int right = high;
        int temp = arr[left]; //确定基准值;
        while (left<right){
    
    
            while (left<right&&arr[right]>=temp){
    
      //从后往前,找到小于或等于基准的数进行交换
                right--;
            }
            arr[left] = arr[right];

            while (left<right&&arr[left]<=temp){
    
      //从前往后走,找到大于或等于基准的数进行交换
                left++;
            }
            arr[right] = arr[left];
        }
      arr[left]=temp; //将基准值填到坑3中
        //开始递归;
        QuickSort(arr,low,left-1);
        QuickSort(arr,left+1,high);
        System.out.println(Arrays.toString(arr));
    }
   
}

     上面是递归版的快速排序:通过把基准temp插入到合适的位置来实现分治,并递归地对分治后的两个划分继续快排。那么非递归版的快排如何实现呢?
 因为递归的本质是栈,所以我们非递归实现的过程中,可以借助栈来保存中间变量就可以实现非递归了。在这里中间变量也就是通过QuickSort1函数划分
 区间之后分成左右两部分的首尾指针, 只需要保存这两部分的首尾指针即可。
import java.util.Arrays;
import java.util.Stack;

/**
 * 用伪代码描述如下:
 *
 * ①. i = L; j = R; 将基准数挖出形成第一个坑a[i]。
 * ②.j--,由后向前找比它小的数,找到后挖出此数填前一个坑a[i]中。
 * ③.i++,由前向后找比它大的数,找到后也挖出此数填到前一个坑a[j]中。
 * ④.再重复执行②,③二步,直到i==j,将基准数填入a[i]中
 * ————————————————
 *
 */

public class Deom {
    
    
    public static void main(String[] args) {
    
    
        int[] arr = {
    
    25,89,56,88,21,3,56,66,8,663,55};
       // QuickSort(arr,0,arr.length-1);//以第一个数为基准、
        quickSortByStack(arr);
    }
    public static void QuickSort(int[] arr ,int low,int high){
    
    
        if(arr==null||arr.length==0) return;
        if(low>=high) return;
        int left = low;
        int right = high;
        int temp = arr[left]; //确定基准值;
        while (left<right){
    
    
            while (left<right&&arr[right]>=temp){
    
      //从后往前,找到小于或等于基准的数进行交换
                right--;
            }
            arr[left] = arr[right];

            while (left<right&&arr[left]<=temp){
    
      //从前往后走,找到大于或等于基准的数进行交换
                left++;
            }
            arr[right] = arr[left];
        }
      arr[left]=temp; //将基准值填到坑3中
        //开始递归;
        QuickSort(arr,low,left-1);
        QuickSort(arr,left+1,high);
        System.out.println(Arrays.toString(arr));
    }
    /**
     * 快速排序(非递归)
     *
     * ①. 从数列中挑出一个元素,称为"基准"(pivot)。
     * ②. 重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(相同的数可以到任一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
     * ③. 把分区之后两个区间的边界(low和high)压入栈保存,并循环①、②步骤
     * @param arr   待排序数组
     */
    public static void quickSortByStack(int[] arr){
    
    
        if(arr.length <= 0) return;
        Stack<Integer> stack = new Stack<Integer>();

        //初始状态的左右指针入栈
        stack.push(0);
        stack.push(arr.length - 1);
        while(!stack.isEmpty()){
    
    
            int high = stack.pop();     //出栈进行划分
            int low = stack.pop();
            int pivotIdx = QuickSort1(arr, low, high);
            //保存中间变量
            if(pivotIdx > low) {
    
    
                stack.push(low);
                stack.push(pivotIdx - 1);
            }
            if(pivotIdx < high && pivotIdx >= 0){
    
    
                stack.push(pivotIdx + 1);
                stack.push(high);
            }
        }
    }

    private static int QuickSort1(int[] arr, int low, int high){
    
    
        if(arr.length <= 0) return -1;
        if(low >= high) return -1;
        int l = low;
        int r = high;

        int pivot = arr[l];    //挖坑1:保存基准的值
        while(l < r){
    
    
            while(l < r && arr[r] >= pivot){
    
      //坑2:从后向前找到比基准小的元素,插入到基准位置坑1中
                r--;
            }
            arr[l] = arr[r];
            while(l < r && arr[l] <= pivot){
    
       //坑3:从前往后找到比基准大的元素,放到刚才挖的坑2中
                l++;
            }
            arr[r] = arr[l];
        }
        arr[l] = pivot;   //基准值填补到坑3中,准备分治递归快排
        System.out.println(Arrays.toString(arr));
        return l;
    }
}

在这里插入图片描述

5.希尔排序(Shell Sort)

import java.util.Arrays;

/**
 * 希尔排序
 *
 * 1. 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;(一般初次取数组半长,之后每次再减半,直到增量为1)
 * 2. 按增量序列个数k,对序列进行k 趟排序;
 * 3. 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。
 *    仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
 * @param arr  待排序数组
 */

public class Deom {
    
    
    public static void main(String[] args) {
    
    
        int[] arr = {
    
    2,1,6,9,5,4,20,19,11};
         shellSort(arr);
    }

    public  static  void shellSort(int[] arr){
    
    
        if(arr==null||arr.length==0){
    
    
            return;
        }
        int groud = arr.length/2;
        for(;groud>0;groud/=2){
    
    
            for(int i = 0;i+groud<arr.length;i++){
    
    
                for(int j = 0;j+groud<arr.length;j+=groud){
    
    
                    if(arr[j]>=arr[j+groud]){
    
    
                        int temp = arr[j];
                        arr[j] = arr[j+groud];
                        arr[j+groud] = temp;
                    }
                }
            }
        }
        System.out.println(Arrays.toString(arr));
    }

}


在这里插入图片描述

6、堆排序(Heap Sort)

代码实现:

import java.util.Arrays;

/**
 * 堆排序
 *
 * 1. 先将初始序列K[1..n]建成一个大顶堆, 那么此时第一个元素K1最大, 此堆为初始的无序区.
 * 2. 再将关键字最大的记录K1 (即堆顶, 第一个元素)和无序区的最后一个记录 Kn 交换, 由此得到新的无序区K[1..n−1]和有序区K[n], 且满足K[1..n−1].keys⩽K[n].key
 * 3. 交换K1 和 Kn 后, 堆顶可能违反堆性质, 因此需将K[1..n−1]调整为堆. 然后重复步骤②, 直到无序区只有一个元素时停止.
 * @param arr  待排序数组
 */


public class Demo {
    
    
    public static void main(String[] args) {
    
    
        int[] arr = {
    
    1,5,4,8,20,15,23,65,45};
        heapSort(arr);

    }


    public static void heapSort(int[] arr){
    
    
        for(int i = arr.length; i > 0; i--){
    
    
            max_heapify(arr, i);

            int temp = arr[0];      //堆顶元素(第一个元素)与Kn交换
            arr[0] = arr[i-1];
            arr[i-1] = temp;
        }
        System.out.println(Arrays.toString(arr));
    }

    private static void max_heapify(int[] arr, int limit){
    
    
        if(arr.length <= 0 || arr.length < limit) return;
        int parentIdx = limit / 2;

        for(; parentIdx >= 0; parentIdx--){
    
    
            if(parentIdx * 2 >= limit){
    
    
                continue;
            }
            int left = parentIdx * 2;       //左子节点位置
            int right = (left + 1) >= limit ? left : (left + 1);    //右子节点位置,如果没有右节点,默认为左节点位置

            int maxChildId = arr[left] >= arr[right] ? left : right;
            if(arr[maxChildId] > arr[parentIdx]){
    
       //交换父节点与左右子节点中的最大值
                int temp = arr[parentIdx];
                arr[parentIdx] = arr[maxChildId];
                arr[maxChildId] = temp;
            }
        }
        //System.out.println("Max_Heapify: " + Arrays.toString(arr));
    }

}

在这里插入图片描述
以上,
①. 建立堆的过程, 从length/2 一直处理到0, 时间复杂度为O(n);
②. 调整堆的过程是沿着堆的父子节点进行调整, 执行次数为堆的深度, 时间复杂度为O(lgn);
③. 堆排序的过程由n次第②步完成, 时间复杂度为O(nlgn).

7、归并排序(Merging Sort)

代码实现:
归并排序其实要做两件事:

分解:将序列每次折半拆分
合并:将划分后的序列段两两排序合并
因此,归并排序实际上就是两个操作,拆分+合并

如何合并?

L[first…mid]为第一段,L[mid+1…last]为第二段,并且两端已经有序,现在我们要将两端合成达到L[first…last]并且也有序。

首先依次从第一段与第二段中取出元素比较,将较小的元素赋值给temp[]
重复执行上一步,当某一段赋值结束,则将另一段剩下的元素赋值给temp[]
此时将temp[]中的元素复制给L[],则得到的L[first…last]有序

如何分解?

在这里,我们采用递归的方法,首先将待排序列分成A,B两组;然后重复对A、B序列
分组;直到分组后组内只有一个元素,此时我们认为组内所有元素有序,则分组结束。

import java.util.Arrays;

public class Demo {
    
    
    public static void main(String[] args) {
    
    
       int[] arr = {
    
    1,5,3,8,4,6,15,3,24};
        System.out.println("归并排序前"+Arrays.toString(arr));
        System.out.println("归并排序后"+Arrays.toString(mergeSort(arr)));
    }
//将一个序列,拆分成两个序列
    public  static  int[] mergeSort(int[] arr){
    
    
        if(arr.length<=1) return arr;
        int num = arr.length>>1;
        int[] leftarr = Arrays.copyOfRange(arr,0,num);
        int[] rightarr= Arrays.copyOfRange(arr,num,arr.length);
        return mergeTwoArray(mergeSort(leftarr),mergeSort(rightarr));
    }

    //将两个排好序列的短序列合并为一个序列
    public  static  int[] mergeTwoArray(int[] arr1,int[] arr2){
    
    
     int i =0;int j =0;int k=0;
     int[] result = new int[arr1.length+arr2.length];
     while (i< arr1.length&&j<arr2.length){
    
    
         if(arr1[i]<=arr2[j]){
    
    
             result[k++]= arr1[i++];
         }else {
    
    
             result[k++]=arr2[j++];
         }
     }
     while (i<arr1.length){
    
    
         result[k++] = arr1[i++];
     }
        while (j<arr2.length){
    
    
            result[k++] = arr2[j++];
        }
       return result;
    }
}

在这里插入图片描述
由上, 长度为n的数组, 最终会调用mergeSort函数2n-1次。通过自上而下的递归实现的归并排序, 将存在堆栈溢出的风险。

以下是归并排序算法复杂度:

8、基数排序(Radix Sort)

/**
 * 基数排序(LSD 从低位开始)
 *
 * 基数排序适用于:
 *  (1)数据范围较小,建议在小于1000
 *  (2)每个数值都要大于等于0
 *
 * ①. 取得数组中的最大数,并取得位数;
 * ②. arr为原始数组,从最低位开始取每个位组成radix数组;
 * ③. 对radix进行计数排序(利用计数排序适用于小范围数的特点);
 * @param arr    待排序数组
 */
public static void radixSort(int[] arr){
    
    
    if(arr.length <= 1) return;

    //取得数组中的最大数,并取得位数
    int max = 0;
    for(int i = 0; i < arr.length; i++){
    
    
        if(max < arr[i]){
    
    
            max = arr[i];
        }
    }
    int maxDigit = 1;
    while(max / 10 > 0){
    
    
        maxDigit++;
        max = max / 10;
    }
    System.out.println("maxDigit: " + maxDigit);

    //申请一个桶空间
    int[][] buckets = new int[10][arr.length-1];
    int base = 10;

    //从低位到高位,对每一位遍历,将所有元素分配到桶中
    for(int i = 0; i < maxDigit; i++){
    
    
        int[] bktLen = new int[10];        //存储各个桶中存储元素的数量

        //分配:将所有元素分配到桶中
        for(int j = 0; j < arr.length; j++){
    
    
            int whichBucket = (arr[j] % base) / (base / 10);
            buckets[whichBucket][bktLen[whichBucket]] = arr[j];
            bktLen[whichBucket]++;
        }

        //收集:将不同桶里数据挨个捞出来,为下一轮高位排序做准备,由于靠近桶底的元素排名靠前,因此从桶底先捞
        int k = 0;
        for(int b = 0; b < buckets.length; b++){
    
    
            for(int p = 0; p < bktLen[b]; p++){
    
    
                arr[k++] = buckets[b][p];
            }
        }

        System.out.println("Sorting: " + Arrays.toString(arr));
        base *= 10;
    }
}


在这里插入图片描述

2.2.死锁

public class Deadlock {
    
    
    public static void main(String[] args) {
    
    
        Object lock1 = new Object();
        Object lock2 = new Object();
        Thread thread1 = new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                synchronized (lock1){
    
    
                    System.out.println("线程一得到了lock1");
                    try{
    
    
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                    System.out.println("线程一获取lock2");
                    synchronized (lock2){
    
    
                        System.out.println("线程一得到了lock2");
                    }
                }
            }
        });
        thread1.start();


        Thread thread2 = new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                synchronized (lock2){
    
    
                    System.out.println("线程二得到了lock2");
                    try{
    
    
                        //让线程2,获取锁1
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                    System.out.println("线程二获取lock1");
                    //尝试获取lock1
                    synchronized (lock1){
    
    
                        System.out.println("线程二得到了lock1");
                    }
                }
            }
        });
        thread2.start();

    }
}

在这里插入图片描述
死锁解决方法推荐:https://editor.csdn.net/md/?articleId=116027470

2.3.更多手写代码常见题目

推荐学习:https://editor.csdn.net/md/?articleId=116036565

三 .java基础,数据库知识是主体

3.1.常见题目

1、 meta标签的作用是什么
2、 ReenTrantLock可重入锁(和synchronized的区别)总结
3、 Spring中的自动装配有哪些限制?
4、 什么是可变参数?
5、 什么是领域模型(domain model)?贫血模型(anaemic domain model)和充血模型(rich domain model)有什么区别?
6、 说说http,https协议
7、"= ="和equals方法究竟有什么区别?
8、&和&&的区别?
9、.super.getClass()方法调用?
10、10条SQL优化技巧
11、10道经典java面试题_实习生必问!
12、15个Java线程并发面试题和答案
13、15个高级Java多线程面试题及回答
14、2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据
15、2018年java分布式相关最新面试题
16、2018最新java技术面试题与答案
17、23种经典设计模式都有哪些,如何分类?
18、4个Spring常见面试题及答案解析
19、58到家MySQL数据库开发规范
20、9条改善Java性能的小建议
21、9道常见的java笔试选择题
22、abstract class和interface有什么区别
23、ActiveMQ是什么
24、activity是什么?
25、Ajax的最大的特点是什么
26、ajax的缺点
27、ajax请求时,如何解释json数据
28、ajax请求的时候get 和post方式的区别
29、Ajxa常见问题都有哪些
30、Anonymous Inner Class (匿名内部类) 是否可以extends(继承)其它类,是否可以implements(实 现)interface(接口
31、Anonymous Inner Class(匿名内部类)是否可以继承其它类?是否可以实现接口?
32、aop代理模式
33、ArrayList、Vector、LinkedList的区别
34、ArrayList与LinkedList的区别
35、ArrayList遍历时正确删除元素
36、Arrays.sort 实现原理和 Collection 实现原理
37、BeanFactory 和 ApplicationContext
38、BeanFactory 和 FactoryBean
39、BIO、NIO和AIO
40、break和continue的作用
41、C/S 与B/S 区别
42、CAS机制是什么?有什么缺点,会出现什么问题
43、char型变量中能不能存贮一个中文汉字?为什么?
44、Class类的作用?生成Class对象的方法有哪些?
45、Collection和Collections的区别?
46、Comparable和Comparator接口是干什么的?列出它们的区别
47、ConcurrenHashMap介绍1.8 中为什么要用红黑树
48、cookie和session的区别,分布式环境怎么保存用户状态
49、CSRF攻击防御方法
50、CSS3有哪些新特性?
上面是节选知识内容:答案在这里:java程序员800道基础面试题

四.数据库sql基础命令是基操

4.1. 创建数据库

代码如下(示例):

create  database database-name 

4.2.删除数据库

代码如下(示例):

drop database name

4.3.创建新表

create table tabname(col1 type1 [not null] [primary key],col2 type2 [not null],..)

根据已有的表创建新表: 

A:create table tab_new like tab_old (使用旧表创建新表)(在orcale中不能用)

B:create table tab_new as select col1,col2… from tab_old definition only

4.4.删除新表

drop table name;

5.5.数据库的基本操作必须掌握

增加一个列

   Alter table tabname add column col type

   注:列增加后将不能删除。DB2中列加上后数据类型也不能改变,唯一能改变的是增加varchar类型的长度。

添加主键

 Alter table tabname add primary key(col) 

 创建索引

create [unique] index idxname on tabname(col….) 

删除索引

drop index idxname

注:索引是不可更改的,想更改必须删除重新建。

 创建视图

create view viewname as select statement 

删除视图:

drop view viewname

 几个简单的基本的sql语句

选择:select * from table1 where 范围

插入:insert into table1(field1,field2) values(value1,value2)

Insert into table1 values(001,’sll’)

删除:delete from table1 where 范围

更新:update table1 set field1=value1 where 范围

查找:select * from table1 where field1 like%value1%’

表示模糊查询(匹配字符串)

排序:select * from table1 order by field1,field2 [desc]

总数:select count(*as totalcount from table1

求和:select sum(field1) as sumvalue from table1

平均:select avg(field1) as avgvalue from table1

最大:select max(field1) as maxvalue from table1

最小:select min(field1) as minvalue from table1

 使用外连接 

A、left outer join: 

  左外连接(左连接):结果集几包括连接表的匹配行,也包括左连接表的所有行。 

  sql: select a.a, a.b, a.c, b.c, b.d, b.f from a LEFT OUT JOIN b ON a.a =b.c

B:right outer join: 右外连接(右连接):结果集既包括连接表的匹配连接行,也包括右连接表的所有行。 

C:full outer join: 全外连接:不仅包括符号连接表的匹配行,还包括两个连接表中的所有记录。

D:等值连接 无条件连接,取两个表的笛卡尔积

in 的使用方法

select * from table1 where a [not] in (‘值1,’值2,’值4,’值6)

两张关联表,删除主表中已经在副表中没有的信息 

delete from table1 where not exists ( select * from table2 where table1.field1=table2.field1 )

5.6.JDBC编程原理与实现

推荐文章:全网最细JDBC编程

五.操作系统是灵魂

这可能是最全面的操作系统知识细了,一定要收藏!

https://blog.csdn.net/qq_36894974/article/details/115654242

六.网络知识是升华

6.1.TCP/IP知识

端口号(Port)标识了一个主机上进行通信的不同的应用程序;
在这里插入图片描述

在TCP/IP协议中, 用 “源IP”, “源端口号”, “目的IP”, “目的端口号”, “协议号” 这样一个五元组来标识一个通信(可以通过netstat -n查看);
在这里插入图片描述

端口号范围划分
0 - 1023: 知名端口号, HTTP, FTP, SSH等这些广为使用的应用层协议, 他们的端口号都是固定的.
1024 - 65535: 操作系统动态分配的端口号. 客户端程序的端口号, 就是由操作系统从这个范围分配的.
认识知名端口号(Well-Know Port Number)
有些服务器是非常常用的, 为了使用方便, 人们约定一些常用的服务器, 都是用以下这些固定的端口号: ssh服务器, 使用22端口
ftp服务器, 使用21端口telnet服务器, 使用23端口http服务器, 使用80端口https服务器, 使用443
我们自己写一个程序使用端口号时, 要避开这些知名端口号.
两个问题
1.一个进程是否可以bind多个端口号?
2.一个端口号是否可以被多个进程bind?

UDP协议
UDP协议端格式
在这里插入图片描述

16位UDP长度, 表示整个数据报(UDP首部+UDP数据)的最大长度; 如果校验和出错, 就会直接丢弃;
UDP的特点
UDP传输的过程类似于寄信.
无连接: 知道对端的IP和端口号就直接进行传输, 不需要建立连接;
不可靠: 没有确认机制, 没有重传机制; 如果因为网络故障该段无法发到对方, UDP协议层也不会给应用层返回任何错误信息;
面向数据报: 不能够灵活的控制读写数据的次数和数量;
理解 UDP 的 “不可靠”
在这里插入图片描述

面向数据报
应用层交给UDP多长的报文, UDP原样发送, 既不会拆分, 也不会合并; 用UDP传输100个字节的数据:
如果发送端调用一次sendto, 发送100个字节, 那么接收端也必须调用对应的一次recvfrom, 接收100个字节; 而不能循环调用10次recvfrom, 每次接收10个字节;
UDP的缓冲区
UDP没有真正意义上的 发送缓冲区. 调用sendto会直接交给内核, 由内核将数据传给网络层协议进行后续的传输动作;
UDP具有接收缓冲区. 但是这个接收缓冲区不能保证收到的UDP报的顺序和发送UDP报的顺序一致; 如果缓冲区满了, 再到达的UDP数据就会被丢弃;

UDP的socket既能读, 也能写, 这个概念叫做 全双工
UDP使用注意事项
我们注意到, UDP协议首部中有一个16位的最大长度. 也就是说一个UDP能传输的数据最大长度是64K(包含UDP首部).

然而64K在当今的互联网环境下, 是一个非常小的数字.

如果我们需要传输的数据超过64K, 就需要在应用层手动的分包, 多次发送, 并在接收端手动拼装;
基于UDP的应用层协议
NFS: 网络文件系统
TFTP: 简单文件传输协议
DHCP: 动态主机配置协议
BOOTP: 启动协议(用于无盘设备启动)
DNS: 域名解析协议
当然, 也包括你自己写UDP程序时自定义的应用层协议;
2.3 TCP协议
TCP全称为 “传输控制协议(Transmission Control Protocol”). 人如其名, 要对数据的传输进行一个详细的控制;
TCP协议段格式
在这里插入图片描述

源/目的端口号: 表示数据是从哪个进程来, 到哪个进程去; 32位序号/32位确认号: 后面详细讲;
4位TCP报头长度: 表示该TCP头部有多少个32位bit(有多少个4字节); 所以TCP头部最大长度是15 * 4
= 60
6位标志位:
URG: 紧急指针是否有效 1为紧急标志位
ACK: 确认号是否有效
PSH: 提示接收端应用程序立刻从TCP缓冲区把数据读走
RST: 对方要求重新建立连接; 我们把携带RST标识的称为复位报文段
SYN: 请求建立连接; 我们把携带SYN标识的称为同步报文段
FIN: 通知对方, 本端要关闭了, 我们称携带FIN标识的为结束报文段
16位窗口大小: 后面再说
16位校验和: 发送端填充, CRC校验. 接收端校验不通过, 则认为数据有问题. 此处的检验和不光包含
TCP首部, 也包含TCP数据部分.
16位紧急指针: 标识哪部分数据是紧急数据;
40字节头部选项: 暂时忽略;

6.2.1 TCP特性

TCP特性1:确认应答(保障TCP稳定的核心机制)

在这里插入图片描述

** TCP 特性2:超时重发**

在这里插入图片描述
在这里插入图片描述

TCP 特性3:三次握手,四次挥手

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

TCP 特性4:滑动窗口

窗口大小指的是无需等待确认应答而可以继续发送数据的最大值.上图的窗口大小就是4000.个字节(四个段).
发送前四个段的时候,不需要等待任何ACK,直接发送;
收到第一个ACK后,滑动窗口向后移动,继续发送第五个段的数据;依次类推;
操作系统内核为了维护这个滑动窗口,需要开辟发送缓冲区来记录当前还有哪些数据没有应答;只有确认应答过的数据,才能从缓冲区删掉;

在这里插入图片描述
在这里插入图片描述

TCP 特性5:流量控制

流量控制

   接收端处理数据的速度是有限的.如果发送端发的太快,导致接收端的缓冲区被打满,这个时候如果发送端继续发送,就会造成丢包,继而引起丢包重传等等一系列连锁反应.
因此TCP支持根据接收端的处理能力,来决定发送端的发送速度.这个机制就叫做流量控制(FlowControl);

接收端将自己可以接收的缓冲区大小放入TCP首部中的"窗口大小"字段,通过ACK端通知发送端;
窗口大小字段越大,说明网络的吞吐量越高;
接收端一旦发现自己的缓冲区快满了,就会将窗口大小设置成一个更小的值通知给发送端;发送端接受到这个窗口之后,就会减慢自己的发送速度;
如果接收端缓冲区满了,就会将窗口置为0;这时发送方不再发送数据,但是需要定期发送一个窗口探测数据段,使接收端把窗口大小告诉发送端.

接收端如何把窗口大小告诉发送端呢?回忆我们的TCP首部中,有一个16位窗口字段,就是存放了窗口大小信息;
那么问题来了,16位数字最大表示65535,那么TCP窗口最大就是65535字节么?
实际上,TCP首部40字节选项中还包含了一个窗口扩大因子M,实际窗口大小是窗口字段的值左移M位

TCP 特性6:拥塞控制
拥塞控制

虽然TCP有了滑动窗口这个大杀器,能够高效可靠的发送大量的数据.但是如果在刚开始阶段就发送大量
的数据,仍然可能引发问题.
因为网络.上有很多的计算机,可能当前的网络状态就已经比较拥堵.在不清楚当前网络状态下,贸然发送大
量的数据,是很有可能引起雪上加霜的.

TCP引入慢启动机制,先发少量的数据,探探路,摸清当前的网络拥堵状态,再决定按照多大的速度传输数
据;
此处引入一个概念程为拥塞窗口
发送开始的时候,定义拥塞窗口大小为1;每次收到一个ACK应答,拥塞窗口加1;
每次发送数据包的时候将拥塞窗口和接收端主机反馈的窗口大小做比较,取较小的值作为实际发送的窗口;
像上面这样的拥塞窗口增长速度,是指数级别的."慢启动"只是指初使时慢,但是增长速度非常快.
·为了不增长的那么快,因此不能使拥塞窗口单纯的加倍.
此处引入一个叫做慢启动的阈值
·当拥塞窗口超过这个阈值的时候,不再按照指数方式增长,而是按照线性方式增长

当TCP开始启动的时候,慢启动阈值等于窗口最大值;
·在每次超时重发的时候,慢启动阈值会变成原来的一半,同时拥塞窗口置回1;

少量的丢包,我们仅仅是触发超时重传;大量的丢包,我们就认为网络拥塞;
当TCP通信开始后,网络吞吐量会逐渐上升;随着网络发生拥堵,吞吐量会立刻下降;
拥塞控制,归根结底是TCP协议想尽可能快的把数据传输给对方,但是又要避免给网络造成太大压力的折中方案.
TCP拥塞控制这样的过程,就好像热恋的感觉

TCP 特性7:延迟应答

如果接收数据的主机立刻返回ACK应答,这时候返回的窗口可能比较小.
·假设接收端缓冲区为1M.一次收到了500K的数据;如果立刻应答,返回的窗口就是500K;·但实际上可能处理端处理的速度很快, 10ms之内就把50OK数据从缓冲区消费掉了;
。在这种情况下,接收端处理还远没有达到自己的极限,即使窗口再放大一些,也能处理过来;如果接收端稍微等一会再应答,比如等待200ms再应答,那么这个时候返回的窗口大小就是1M;
一定要记得,窗口越大,网络吞吐量就越大,传输效率就越高.我们的目标是在保证网络不拥塞的情况下尽量提高传输效率;
那么所有的包都可以延迟应答么?肯定也不是;
数量限制:每隔N个包就应答一次;
●时间限制:超过最大延迟时间就应答一次;
具体的数量和超时时间,依操作系统不同也有差异;一般N取2,超时时间取200ms;

2.11 TCP 特性8:捎带应答
在延迟应答的基础上,我们发现,很多情况下,客户端服务器在应用层也是"一发一收"的.意味着客户端给服务器说了"How are you",服务器也会给客户端回一个"Fine, thank you";
那么这个时候ACK就可以搭顺风车,和服务器回应的"Fine, thank you"一起回给客户端

6.3.1 面向字节流

创建一个TCP的socket,同时在内核中创建一个发送缓冲区和一个接收缓冲区;
调用write时,数据会先写入发送缓冲区中;
如果发送的字节数太长,会被拆分成多个TCP的数据包发出;
。如果发送的字节数太短,就会先在缓冲区里等待,等到缓冲区长度差不多了,或者其他合适的时机发送出去;
接收数据的时候,数据也是从网卡驱动程序到达内核的接收缓冲区;·然后应用程序可以调用read从接收缓冲区拿数据;
·另一方面, TCP的一个连接,既有发送缓冲区,也有接收缓冲区,那么对于这一个连接,既可以读数据,也可以写数据.这个概念叫做全双工
由于缓冲区的存在,TCP程序的读和写不需要——匹配,例如:
。写100个字节数据时,可以调用一次write写100个字节,也可以调用100次write,每次写一个字节;
。读100个字节数据时,也完全不需要考虑写的时候是怎么写的,既可以一次read 100个字节,也可以一次read—个字节,重复100次;

6.3.2 沾包问题

[八戒吃馒头例子]
首先要明确,粘包问题中的"包",是指的应用层的数据包.
。在TCP的协议头中,没有如同UDP一样的"报文长度"这样的字段,但是有一个序号这样的字段.·站在传输层的角度,TCP是一个一个报文过来的.按照序号排好序放在缓冲区中.
·站在应用层的角度,看到的只是一串连续的字节数据.
那么应用程序看到了这么一连串的字节数据,就不知道从哪个部分开始到哪个部分,是一个完整的应用层数据包.
那么如何避免粘包问题呢?归根结底就是一句话,明确两个包之间的边界.
。对于定长的包,保证每次都按固定大小读取即可;例如上面的Request结构,是固定大小的,那么就从缓冲区从头开始按sizeof(Request)依次读取即可;
对于变长的包,可以在包头的位置,约定一个包总长度的字段,从而就知道了包的结束位置;对于变长的包,还可以在包和包之间使用明确的分隔符(应用层协议,是程序猿自己来定的,只要保证分隔符不和正文冲突即可);
思考:对于UDP协议来说.是否也存在"粘包问题"呢?
·对于UDP,如果还没有上层交付数据,UDP的报文长度仍然在.同时, UDP是一个一个把数据交付给应用层.就有很明确的数据边界.
·站在应用层的站在应用层的角度,使用UDP的时候,要么收到完整的UDP报文,要么不收.不会出现"半个"的情况.

6.3.3 TCP异常情况

进程终止:进程终止会释放文件描述符,仍然可以发送FIN.和正常关闭没有什么区别.机器重启:和进程终止的情况相同.
机器掉电/网线断开:接收端认为连接还在,一旦接收端有写入操作,接收端发现连接已经不在了,就会进行reset.即使没有写入操作,TCP自己也内置了一个保活定时器,会定期询问对方是否还在.如果对方不在,也会把连接释放.
另外,应用层的某些协议,也有一些这样的检测机制.例如HTTP长连接中,也会定期检测对方的状态.例如QQ,在QQ断线之后,也会定期尝试重新连接.

6.4.1为什么TCP这么复杂?

因为要保证可靠性,同时又尽可能的提高性能.
可靠性:
校验和序列号(按序到达)确认应答
超时重发 连接管理 流量控制 拥塞控制
提高性能:
滑动窗口 快速重传 延迟应答 捎带应答
其他:
定时器(超时重传定时器,保活定时器,TIME_WAIT定时器等)基于TCP应用层协议
HTTP
HTTP SS SHT elnet FTPS MTP

6.4.2 TCP/UDP对比

我们说了TCP是可靠连接,那么是不是TCP一定就优于UDP呢?TCP和UDP之间的优点和缺点,不能简单,绝对的进行比较
TCP用于可靠传输的情况,应用于文件传输,重要状态更新等场景;
UDP用于对高速传输和实时性要求较高的通信领域。例如,早期的QQ,视频传输等.另外UDP可以用于广播;
归根结底, TCP和UDP都是程序员的工具,什么时机用,具体怎么用,还是要根据具体的需求场景去判定.用UDP实现可靠传输(经典面试题)
参考TCP的可靠性机制,在应用层实现类似的逻辑;例如:
引入序列号,保证数据顺序;
引入确认应答,确保对端收到了数据;
引入超时重传,如果隔一段时间没有应答,就重发数据;

八.总结

哈哈,我希望所有友友可以收藏,点赞,创作不易,我的总结虽然不是很全面,但是如果都掌握了,你只需要认认真真的在刷题,写代码,好好完善自己的简历,进大厂不是梦,希望每一个友友都可以找到理想的工作!

猜你喜欢

转载自blog.csdn.net/ILOVEMYDEAR/article/details/117351084