class A {
public static void main (String[] args) {
int[] arr = {2, 3, 4, 1, 7, 35, 15};
// 冒泡排序 相邻两个数据排列 最大数在最下边 for i是需要循环比较的次数
for (int i = 0; i < arr.length - 1; i++) {
// for j 是真正执行比较的代码
for (int j = 0; j < arr.length - 1; j++) {
// 相邻两数比较
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
System.out.println(Arrays.toString(arr));
// <---------------我是分割线-------------------->
// 选择排序 第一个数依次和后边的数进行比较
// i 与后边所有的数进行比较 并换位
for (int i = 0; i < arr.length-1; i++) {
// j=i+1 索引开始比较,起始位置是1
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));
}
}
- Arrays.sort(); // 数组排序
- Arrays.toString(); // 打印数组
- Arrays.asList(arr); //根据数组创建ArrayList<>集合;
- ArrayUtils.addAll(arr1,arr2); // 合并两个数组
- list.toArray(arr); //集合转为数组;**加粗样式**
- ArrayUtils.reverse(arr) ; //数组元素反转
- ArrayUtils.removeElement(arr,index) ; //移除元素,创建新的数组
JDK7之前使用的是**快速排序**;
JDK8之后使用的是**归并排序**;
1. 使用二分查找之前需要先对数组进行排序。
Arrays.sort();
2. 代码实现,定义方法实现二分查找 binarySearch();
// 传入参数为排序之后的arr[] + 搜索值 ;
public static int binarySearch(int[] arr, int target) {
// 二分查找 定义左右指针索引 left right
int left = 0;
int right = arr.length-1;
// 当right在left左边的时候(left > right),表示数组搜素完毕 停止搜索,所以搜索条件是left <= right;
while (left <= right) {
// 定义中值索引 mid ,防止溢出
int mid = left + (right - left) / 2;
if (arr[mid] == target) {
return mid;
} else if (arr[mid] < target) {
left = mid + 1;
} else if (arr[mid] > target) {
right = mid - 1;
}
}
return -1;
}
Throwable是所有异常的父类;
|-- exception 异常,可以使用throw抛出/try...catch进行捕获,异常一旦产生便不会继续执行下面的代码。
|-- runtimeException 运行时异常,程序会继续运行
|-- exception 编译时异常,此时程序不会继续编译
|-- error 错误,无法挽回
1. 系统会在出现异常的地方创建一个异常对象 new ArrayIndexOutOfBoundsException(),将其抛给方法调用者位置;如果调用者也没有处理这个异常,就会将异常抛给JVM。
2. JVM此时会将异常信息输出到控制台或者停止程序。
// 异常的抛出关键字
1. throw 手动向外抛出异常对象;throw new 异常类名();
2. object 通过null调用任何非静态方法或属性都会引发空指针异常; requireNonNull(T obj)
3. throws 进行方法的异常声明;在方法后面 + throws 异常类名(可以包含多个异常类名)
4. try...catch try{ 可能出现异常的代码 } catch ( 异常类名 变量名 ){};
|-- 如果try中没有任何异常,就会跳过catch()向下执行
|-- try中代码有异常,catch()捕获到了异常,此时会执行catch()代码中的代码(需要try中抛出的异常和catch中的异常类一致)
|-- 多catch情况下,那个catch先捕获到异常,就执行那个catch语句,其他的catch块则不会执行,然后再执行其他语句。
|-- 在多catch处理异常情况下,父类异常不能放在子类异常的前边(可以在catch最后使用Exception来捕获其他异常)
|-- try中代码有异常但是catch()没有捕获到异常,那么代码还是会向外抛出异常。
5. finally try{} catch(){} finally{ 一定会执行的内容,可以在里面添加释放资源的操作 }
// 编译时异常和运行时异常区别
- 编译时异常:如果编译时期会出现异常就必须处理,try...catch/throws
- 运行时异常: 编译时期出现
|-- 父类方法抛出了多个异常,子类重写父类方法,只能抛出相同异常或是它的子异常。
|-- 父类方法不抛出异常的话,子类方法重写父类方法也不能抛出该异常,只能进行try...catch处理
|-- 当多异常进行捕获处理时,前边抛出的异常不可是后边异常的父类;
|-- 多异常捕获使用比较多的是一次捕获,多次处理方式;
|-- try...catch中添加finally代码块,finally代码块中可以添加释放资源的操作;
// 捕获异常信息
-- 需要使用捕获到的异常对象来调用方法 e = new Exception()
void printStackTrace() -开发者看- 使用标准的错物流将异常信息输出到console System.err.println()
String getMessage() -给用户看- 获取异常信息的字符串,简短的异常信息
// 自定义异常
// 最终目的是:提高CPU利用率
进程:在内存中运行的程序就是进程,一个程序中至少有一个线程;
线程:程序中的执行单元就是线程,每个线程可以单独执行一个任务。
单线程程序:程序中只有一个线程,只能做一件事;
多线程程序:程序中有多个线程,可以同时做多个事;
线程需要由CPU调度才会去执行,一个CPU只能调度一个线程,CPU切换速度很快,可以看成是同时;
|-- 抢占式调度:线程随机抢占,主动权在CPU。
// 并发和并行
并发:同一时间,多个线程一起执行,
并行:真正意义上的多线程,同一时间点,多个线程一起执行。(必须有多个CPU的支持)
// main线程执行main方法
// Thread类 完成多线程操作
栈内存是私有的,每一个线程都有自己的栈内存来运行自己的方法;方法是由哪个线程执行的,就在哪个线程的栈内存中开辟空间。
堆内存:所有的线程都共同使用一个堆内存
// Thread中的方法
1.定义类继承Thread类,重写Thread类中的run()方法;
2.在另外一个类中通过创建继承Thread类的对象,来调用它的start()方法实现。
Thread();
Thread("线程名字")
getName();
setName();
currentThread(): 获取当前执行线程
sleep(long millis): 线程休眠
// Runnable接口
1.类实现Runnable接口,重写run(),在run()中定义要实现的内容;
2.创建 new Thread(new MyThread()).start(); new Thread()中传入实现Runnable接口的类的对象;调用线程的start()方法。
// 匿名内部类 某个类的子类只使用了一次
new Thread(new Runnable(){
重写run()方法
}).start();
只能保证可见性,不能保证原子性。
被volatile关键字修饰的变量,在被其他线程修改时,对于其他线程来说也是可见的。
1.修饰同步代码块
synchronized(锁对象){
...
}
锁对象是一个普通的java对象,可以是任何类型的。只起到一个标记的作用;
会牺牲效率;
2.修饰方法
|-- 如果同步方法是非静态的,锁是this,表示本类的一个对象;
|-- 如果是静态的,锁对象是 当前类名.class。
Lock接口需要使用实现类ReentrantLock。
Lock lock = new ReentrantLock();
在需要加锁的前边:lock.lock()进行加锁;
走完方法之后需要释放锁:lock.unlock();
JMM java memory model;
定义了线程对于共享变量的访问规则;
|-- 主内存:线程共享的数据是保存在主内存中
|-- 线程的工作内存:线程会将主内存复制一份到自己的工作内存,线程无法直接操作主内存数据;之后再将工作内存数据保存到主内存中;
线程内的数据是线程私有的,保存到主内存的数据是共享内存 -- > 堆区
多个操作是不可分割的整体。
count++ 不具备原子性。 ++ 并不是一个整体; 其中的某些操作会被插队。
可以使用synchronized关键字来对多线程操作的步骤进行同步,保证只有一个线程能拿到锁对工作空间以及主空间操作。
AtomicInteger 整数原子类 需要new AtomicInteger()之后调用下面的方法。
|-- get() 获取当前值
|-- getAndIncrement() 返回自增前的值 (可以保证保证原子性) -- count++
|-- incrementAndGet() 返回自增后的值
|-- getAndDecrement() (可以保证保证原子性) -- count--
// 类实现Runnable接口,需要将实现类的对象传到new Thread( 实现类对象 ).start();
// 继承Thread类,需要通过该类对象调用start()方法实现多线程。
compare and Swap 先比较再交换;
比较旧的预期值与主内存中的值是否相同,如果不是则表示主内存数据被修改。
内存地址保存值:V
旧的预期值:A
新的修改值:B
乐观锁/悲观锁:synchronized认为每次都会引发线程安全问题,只让一个线程去访问;可以看成是悲观锁;
CAS认为每次都不会发生线程安全问题,只有将数据保存到主内存中才会去验证该数据是否被其他线程操作,可以看成是乐观锁;
HashMap线程不安全;
Hashtable是线程安全的,对方法加了synchronized关键字来保证同步;效率低;==表锁==
ConcurrentHashMap线程安全,内部使用了CAS+分段锁。分段锁对桶进行的加锁,不会对其他桶加锁,可以实现多线程修改;
# CountDownLatch(int count)
await() 线程等待,直到计数器(count)的值到0;
countDown() 计数器值-1;
# 通过构造方法传入同一个参数,保证使用的是同一个CountDownLatch对象。
类需要继承Thread类;
需要同时在类中定义构造方法,并且传入(CountDownLatch 对象),在main方法中创建CountDownLatch对象,并设置计数器的值,然后new Task(countdownlatch).start; 传入countdownlatch对象。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aCLLa7eD-1608212449615)(C:\Users\76734\AppData\Roaming\Typora\typora-user-images\image-20201208210233152.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FKAcsR0Z-1608212449620)(C:\Users\76734\AppData\Roaming\Typora\typora-user-images\image-20201208210252946.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QGxc7Dsn-1608212449621)(C:\Users\76734\AppData\Roaming\Typora\typora-user-images\image-20201208210314232.png)]
让一组线程达到一个同步点时被阻塞,知道最后一个线程到达的时候,阻塞才会消失;
CyclicBarrier(int count,Runnable barrierAction),当有count个线程到达同步点的时候,会执行barrierAction方法
int await() 线程等待,会通知barrierAction已经到达同步点
count 是线程数; barrierAction 是需要执行的方法,需要实现Runnable接口
需要使用构造方法传参,使得变量都是一致的。
使用await()方法来告诉barrierAction,线程到齐了,可以执行方法了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kY8No7EP-1608212449624)(C:\Users\76734\AppData\Roaming\Typora\typora-user-images\image-20201208214341344.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xXLBOtpf-1608212449627)(C:\Users\76734\AppData\Roaming\Typora\typora-user-images\image-20201208214413404.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q5IHKxNt-1608212449629)(C:\Users\76734\AppData\Roaming\Typora\typora-user-images\image-20201208214427686.png)]
控制线程的并发数量;
Semaphore(int permits) 允许几个线程共同执行某段代码
acquire() 获取凭证
release() 释放凭证
Executor是根接口,一般使用ExecutorService,提供了线程工具类池:管理线程池的方法
线程池工具类:Executors,获取线程池
# ExecutorService newFixedThreadPool(int nThreads) 创建定长线程池
submit() 提交线程任务并执行
Future submit(Callable task)
shutdown() 销毁线程池
使用步骤:
|-- 通过Executors工具类获取线程池
|-- 使用submit(),提交线程任务
|-- 销毁线程池,一般不做。
线程池中的线程序号是从1开始的,pool-1-thread-1
Executors中的submit(Callable task)返回值是Future类型的,封装了结果,最后需要调用Future中的get()来取到结果;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LUExdaur-1608212449631)(C:\Users\76734\AppData\Roaming\Typora\typora-user-images\image-20201209081344458.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MGXgQt0y-1608212449632)(C:\Users\76734\AppData\Roaming\Typora\typora-user-images\image-20201209081358184.png)]
a=a+b,需要我们进行强制类型转换,否则会编译错误;
a+=b,则不需要
1. 封装:将类的某些信息隐藏在类内部,不允许外部直接访问,而是需要通过该类提供的方法来实现对类中属性的访问。private setter/gettrt
|-- 例如java的内部类
内部类的方法可以直接访问外部类的所有数据,包括私有数据;
内部类提供了更好的封装,不允许同一个包中的其他类访问该类。
2. 继承:继承是单继承,只有一个顶级父类
final修饰 不可被继承,
|-- 继承初始化顺序
1.初始化顺序:父类 子类
2.初始化对象中的属性,执行构造方法中的初始化
3. 多态: 抽象类
创建本类对象时,调用的方法为本类方法;
创建子类对象时,调用的方法为子类重写的方法/继承的方法;
4. 抽象 使用abstract修饰类
public abstract class Telephone(){
// 可以没有抽象方法
public void call(){
System.out.println("抽象方法");
}
// 抽象方法
public abstract void call();
}
继承抽象类的子类需要重写抽象方法。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SryswQp3-1608212449633)(C:\Users\76734\AppData\Roaming\Typora\typora-user-images\image-20201210161212508.png)]
#{}是预编译处理,${}是字符串替换。
预编译就是提前对SQL语句进行预编译,而其后注入的参数将不会再进行SQL编译。
SQL注入发生在编译的过程中,因为恶意注入了某些特殊字符,最后被编译成恶意的执行操作,预编译机制可以很好的防止SQL注入。
mybatis在处理#{}时,会将SQL中的#{}替换为?号,调用PreparedStatement的set方法来赋值;
mybatis在处理${}时,会将${}替换成变量的值;但是表名只能用${}来替换。
String中的split(",")方法, 会将字符串进行分割,最后到一个数组中; --> 可以分割
但是split(".")会返回一个空数组[],原因是"."会跟正则的符号冲突,此时需要进行转译操作;
详细操作如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VXCCQOww-1608212449634)(C:\Users\76734\AppData\Roaming\Typora\typora-user-images\image-20201210164002539.png)]
第一次握手:建立连接时,客户端发送syn包到服务器,进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到syn包,必须确认客户的SYN,同时自己也发送一个SYN包,SYN+ACK,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,想服务器发送确认包ACK,发送完毕之后,客户端和服务器进入ESTABLISH状态,完成三次握手;
三次握手最主要目的是为了保证连接是双工的,可靠更多的靠的是重传机制。
// 客户端与服务器需要请求响应一次才可以关闭连接
为了使在部分节点失效或者大部分节点无法通信的情况下集群仍可用,所以集群使用了主从复制模型,每个节点都会有N-1个复制品;
异步复制。
16384个。
目前无法做选择,默认在0数据库。
redis事务是一个单独的隔离操作:事务中的所有命令都会被序列化、按顺序的执行。事务在执行的过程中,不会被其他客户端发送来的请求打断。事务是一个原子性操作:要么同时成功,要么同时失败;
expire(设置过期时间)和persist(持久化)。
尽可能使用散列表(hashtable),使用的内存非常小;可以将用户所有信息存储到一张散列表里面;
RDB和AOF。
开启AOF:appendonly no —> 修改为 appendonly yes,之后会生成一个appendonly.aof文件
设置持久化时机:appendfsync everysec
RDB是在指定的时间间隔内将内存中的数据集写到磁盘中,是默认的持久化方式,文件名为dump.rdb。可以恢复大数据,恢复速度快,但是恢复完整性没有AOF那么好。
AOF机制:会记录每一个写命令,保存到.aof文件中,所以文件会比较大;为了压缩AOF文件,redis提供了bgrewriteaof命令。每秒一次同步。
故意查询一个一定不存在的key,请求量很大,会对后端DB造成很大压力,即缓存穿透;
如何避免:如果一个查询返回的数据为空,就把这个空结果缓存,设置一个过期时间,最长不超过五分钟;
设置缓存时设置了相同的过期时间,导致缓存在同一时间同时失效,请求全部转发到DB,DB崩溃。
解决方法:可以在原有的失效时间上增加一个随机值,比如1-5分钟随机,expire不在要同一个点上。
对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发访问,成为热点数据。
解决方法:设置热点数据永不过期
物理不过期:redis不设置过期时间即可;
功能不过期:将过期时间存在key的value中,如果发现快要过期了,通过后台的异步线程进行缓存的构建;
有两种。通过REST接口调用服务的http接口,参数和结果默认都是通过Jackson序列化和反序列化;
可以做:授权、监控、分发路由、负载均衡、缓存、请求分片、管理、静态响应处理等;
API 网关负责请求转发、合成和协议转换。所有来自客户端的请求都要先经过API gateway,然后路由这些请求到对应的微服务。
一般用作系统的参数配置,需要满足:高效获取、实时感知、分布式访问。
zookeeper配置中心:将数据加载到内存方式解决高效获取的问题,借助zookeeper的节点监听机制来实现实时感知。(Watcher)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OH8vLCdA-1608212449635)(C:\Users\76734\AppData\Roaming\Typora\typora-user-images\image-20201211150351060.png)]
消息服务和事件的统一调度,常用kafka,mq
实现请求从一个微服务到下一个微服务的传播过程,Spring Cloud Sleuth在日志中引入唯一ID,保证微服务调用之间的一致性,通过此方式来跟踪请求的传递路线。
请求到达分布式系统的入口端点时,需要为其创建一个唯一的跟踪标识,同时在分布式系统内部流转的时候,会一直保持该值的传递,知道返回给请求放为止。TraceID
三种状态:全开 半开 关闭
微服务框架中通常会有多个服务层调用,基础服务的故障可能导致级联故障,进而造成整个系统不可用的情况,即服务雪崩效应:因服务提供者不可用导致服务消费者的不可用,并将不可用逐渐放大的过程。
熔断器可以实现快速失败,如果在一段时间内侦测到许多类似的错误,会强迫其以后的多个调用快速失败,不再访问远程服务,从而防止应用程序不断的尝试执行可能会失败的操作。
**断路器机制:**当请求后端服务失败数量超过一定比例(默认50%)断路器会切换到开路状态Open,此时所有的请求会直接失败而不会发送到后端服务。
断路器保持开路状态一段时间后(默认5秒)自动切换到半开路状态,此时会判断下一次请求的返回情况,如果请求成功,断路器切回闭路状态Closed,否则重新切换到开路状态。
SwaggerAPI 管理工具。
1. 内置Tomcat,无需部署war文件;
2. 简化Maven配置;
3. 创建独立的Spring应用程序
4. 自动配置Spring
分为一级缓存和二级缓存:
默认情况下一级缓存是开启的且不能关闭。
一级缓存指SQLSession级别的缓存(本地缓存,缓存使用的数据结构是一个map)。当在同一个SQLSession中进行相同的SQL语句查询时,第二次之后的查询不会从数据库查询,可以直接从缓存中获取,一级缓存最多缓存1024条SQL。当出现commit操作时,需要将SQLSession中的一级缓存区域全部清空,下次查询需要从数据库查,之后写入本地缓存。
|-- key:MapperID + offset + limit +Sql + 所有入参
value:用户信息
二级缓存指可以跨SQLSession的缓存(全局缓存),是mapper级别的缓存。mapper以命名空间为单位创建缓存数据结构,结构是map。mybatis的二级缓存是通过CacheExecutor实现的。
配置:mybatis全局配置中启用二级缓存配置;
在对应的Mapper.xml中配置cache节点;
在对应的select查询节点中添加uesCache = true;
ACID:原子性Atomic ,一致性(Consistency) 隔离性(Isolation) 持久性(Durability)
面向切面编程,将影响多个类的公共行为封装到一个可重用模块,将其命名为Aspect。将与业务无关,但是被业务模块共同调用的逻辑封装起来,减少代码重复率,降低模块之间的耦合。比如权限认证、日志。
-- xml getter/setter 构造函数 注解方式来实现,最简单的是注解方式
IOC容器管理bean的创建以及实例化,降低类与类之间的耦合性,当想要对象的时候可以直接去IOC容器中获取;
singleton:IOC容器中只会存在一个共享的bean实例。
<bean id="userDao" class="com.ioc.UserDaoImpl" scope="singleton"> </bean>
prototype:原型模式,每次通过Spring容器获取prototype定义的bean时,容器都将创建一个新的Bean实例,每个Bean实例都有自己的属性和状态。对有状态的bean使用prototype作用域,对无状态的bean使用singleton作用域;
Request:一个request一个实例。不同Http请求会产生新的Bean,仅在当前http请求有效,请求结束,则bean的实例也会被销毁;
<bean id="loginAction" class="com.cn.Login" scope="request" />
session:
global Session:
调用对象的getClass();
Person p = new Person();
Class clazz = p.getClass();
调用类的class属性来获取该类对应的Class对象; Class clazz = Person.class;
使用Class类中的forName()静态方法(安全性/性能最好)
Class clazz = Class.forName("类的全路径"); -- 最常用的方法
// .newInstance()
Class clazz = Class.forName("类的全路径");
Person p = clazz.newInstance(); -- 类对象调用newInstance()来创建对象
// 获取构造方法来和创建对象
constructor c = clazz.getDeclaredConstructor(String.class,String.class,int.class) -- 获取类的构造函数,构造参数
Person p = (Person)c.newInstance("张三","男",32);
(new Bird(){
@Override // 重写方法
public int fly(){
return 2;
}
})
//
因特网整个TCP/IP协议族。TCP/IP由四个层次组成:网络接口层,网络层,传输层、应用层。