java知识体系
2022-10-08 10:26:32 24 举报
AI智能生成
Java知识
作者其他创作
大纲/内容
数据结构
数据结构
链表
单链表
双链表
数组
树
算法
操作系统和计算机基础
cpu
内存
硬盘
内核态和用户态
操作系统
零拷贝
设计模式
单例模式
策略模式
工厂模式
观察者模式
。。。
架构
微服务
DDD领域建模
单体架构
zk
数据存储
mysql
执行 过程
索引
索引基本知识
索引优点
减少数据服务器需要扫描的数据盘次数,也就是减少io的此时
帮助服务器避免排序和创建临时表
将随机io变为顺序io
快速查询where条件的行
减少分组和排序的时间
在查询过程中使用优化器提高查询效率
可以在表连接查询的时候减少扫描的行数
索引用处
频繁作为查询条件的字段,并且字段的值比较散列
经常使用表连接的字段
经常需要使用范围查找的字段
经常需要排序的字段
不使用索引的字段
表记录很少
增删改比较频繁的字段
维护索引成本较高,这个需要平衡
字段的值不散列的字段,例如status 可能只有0、1两个值
索引分类
主键索引
唯一非空索引
INNODB如果没有创建主键和唯一键,存储引擎会自动生成一个6bit的row_id
会涉及到回表
唯一索引
唯一值,值可为空
普通索引
没有唯一性,可为空
全文索引
查找文本中的关键词
组合索引
最左匹配原则
查询从索引的最左侧的列开始并且不跳过中间列
组合索引 (a,b) where a=? and b=? 使用索引
where a=? 使用索引
where b=? 不使用索引
where b=? and a=? 使用索引 因为mysql的server有优化器,会对查询语句进行优化
组合索引 (a,b,c)
where a=? and b=?and c=? 使用索引
where a=? and b>? and c=? a命中索引 b会使用索引 c不会使用索引
where a=? and c=? a命中索引 c不会使用索引
where b=3 order by a 都不使用索引
where a=3 order by c a使用索引 c不走索引
聚簇索引
innodb通过B+树对主键创建索引,叶子节点会存储数据
非聚簇索引
myisam的主键索引叶子节点存储的是数据在磁盘的地址,根据磁盘地址再去查询数据
普通索引的叶子节点存储的数据是主键的值,也属于非聚簇索引
技术名词
回表
innodb的普通索引的叶子节点存储的是表的主键的值,通过普通索引的树查询到主键值以后还需要到主键索引的树查询数据
覆盖索引
在普通索引的数据中就能查询到数据不需要回表
执行计划中extra列的值为using index
例如select id from table where a = ?
索引下推 5.6开始
执行计划的extra列using index condition
举例1 :创建组合索引(name,age)执行select * from table where name like '?%' and age = ? ; 5.6之前现根据name把所有数据查询出来,然后在server层进行挨个字段的数据筛选 5.6之后会存储引擎根据name和age两个字段都做筛选,将符合条件的数据返回给server
索引失效和优化
like操作中,如果百分号出现在左边就会索引失效,例如 name like '%张' 或是 name like '%张%'
查询条件字段发生了类型转换,例如 phone为varchar类型,但是使用phone=13333333333 这样也可以查询到数据,但是索引是失效的
where中除了主键索引以及整形字段索引以外使用<、>、!=
使用or但是只有其中一个字段没有创建索引,例如 name = 'zhangsan' or age = 20 如果name有索引但是age没有索引就会不命中索引
对条件字段进行计算或是使用函数的,例如 age -1 =20 或是 left(phone,3)=189
使用not in
使用is null 或是is not null当查询的数量达到总表的30%以上
组合索引中的字段不使用最左侧的字段做查询条件的
排序的索引
EXPLIAN 的extra字段为Using filesort说明排序未使用索引
index name pk id
如果select age from table order by name ,这时候索引是失效的,explian的type是ALL,查询的列没有索引,但是排序的列有索引,索引失效
如果是select * from table order by name 索引失效
select * from table order by id 索引失效 explian 的type为index
select name from table order by id 索引生效
如果where中条件字段有索引而且orderby的字段也有索引,那么where中的索引是生效的,orderby中的索引失效的
如果orderby的列是多列,那么列的顺序要跟组合索引的顺序一致才可以使索引生效
表关联
尽量给被驱动表的关联字段创建索引
例如:select * from class left join user on class.id=user.classid --如果两个表都没有创建索引则会进行全表扫描,扫描的行数为两个表做笛卡尔积(class 20行 user 30行 就是20*30) --如果class index id ;user index classid 则user的classid索引有效,物理扫描的行数为20*1
innerjoin中优化器会优化选择表的顺序,一般情况吧小标放到前面可减少物理扫描行数
例如 select * from class inner join user on class.id = user.classid alert table class add index X(id) --索引优化,MySQL会自己选择驱动表和被驱动表 --一张大表一张小表,大表创建索引然后放到被驱动表的位置,有利于减少物理行数扫描
子查询
尽量不使用not in 或是not exisit,可以使用left join on xxx is null
select * from emp e where e.eid not in (select d.eid from dept d where d.eid is not null);-- 优化子查询:确保索引不失效select * from emp e left join dept d on e.eid=d.eid where d.eid is null;
单列的索引和组合索引同时存在的时候组合索引优先生效
索引的命中和扫描的数据量还有查询的类型收到MySQL优化器等因素的影响,具体情况需要更具体的分析
索引数据结构选择
哈希表
哈希索引可以在O(1)的时间复杂度内查到单条记录,但是数据是无序的
由于数据的存储不是按照哈希值的顺序存储的,所以无法用于排序和分组
只能支持精确的单条查找,无法用于部分查找和范围查找
存在哈希冲突的问题,依然还是需要遍历对应的链表
红黑树和二叉树
红黑树和二叉树单节点只能存一个数据,数据量过大还是会导致树的层数太多
b树
所有的键值都在整颗树的节点上
查询可能在非叶子节点结束
每个节点最多有m棵子树(m代表树的阶)
所有叶子节点在同一层,每个节点有m-1个key
每个节点都会存储key和data,每个节点的存储空间大小是固定的,如果data较大,则会导致每个节点key的个数变小,从而导致树的层数增加,增加查找的io次数
b+树
非叶子节点只存储key,叶子节点存储key和data
叶子节点的之间是有指针相互链接,构成了链式的循环结构,兼顾了顺序查询的性能
每个节点最多有m棵子树(m代表阶数)
所有的叶子节点在同一层,最多有m-1个key
存储数据是有序的,提高排序和分组的效率
假设索引字段类型是Bigint,8bit,每两个元素之间存的是下一个节点的地址,mysql分配的是6bit,也就是说一个索引后面配对一个节点地址,成对出现,可以算一下16K的节点可以存多少对也就是多少个索引,8b+6b=14b,16K /14b=1170个索引,叶子节点有索引有data元素,假设占1K,那一个节点就放16K/1K=16个元素,假设树高是3,所有节点都放满,能放多少数据?可以算一下,1170*1170*16=21902400,2千多万,mysql设置16K的大小,数据就可以存2千多万就已经足够了吧,既能保证一次磁盘IO不要Load太多的数据 又能保证一次load的性能,即便表的数据在几千万的数量也能保证树的高度在一个可控的范围。
索引监控
show status like 'Handler_read%';
参数解释
Handler_read_first 读取索引第一个条目的次数,如果这个值很高说明全索引扫描很多
Handler_read_key 一个索引被使用的次数
Handler_read_last 读取索引最后一列的次数
Handler_read_next 通过索引读取索引字段下一字段的次数
Handler_read_prev 通过索引读取索引的上一条字段数据的次数
Handler_read_rnd 从固定位置读取行的次数,如果值比较高说明大量的结果集进行了排序或是全表扫描、关联查询没有用到合适的key
Handler_read_rnd_next 从数据节点读取下一条数据的次数
redolog
binlog
undolog
MVCC
事务隔离
锁机制
排它锁
共享锁
间隙锁
读写分离
主从复制
故障恢复
集群
redis
数据类型
持久化
哨兵模式
集群模式
分布式锁
es
MongoDB
java基础
面向对象
继承
不可继承
构造器
子主题
封装
合理隐藏,合理暴露
多态
基础数据类型
自动装箱拆箱
包装类
8个基本数据类型
String
substring
StringBuffer
StringBulider
字符串常量池
集合框架
map
HashMap
putval
getval
resize
hash
key的哈希值无符号右移16位 (h = key.hashCode()) ^ (h >>> 16)
下标计算
(n - 1) & hash 数组长度减1与哈希值进行与计算
tableSizeFor
Treemap
HashTable
linkedHashMap
Collections
Collection
List
ArrayList
数组
内存连续
查找快
插入慢
扩容机制
LinkList
链表
查找较慢
子主题
插入删除快
Vector
set
TreeSet
HashSet
LinkedHashSet
Queue
concurrent集合
ConcurrentHashMap
线程 安全的原理
putval
getval
spread
ConcurrentLinkedDeque
ConcurrentLinkedQueue
CopyOnWriteArrayList
CopyOnWriteArraySet
枚举
io
aio
bio
nio
netty
字节流、字符流
输入流、输出流
反射
动态代理
jdk动态代理
实现接口的方式
生成了一个$Proxy0的类、
cglib动态代理
继承的方式
自己模拟一个动态代理?
自己模拟aop
静态代理
序列化
注解
多线程
thread
线程的状态以及转换
run和start的区别
调用run是同步串行进行执行的,先执行线程的代码,然后继续在往下执行
start是异步执行线程的代码,主程序继续往下执行,会有新的线程执行线程的代码
创建线程的三种方式
实现Runnable接口
减少单继承带来的编码不方便
实现runnable接口生成的线程对象可以共享成员变量资源
class MyThread2 implements Runnable{
@Override
public void run(){
System.out.println("实现runnable");
}
}
new Thread(new MyThread2()).start();
@Override
public void run(){
System.out.println("实现runnable");
}
}
new Thread(new MyThread2()).start();
继承Thread
线程独立运行
class MyThread extends Thread{
@Override
public void run(){
System.out.println("继承thread");
}
}
new MyThread().start();
@Override
public void run(){
System.out.println("继承thread");
}
}
new MyThread().start();
lambda表达式
new Thread(()->{
System.out.println("lambda");
}).start();
System.out.println("lambda");
}).start();
面试题
启动线程的三种方式
thread
runnable
Executeors.newCachedRhread
方法
sleep
yield
暂时让出cpu,进入同步队列,就绪状态,然后等待cpu,
join
t1线程中调用t2的join方法,此时t1等待t2执行完后在继续执行t1
打断interrupt
打断后会抛出一个异常,可以catch住异常然后处理这个异常
static class MyThread2 implements Runnable{
@Override
public void run(){
System.out.println("实现runnable");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("线程被打断以后将会出现在这里");
}
System.out.println("继续执行");
}
}
Thread t=new Thread(new MyThread2());
t.start();
t.interrupt();
运行结果:
实现runnable
线程被打断以后将会出现在这里
继续执行
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.ecarx.demo.firstdemo.controller.WWW$MyThread2.run(WWW.java:27)
@Override
public void run(){
System.out.println("实现runnable");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("线程被打断以后将会出现在这里");
}
System.out.println("继续执行");
}
}
Thread t=new Thread(new MyThread2());
t.start();
t.interrupt();
运行结果:
实现runnable
线程被打断以后将会出现在这里
继续执行
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.ecarx.demo.firstdemo.controller.WWW$MyThread2.run(WWW.java:27)
volatile
内存可见
内存模型
主内存和工作内存
lock addl
禁止指令重排
指令重排序 happen-before
内存屏障
synchronized
使用
public synchronized String tes(){
System.out.println(2222);
return "";
}
System.out.println(2222);
return "";
}
修饰方法的时候会在字节码文件加上访问标识ACC_SYNCHRONIZED
synchronized(对象){}
修饰代码块的时候,会在代码块前后加上moniterenter 和moniterexit
锁升级
锁定的对象补可以使用string常量还有integer、long等基础数据类型
线程池
创建线程池
线程池的参数
参数设置
线程池的额问题
threadlocal
引用类型
强引用
object o=new object(); o属于强引用
软引用
SoftReference<Object> so = new SoftReference<>(new Object());
引用存在的情况下,垃圾回收不会被回收,但是当内存不够的时候就会被回收
弱引用
WeakReference<Object> wo = new WeakReference<>(new Object());
即使有引用,当gc的时候会直接被回收
虚引用
管理堆外内存
堆外内存回收过程:1.JVM在堆内只保存堆外内存的引用,用DirectByteBuffer对象来表示2.每个DirectByteBuffer对象在初始化时,都会创建一个对应的Cleaner对象3.当DirectByteBuffer对象在某次GC中被回收,只有Cleaner对象知道堆外内存的地址4.当下一次GC执行时,Cleaner对象会触发自身clean方法清理对应的堆外内存5.Cleaner对象在下次GC时被回收
堆外内存的优点:
1、保持了一个较小的堆内内存,可以减少垃圾收集对应哟的影响。
2、在使用缓存时,需要消耗一些内存空间来提升速度,使用堆内内存会给虚拟机带来GC压力,使用硬盘或者分布式缓存的响应时间会比较长,这时候堆外缓存会是一个比较好的选择。
3、堆内内存由JVM 管理,属于用户态,而堆外内存由操作系统管理,属于内核态。如果从堆内向磁盘些数据时,数据会被先复制到堆外内存,即内核缓冲区,然后再由操作系统写入磁盘。使用堆外内存,直接写入磁盘,提升了效率。
1、保持了一个较小的堆内内存,可以减少垃圾收集对应哟的影响。
2、在使用缓存时,需要消耗一些内存空间来提升速度,使用堆内内存会给虚拟机带来GC压力,使用硬盘或者分布式缓存的响应时间会比较长,这时候堆外缓存会是一个比较好的选择。
3、堆内内存由JVM 管理,属于用户态,而堆外内存由操作系统管理,属于内核态。如果从堆内向磁盘些数据时,数据会被先复制到堆外内存,即内核缓冲区,然后再由操作系统写入磁盘。使用堆外内存,直接写入磁盘,提升了效率。
堆外内存的缺点:
1、堆外内存的缺点就是内存难以控制。
2、使用堆外内存就间接失去了使用JVM管理内存的可能性,一旦发生内存溢出时排查起来非常困难。
1、堆外内存的缺点就是内存难以控制。
2、使用堆外内存就间接失去了使用JVM管理内存的可能性,一旦发生内存溢出时排查起来非常困难。
堆外内存泄露:
1.操作系统对每个进程能管理的内存都是有限制的,对于32位系统而言,Linux最大接近4G,Windows大概2G,如果堆内实际内存1.6G,那留给堆外内存的大小也就0.4G
2.堆外内存不会像新生代和老年代那样,发现空间不足就通知收集器进行回收
3.它只能等待老年代满了后触发Full GC,然后JVM会“顺便地”清理堆外内存中废弃的对象
4.在极端情况下,如果JVM一直没有执行Full GC的话, 堆外内存也一直得不到释放
1.操作系统对每个进程能管理的内存都是有限制的,对于32位系统而言,Linux最大接近4G,Windows大概2G,如果堆内实际内存1.6G,那留给堆外内存的大小也就0.4G
2.堆外内存不会像新生代和老年代那样,发现空间不足就通知收集器进行回收
3.它只能等待老年代满了后触发Full GC,然后JVM会“顺便地”清理堆外内存中废弃的对象
4.在极端情况下,如果JVM一直没有执行Full GC的话, 堆外内存也一直得不到释放
零拷贝
死锁
一般具有两把以上的锁,在锁定一把的时候等待另一把锁
解决方案
多巴锁合并成一把锁
哲学家就餐问题
筷子做成集合
其中一个人抢的锁跟其他人不一样(混进一个左撇子)
混进一半的左撇子
子主题
面试题
交替打印
两个线程交替打印1A2B3C4D......
LockSupport
wait+notify
生产者消费者
juc
Automic类
concuuect集合
LockSupport
park(Thread t)阻塞线程,没有其他线程唤醒无法醒来,主要是在线程内部设定了标志位
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
唤醒条件:1:线程调用了unpark; 2:其它线程中断了线程;3:发生了不可预料的事情
unpark(Thread t)唤醒线程,及时线程没有睡眠,也能叫醒
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
if (thread != null)
UNSAFE.unpark(thread);
}
private static void setBlocker(Thread t, Object arg) {
// Even though volatile, hotspot doesn't need a write barrier here.
UNSAFE.putObject(t, parkBlockerOffset, arg);
}
// Even though volatile, hotspot doesn't need a write barrier here.
UNSAFE.putObject(t, parkBlockerOffset, arg);
}
在Linux系统下,是用的Posix线程库pthread中的mutex(互斥量),condition(条件变量)来实现的。
mutex和condition保护了一个_counter的变量,当park时,这个变量被设置为0,当unpark时,这个变量被设置为1
mutex和condition保护了一个_counter的变量,当park时,这个变量被设置为0,当unpark时,这个变量被设置为1
其实park/unpark的设计原理核心是“许可”:park是等待一个许可,unpark是为某线程提供一个许可。
如果某线程A调用park,那么除非另外一个线程调用unpark(A)给A一个许可,否则线程A将阻塞在park操作上。
有一点比较难理解的,是unpark操作可以再park操作之前。
也就是说,先提供许可。当某线程调用park时,已经有许可了,它就消费这个许可,然后可以继续运行。这其实是必须的。考虑最简单的生产者(Producer)消费者(Consumer)模型:Consumer需要消费一个资源,于是调用park操作等待;Producer则生产资源,然后调用unpark给予Consumer使用的许可。非常有可能的一种情况是,Producer先生产,这时候Consumer可能还没有构造好(比如线程还没启动,或者还没切换到该线程)。那么等Consumer准备好要消费时,显然这时候资源已经生产好了,可以直接用,那么park操作当然可以直接运行下去。如果没有这个语义,那将非常难以操作。
所以只要我们的 park/unpark 成对出现,无论执行顺序如何,都不会因此造成死锁
但是这个“许可”是不能叠加的,“许可”是一次性的。
比如线程B连续调用了三次unpark函数,当线程A调用park函数就使用掉这个“许可”,如果线程A再次调用park,则进入等待状态。
park不需要获取某个对象的锁
因为中断的时候park不会抛出InterruptedException异常,所以需要在park之后自行判断中断状态,然后做额外的处理。就像 sleep ,notify 唤醒后,jvm 会帮助我们检查一次是否有 interrupt 信号,其原理之前做过解析 https://www.cnblogs.com/niuyourou/p/12392942.html ,将检查 interrupt 信号的逻辑放在阻塞线程的逻辑之后,一旦被唤醒,首先执行检查 interrupt 信号的逻辑,检查完后退出 sleep/wait 方法,程序继续向下执行。而 park / unpark 并没有提供类似的机制,如果需要我们应自己在 park 方法后加入检查 interrupt 信号的逻辑。
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
如果某线程A调用park,那么除非另外一个线程调用unpark(A)给A一个许可,否则线程A将阻塞在park操作上。
有一点比较难理解的,是unpark操作可以再park操作之前。
也就是说,先提供许可。当某线程调用park时,已经有许可了,它就消费这个许可,然后可以继续运行。这其实是必须的。考虑最简单的生产者(Producer)消费者(Consumer)模型:Consumer需要消费一个资源,于是调用park操作等待;Producer则生产资源,然后调用unpark给予Consumer使用的许可。非常有可能的一种情况是,Producer先生产,这时候Consumer可能还没有构造好(比如线程还没启动,或者还没切换到该线程)。那么等Consumer准备好要消费时,显然这时候资源已经生产好了,可以直接用,那么park操作当然可以直接运行下去。如果没有这个语义,那将非常难以操作。
所以只要我们的 park/unpark 成对出现,无论执行顺序如何,都不会因此造成死锁
但是这个“许可”是不能叠加的,“许可”是一次性的。
比如线程B连续调用了三次unpark函数,当线程A调用park函数就使用掉这个“许可”,如果线程A再次调用park,则进入等待状态。
park不需要获取某个对象的锁
因为中断的时候park不会抛出InterruptedException异常,所以需要在park之后自行判断中断状态,然后做额外的处理。就像 sleep ,notify 唤醒后,jvm 会帮助我们检查一次是否有 interrupt 信号,其原理之前做过解析 https://www.cnblogs.com/niuyourou/p/12392942.html ,将检查 interrupt 信号的逻辑放在阻塞线程的逻辑之后,一旦被唤醒,首先执行检查 interrupt 信号的逻辑,检查完后退出 sleep/wait 方法,程序继续向下执行。而 park / unpark 并没有提供类似的机制,如果需要我们应自己在 park 方法后加入检查 interrupt 信号的逻辑。
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
cas
lock
aqs
ReentrantLock
子主题
子主题
error和exception
开发框架
spring
springboot
springcloud
aop
ioc
dubbo
mybatis
jvm
内存区域
垃圾回收算法
垃圾回收器
字节码文件
类加载过程
对象的内存布局
JAVA OBJECT layout
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
查看对象布局的工具
子主题
子主题
网络
网络模型
tcp
udp
http、https
0 条评论
下一页