目录
一、线程(Thread)
1.引言
我们的代码都是从上向下执行的,但如果需要同时干两件事,就需要用多线程进行解决。
2.并发与并行
了解线程需要知道两个概念:
并发:两个或多个事件同一个时间段内发生;
并行:两个或多个事件在同一时刻发生。
3.多线程
在操作系统中,安装有多个程序,每个程序运行系统都会分配一定的内存空间,每个程序也已有多个进程,每个进程至少有一个线程,每个线程分配一个程序执行的任务,这就是多线程。
4.线程与进程的区别
进程:是系统分配资源的最小单位,每个进程都有独立的内存空间,进程包含线程。
线程:是系统调度的最小单位,线程与进程之间不共享内存空间,同一个进程中的线程之间共享一个内存空间。
在宏观角度上线程是并行的,在微观角度线程依旧是串行的。
5.创建线程的方法
5.1继承Thread类重写run方法
class MyThread extends Thread {
@Override
public void run() {
System.out.println("这里是线程运行的代码");
}
}
5.2实现Runnable接口重写run方法
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("这里是线程运行的代码");
}
}
5.3匿名内部类创建Thread子类对象
Thread t1 = new Thread() {
@Override
public void run() {
System.out.println("使用匿名类创建 Thread 子类对象");
}
};
5.4匿名内部类创建Runnable子类对象
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("使用匿名类创建 Runnable 子类对象");
}
});
5.5lambda表达式创建Runnable子类对象
Thread t3 = new Thread(() -> System.out.println("使用匿名类创建 Thread 子类对象"));
Thread t4 = new Thread(() -> {
System.out.println("使用匿名类创建 Thread 子类对象");
});
6.前台线程和后台线程
用new Thread方式创建的线程都是前台线程,前台线程能够阻止进程的终止,后台线程不能阻止进程的终止。
当所有的前台线程都终止后,进程也会被终止,进而所有的后台线程都会停止。
二、SpringBoot SpringMVC区别
1.引言
他们都是spring中的组件,产生时间不同。springMVC产生更早,其中企业中常用的ssm框架使用的就是springMVC。springBoot通过“约定大于配置”来减少许多配置,大大提高了生产力。
2.springMVC
2.1概念
springMVC诞生于servlet之后,为了将其封装可以简化开发难度,开发人员不需要处理HttpRequest,只需要关注业务处理,他也能够进行切面封装,加入全局异常处理器。SpringBoot可以返回一个渲染之后的页面。
2.2注解
@Controller 主要标注Controller层(控制层)
@RequestMapping 用来处理请求地址的映射,指定父路径
@Service 标注业务层
@Repository 标注数据库访问组件,DAO组件
@Conmponent 组件,组建不好归类时使用
@Autowired 是Spring注解,自动装配Bean,并自动注入到相应的地方。
@Resource 是J2EE注解,通过指定的类型去匹配bean(建议使用)
@Component-scan 默认扫描所有子包,将含有标签的类自动装配到spring容器中。
3.springBoot
3.1概念
springBoot解决了我们之前需要配置上下文等多个配置任务,只需要将版本交给SpringBoot去管理,就像官网描述的“JUST RUN”。
3.2注解
@SpringBootApplication springboot自动去启动
@Configuration 相当于传统的xml配置文件
@Bean @Value @PathVariable 获取参数
@RestMapping = @ResponseBody+@Controller
三、排序
1.图表展示
2.具体介绍
2.1冒泡排序
比较相邻的元素,如果第一个比第二个大,就交换它们两个。
因为每次只是相邻的进行交换,相等的两个元素可以不交换,所以整体是稳定的。
n个元素每次都要从第一个比较到最后一个元素,并且需要比较n次,所以时间复杂度为O(n方);不需要额外的空间,时间复杂度为O(1)。
当元素有序时,是最好的状态,依然需要n次比较,最好的时间复杂度为O(n);最坏时间复杂度为O(n方)。
public class BubbleSort {
public static void bubbleSort(int[] arr) {
int len = arr.length;
for (int i = 0; i < len - 1; i++) {
boolean flag = true;
for (int j = 0; j < len - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
flag = false;
}
}
if (flag) {
break;
}
}
}
}
2.2选择排序
- 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置;
- 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾;
- 重复第2步,直到所有元素均排序完毕。
每次选择最小的元素放在最前面,有可能会出现相等元素的第二个元素被确定为最小的放在最前面的情况,所以相等元素的位置发生了变化,不稳定。
每次都要找到未排序队列的最大或者最小值,所以时间复杂度为O(n方);不需要额外的空间,空间复杂度为O(1).
在序列有序的情况下,即使最近的元素是当前序列的最小或者最大的值,但是仍然需要比较到最后一个元素,所以最好和最坏的结果都为O(n方)。
public class SelectionSort {
public static void selectionSort(int[] arr) {
int len = arr.length;
for (int i = 0; i < len - 1; i++) {
int minVal = i;
for (int j = i + 1; j < len; j++) {
if (arr[minVal] > arr[j]) {
minVal = j;
}
}
if (minVal != i) {
int tmp = arr[i];
arr[i] = arr[minVal];
arr[minVal] = tmp;
}
}
}
}
2.3插入排序
- 首先从第一个元素开始,该元素被认为是有序的;
- 取出下一个元素,在已经排序的元素序列中从后往前进行扫描;
- 如果该已排好序的元素大于新元素,则将该元素移到下一位置;
- 重复步骤3一直往前进行扫描比较,直到找到已排序的元素小于或者等于新元素的位置;
- 将新元素插入到该位置后;
插入元素偶尔会因为插入排序而相离,但总体上是会越来越靠近,所以是相对稳定的。
因为不占用额外的空间,所以空间复杂度为O(1);时间复杂度为O(n方)。
当序列有序时,每个元素不需要移动位置至于要与前一个元素比较一次,最好时间复杂度为O(n);当序列逆序时为最坏结果,最坏时间复杂度为O(n方)。
public class InsertionSort {
public static void insertionSort(int[] arr) {
for (int i = 1; i < arr.length; i++) {
int val = arr[i], j = i;
while (j > 0 && val < arr[j - 1]) {
arr[j] = arr[j - 1];
j--;
}
arr[j] = val;
}
}
}
2.4希尔排序
一般,增量gap=length/2,缩小增量继续以gap = gap/2的方式,这种增量选择我们可以用一个序列来表示,{n/2, (n/2)/2, …, 1},称为增量序列。最后缩短为gap = 1,每相邻两个进行插入排序。
因为一次插入排序是稳定的,但希尔排序是多次插入排序所以是不稳定的。
希尔排序的时间复杂度是O(n的1.3次方),因为不占用多余的空间所以空间复杂度为O(1).
希尔排序如果是有序时是最好的结果,在内部进行插入排序时只需要与前一个元素比较大小,最好的时间复杂度为O(n);当序列为逆序时,最坏时间复杂度为O(n方)。
public class ShellSort {
public static void shellSort(int[] arr) {
int len = arr.length, tmp, j;
for (int gap = len / 2; gap >= 1; gap = gap / 2) {
for (int i = gap; i < len; i++) {
tmp = arr[i];
j = i - gap;
while (j >= 0 && arr[j] > tmp) {
arr[j + gap] = arr[j];
j -= gap;
}
arr[j + gap] = tmp;
}
}
}
}
2.5归并排序
1.将长度为n的待排序列分成两个长度为n/2的子序列,递归进行调用进行分割,直到每个子序列中只有一个元素;
2.此时的每个子序列被认为是有序的,然后递归调用的返回子序列进行两两合并;
3.合并过程中完成排序操作,具体操作为设定两个指针,分别指向两个已经排序子序列的起始位置;
4.比较两个指针所指向的元素,选择相对小的元素放入到合并返回的数组,并移动指针到下一位置;
5.重复步骤3~4直到某一指针达到序列尾;
6.将另一序列剩下的所有元素直接复制到合并序列尾,最终得到的新序列就是有序序列。
归并排序的稳定性主要取决于两个子序列合并时对相等元素的处理,如果将原序列的第一个元素先放进序列里,那么归并排序就是稳定的。它的最好最坏时间复杂度都是O(nlogn),当归并时需要放进新序列中所以空间复杂度为O(n)。
import java.util.Arrays;
public class MergeSort {
public static int[] mergeSort(int[] arr) {
int len = arr.length;
if (len < 2) {
return arr;
}
int mIdx = len / 2;
return merge(mergeSort(Arrays.copyOfRange(arr, 0, mIdx)), mergeSort(Arrays.copyOfRange(arr, mIdx, len)));
}
private static int[] merge(int[] arrLeft, int[] arrRight) {
int leftLen = arrLeft.length, rightLen = arrRight.length, leftIdx = 0, rightIdx = 0, idx = 0;
int[] result = new int[leftLen + rightLen];
while (leftIdx < leftLen && rightIdx < rightLen) {
if (arrLeft[leftIdx] < arrRight[rightIdx]) {
result[idx++] = arrLeft[leftIdx++];
} else {
result[idx++] = arrRight[rightIdx++];
}
}
while (leftIdx < leftLen) {
result[idx++] = arrLeft[leftIdx++];
}
while (rightIdx < rightLen) {
result[idx++] = arrRight[rightIdx++];
}
return result;
}
}
2.6快速排序
从序列中随机挑出一个元素,做为基准(pivot,这里选择序列的最左边元素作为基准);
重新排列序列,将所有比基准值小的元素摆放在基准前面,所有比基准值大的摆在基准的后面。该操作结束之后,该基准就处于数列的中间位置。这个操作称为分区(partition);
递归地把小于基准值元素的子序列和大于基准值元素的子序列进行上述操作即可。
快速排序过程中可能会调换两个相同元素的位置,所以是不稳定的。
快速排序的平均时间复杂度和最好空间复杂度、空间复杂度都是O(nlogn),最坏时间复杂度为O(n方)。
public class QuickSort {
public static void quickSort(int[] arr) {
sort(arr, 0, arr.length - 1);
}
private static void sort(int[] arr, int left, int right) {
if (left < right) {
int pivotIdx = partition(arr, left, right);
sort(arr, 0, pivotIdx - 1);
sort(arr, pivotIdx + 1, right);
}
}
private static int partition(int[] arr, int left, int right) {
int idx = left + 1;
for (int i = idx; i <= right; i++) {
if (arr[left] > arr[i]) {
swap(arr, i, idx++);
}
}
swap(arr, left, idx - 1);
return idx - 1;
}
private static void swap(int[] arr, int idx1, int idx2) {
int tmp = arr[idx1];
arr[idx1] = arr[idx2];
arr[idx2] = tmp;
}
}
2.7堆排序
将待排序列(R0, R1, ……, Rn)构建成最大堆(最小堆);
将堆顶元素R[0]与最后一个元素R[n]进行交换,此时得到新的无序区(R0, R1, ……, Rn-1)和新的有序区(Rn),且满足R[0, 1, ……, n-1]<=R[n](>=R[n]);
由于调整后的新堆可能违反堆的性质,因此需要对当前无序区(R0, R1, ……, Rn-1)进行调整;
重复步骤2~3直到有序区的元素个数为n。
堆排序显然是不稳定的。
堆排序平均时间复杂度、最好时间复杂度、最坏时间复杂度都为O(nlogn),不占用任何额外空间所以空间复杂度为O(1).