文末有惊喜
Java 基础
- 说一说常见集合
集合可以分为Collection和Map.-
Collection又分为List和Set
- List 中常见的有ArrayList,LinkedList等
首先谈一下ArrayList
ArrayList 作为最常用的集合,首先它的底层数据结构是一个连续数组,这个如果初始的时候不指定大小,默认为10。
初始化: 不指定大小默认容量是10,也可以指定大小(>0)。
添加:添加时会先进行位置判断,是否越界。再判断是否需要扩容。最后将元素添加到数组中。
扩容:如果新加一个元素之后的所需要的容量,大于当前数组的容量,则需要扩容,每次扩容当前容量的1.5倍。
动作为,新建一个数组,把旧数组copy过去。
查询:时间复杂O(1)
线程安全性:不安全,并发插入的时候,可能会报数组越界。
注意点:
LinkedList 的数据结构是一个双向链表,对内存的连续性没有要求,头尾的增删都是O(1),更适合插入和删除多的场景。
LinkedList 不支持随机访问,但是提供一个get(int index)方法,是通过遍历实现的,但是做了小小的优化,
当index > size()/2时,从列表的尾部开始查找。 - Set 有HashSet,TreeSet等
set 集合的特点不允许重复的元素,所以要适当的重写equals和hahsCode方法- HashSet 无序,在Hash表基础上,增加了不可重复的。
- LinkedHashSet
- TreeSet 有序,基于红黑树,时间复杂度时O(logN),遍历时按照排序后的顺序输出
待排序的对象通过实现Comparable 接口中的comparTo()方法,自然排序。
单独提供比较器Comparator,作为TreeSet的构造入参。通过实现compare()方法,进行客户化排序。
- HashSet 无序,在Hash表基础上,增加了不可重复的。
- List 中常见的有ArrayList,LinkedList等
-
Map中常见的有 HashMap,Hashtable,ConcurrentHashMap,LinkedHashMap,WeakHashMap等
A. HashMap- 1.线程不安全
- 2.数据结构: 1.7 数组+链表 1.8+ 数组+链表+红黑树
- 3.put步骤:
- 3.1 key 先进行hash散列,使key更均匀的分布在数组上 hash函数运算为先取 (h = hashCode()^( h >>> 16 ))
- 3.2 将散列值和容量-1,进行位与运算,定位数组的位置。因为容量值都是2的幂次方,进行位与运算,相当于取模,可以定位到具体的数组位置。
- 3.3 hash冲突 如果两个key的hashCode相同,就会产生hash冲突,即落在同一个位置的数组上。 hashCode相同但不一定时同一个对象,再比较equals方法,如果equals也相同,认为是相同对象,会把原来的覆盖掉。否则会加入的数组中,以链表或者红黑树节点的结构存在。
- 3.2.1 hashCode和equals hashCode 计算会得到一个散列值,常用封装类型和String是比较好的做Map的key的方案,因为都单独的进行了hashCode重写,能够较好的散列,如果是自定义的对象,重写不好就会造成严重的hash冲突。 不重写hashCode,用的是Object里的hashCode方法,得到的是对象的地址。 如果hashCode相同,那么就会继续进行equals,如果equals也相同,那么就认为是同一个对象,新的对象将替换旧对象的位置,旧对象随着put()方法返回。 如果equals不相同,Node节点挂在链表或树上。
- 3.2.2 hash冲突的解决方案
-
- 开放地址法
-
- 再hash
-
- 链地址
-
- 建立公共溢出区
-
- 3.4 判断当前链表长度,是否超过8,超过8转成红黑树,目的是减少查找速度(OlogN),插入链表中 1.7 头插法产生并发时循环链表
1.8尾插法优化插入,不会死循环 3.5 扩容 新插入一个值,如果落入的数组位置达到阈值,且数组中的元素值大于0,将发生扩容,扩容的大小位,容量值的一倍,做法就是容量左移1。 扩容后的元素会重新分配,原来的元素会落在原数组位置或者,原数组+扩容的值的位置。
- 4.其他
一个数找到离小于或等于它最近的2的次方数 highestOneBit 多次右移,再和本身进行或运算,保证二进制中的低位均是1,这样两数相减,就得到一个最大且超过原数的2的次方幂
B. ConcurrentHashMap
-
- 通过volatile和分段锁技术,实现了并发的安全性操作。数据结构上同HashMap,但是增加了Segment 段的概念。
jdk1.7 采用分段锁技术 每个线程占用锁访问Segment的时候,不会影响其他线程,访问其他段,实现了更细粒度的加锁。
jdk1.8 采用CAS + synchronized 来保证并发安全性。
put 时如果为null尝试CAS写入,如果失败则进行自旋,自旋超过一定次数则升级为synchronized。
锁升级,偏向锁->CAS轻量级加自旋->重量级锁synchronized。
C. ConcurrentSkipListMap
- 通过volatile和分段锁技术,实现了并发的安全性操作。数据结构上同HashMap,但是增加了Segment 段的概念。
-
Mysql和Mybatis
-
1.1 Mysql 架构
- 连接层
用于处理Mysql的客户端连接,引入了线程池的概念,提供连接处理,安全认证,SSL等功能 - 服务层
查询解析,分析,优化,缓存以及内置函数都在这一层 - 存储引擎层
服务层通过API与存储引擎进行通信,实现了插件式的存储引擎。可以方便的更换存储引擎 - 存储层
具体的数据存储的物理位置
- 连接层
-
1.2 并发控制
1.2.1 读写锁,读锁也叫共享锁,写锁也叫排他锁
读锁在多用户同时读取一个资源时互不干扰,但是不能够进行写操作。
写锁在多用户操作时,如果其中一个线程占用了写锁,其他线程连读取的机会都没有。
1.2.2 锁粒度
锁的粒度越小,并发性越好
因为使用锁会有开销,通常粒度越小,管理起来的开销也越大。所以要在数据安全性和锁开销之间寻求一个平衡,也就是所策略。
Mysql的存储引擎可以实现自己锁粒度和锁策略。1.2.3
常见的有两种锁策略,即表锁和行锁。- 表锁,它会锁定整张表,当A用户需要写操作时,需要先获得锁才能进行,在此期间其他用户的读写都会被阻塞。
只要没有写锁时,其他用户才能获得读锁,读锁之间的不互相阻塞。 - 行锁,只在存储引擎层实现,可以最大程度的支持并发,但也带来的最大的开销。InnoDb中就实现了行级锁。
- 表锁,它会锁定整张表,当A用户需要写操作时,需要先获得锁才能进行,在此期间其他用户的读写都会被阻塞。
-
1.3 事务
ACID
原子性:一个事务中,要么整体提交成功,要么整体失败回滚。
一致性:一个事务总是总一个一致性的状态,转到另外一个一致性的状态。要么钱转走了,要么钱还在。
隔离性:事务之间的执行通常来说时不可见的
持久性:事务被提交之后,会被持久到数据库。事务隔离级别
1.读未提交,允许一个事务读取另外一个事务没有提交的数据。事务A,x转账200元到y的账户,包含两个动作,1:从x的账户扣除200元,2:y增加200元。
事务B在事务A执行第一个动作后,查看到x账户此时减少了200元。如果此时事务A失败回滚,x账户里还是200元,那么事务B得到的x账户的数据就是脏数据,就是脏读。
2.读已提交,对于已经提交的事务可以读取其中的更改的数据。事务A开始查询一个数据count =100,事务B对count进行+1并提交,此时A事务未结束,再对count进行查询,得到101。事务A读到了事务B提交之后的数据,称之为读已提交。
造成的问题,是一个事务中多次读取某个值,可能出现的结果不一致,造成的问题就是不可重读。
3.可重复读, 该事务保证多次读取同样的记录得到的值是一样的。解决了不可重读的问题。但是类似无法解决查询某个范围内的数据量,在事务中一致的问题。或者是查某个数据不存在则插入的场景,
第一次查某个A数据不存在,在插入动作之前,另外一个事务将A插入。会造成插入失败。这个时候就是幻读。
4. 可串行化在,读取的每一行数据上都会加锁,可以解决幻读问题,但是性能上很低效。死锁,两个或多个事务在同一资源上相互占用,请求对方资源,从而导致恶性循环。
解决方案
- 系统检测死锁后返回错误
- 超时等待,超过时间后放弃
-
Mysql 存储引擎
Innodb和MyISAM
Innodb支持事务,外键,聚簇索引,行级锁,但是InnoDB不保存表的具体行数,
MyISAM的索引根据数据的物理位置引用被索引的行,Innodb则是根据主键引用被索引的行。
*MYISAM索引使用前缀压缩技术,Innodb使用原数据格式存储 -
Mysql索引
索引是存储引擎用于快速查找到记录的一种数据结构。-
B-tree
访问速度快,原因是不需要进行全表扫描,从根节点向叶子节点进行搜索,根节点的槽中存放了指向叶子节点的指针,存储引擎根据这些指针进行查找
顺序存储,所以很适合范围查找索引查询
全值匹配
最左前缀
列前缀
范围匹配
精确某一列,并范围匹配另一列限制 必须最左列开始匹配 不能跳过索引中的列
-
Hash索引
基于hash表实现,索引只存储hash值,结构紧凑,查询起来非常快。
只有精确匹配到索引的所有列,才有效。
索引只存储hash值和行指针,所有无法避免行读取。
hash索引,并不按照索引值顺序存储,无法进行排序
hash冲突高的时候,维护代价高,查询效率会降低自定义hash,适合对URL建立hash索引。
如果对url的长字符串使用B-tree进行做索引,索引会很大.
可以先对url做hash,单独存一列,然后对这一列做索引,这样查询效率会很高。索引列的顺序,选择区分度高的列在前
-
-
Mysql 数据类型优化
- binlog和redolog
- binlog 是Mysql的Server实现,redolog是Innodb引擎实现
- binlog 是追加,redolog是反复写
- 两段式提交,执行器开始执行,调用API接口通知redolog写,redolog perpare ,通知binlog可以写入并可以随时提交事务,binlog写完后通知redolog提交事务
- 聚簇索引
B tree的节点存放的是行记录 - 普通索引
B-tree的节点存放的是主键 - 回表
通过主键再查具体行记录的过程称为回表
-覆盖索引
使用索引,直接获取列数据,而不需要再读取行数据,这个索引包含了要查询的是所有列数据
Q&A
Q: 唯一索引怎么实现?
A: 当有一条唯一数据要插入的时候,先查询,再插入
Q: 聚簇索引是什么
A: 聚簇索引是一种存储结构,会把周围的行数据,聚集在一起放在聚簇索引的下面,通过聚簇索引查询数据会很快。
Q: 怎么建立聚簇索引
A: 每个表只有一个聚簇索引,Innodb通过主键聚集数据,也就是Innodb设置了主键,那么也就创建了聚簇索引
Q: 如果没有主键,会有聚簇索引吗
A: 会,如果没有显示声明主键,Innodb会选择一个唯一,非空的索引代替。如果所有列中没有满足这种条件 的列,
那么Innodb会隐式定义一个主键做聚簇索引
多线程和并发
Spring,SpringBoot
SpringIOC
SpringAop
Spring BeanFactory和ApplicationContext
Bean的生命周期
SpringBoot的stater
Spring事务和Mybatis事务
分布式微服务SpringCloud
Spring 功能
- IOC/DI,AOP,事务管理
-
IOC 控制反转
-
谁控制,控制谁
容器控制对象依赖倒置是面向接口编程的一种实现
依赖注入是将对象间的依赖关系由原来的开发人员控制转变成容器的控制
控制反转、依赖倒置和依赖注入三者之间的关系是:控制反转是一种软件设计模式,其遵循了软件工程中的依赖倒置原则;依赖注入是Spring框架实现控制反转的一种方式
-
缓存 Redis
- 数据结构
String,List,Hash,Set,ZSet
- 持久化
RDB: 默认开启,间隔一段时间持久化写入
save 间隔时间 写入次数
AOF(Append Only File): 日志的形式记录对redis的操作,可以设设置每秒写入,每次修改写入(最影响性能),不同步写入。
3.主从复制模式
写主库,读从库
1主1从
(用于主节点故障转移,当需要高并发写且需要持久化的时候,可以在从节点开启持久化,主节点只负责写。即保证数据安全性,也避免主节点的性能影响)
1主多从
(用于读高并发的时候,但从主节点同步到从节点的次数增多,影响带宽,同时影响主节点稳定性)
树形结构
1-1-N
主节点先同步到一个从节点,再由这从节点负责同步到其他子节点,解决了主节点推送压力大的问题
存在的问题:
-
主节点挂了之后,需要人工设置新的主节点
-
单机的主节点,读写能力有限
哨兵模式
当主节点出现故障,会自动在从节点中选举一 成为新的主节点,进行故障转移。
启动哨兵进程,对节点进行监听,若主节点一段时间内没有回复,则进行主观下线,然后再询问其他哨兵,经过判断超过一定数量后对主节点客观下线,并经过选举算法从其他的从节点中选出一个新的主机点。其他从节点从新指向新的主节点并通知客户端,如果原来的主节点恢复后将成为从节点。
集群
缓存穿透和缓存雪崩
带有缓存的应用,查询的内容不在缓存中,直接访问到了数据库,这称之为缓存穿透。
解决方法,可以缓存空对象,或者使用布隆过滤器。
布隆过滤器,N 个长度的数组,k 个hash函数,一个key来了之后经过k个hash函数,落到了数组N的k个位置。当查询的时候先判断k个位置上的值是否都为1,如果都为一则存在放行,否则不存在,返回。在一定程度上保护了存储层。
存在的值肯定能在布隆过滤器中找到,不存在的也可能找到,取决于hash冲突和数组长度。数组越大,冲突越少,越精确。
缓存雪崩:
大量缓存同时失效。
消息队列
计算机网络
Linux
JVM
下图是上述知识点的思维导图,后面思维导图也会不断的更新,大家如果有需要可以去这链接克隆下载。
https://www.processon.com/mindmap/5ffd66b6e0b34d2060d54242
导图中涉及的所有书籍,如果有需要可以留言邮箱获取电子档pdf。