ArrayList、LinkedList、HashMap
实现了List接口和Deque接口
LinkedList底层使用双向链表实现。
双向链表就是通过Node类来体现的,类中通过item变量保存了当前节点的值,通过next变量指向下一个节点,通过prev变量指向上一个节点。
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
HashMap
HashMap是Map接口下的一个实现类,jdk1.7中采用数组+链表实现,jdk1.8之后采用数组+链表+红黑树实现。
HashMap支持存储key/value为null的元素。
HashMap被构造时默认初始容量为16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
HashMap最大容量为2^30
static final int MAXIMUM_CAPACITY = 1 << 30;
HashMap默认的负载因子为0.75(负载因子越大发上冲突的可能性越大,负载因子越小空间浪费较大)
static final float DEFAULT_LOAD_FACTOR = 0.75f;
链表树化的最小链表大小阈值8
,当HashMap中元素数量大于64
并且数组所挂链表的大小超过8
才会将数组所挂的链表转换为红黑树。
static final int TREEIFY_THRESHOLD = 8;
static final int MIN_TREEIFY_CAPACITY = 64;
红黑树链表化的的阈值是6
,当红黑树结点个数小于6
时,就会转换为链表。
static final int UNTREEIFY_THRESHOLD = 6;
HashMap中的hash方法是与我们直接根据key
生成hashCode值再取模的方式不同,在HashMap中的hash算法中,首先会key
计算出hashCode值,然后再将hashCode值与右移16
为的hashCode值进行按位异或操作,得到一个新的哈希值,这样做的目的其实就是为了增强哈希值的随机性,这个方法也叫做“扰动方法”。
索引计算方法
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
数组容量为何是 2 的 n 次幂
HashSet
HashSet是基于HashMap实现的,默认构造函数是构建一个初始容量为16,负载因子为0.75 的HashMap。它封装了一个 HashMap 对象来存储所有的集合元素,所有放入 HashSet 中的集合元素实际上由 HashMap 的 key 来保存,而 HashMap 的 value 则存储了一个 PRESENT,它是一个静态的 Object 对象。
(JDK1.7)在多线程下扩容的时侯可能会导致环形链表的出现,调用get()方法的时候可能导致死循环。
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {
while(null != e) {
Entry<K,V> next = e.next;
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
}
concurrentHashmap和HashTable
Hashtable 对比 ConcurrentHashMap
volatile可以保证有序性和可见性,不能保证原子性。
用 volatile 修饰共享变量,能够防止编译器等优化发生,让一个线程对共享变量的修改对另一个线程可见。
用 volatile 修饰共享变量会在读、写共享变量时加入不同的屏障,阻止其他读写操作越过屏障,从而达到阻止重排序的效果
volatile和synchronized的区别
(1)volatile只能作用于变量,使用范围较小。synchronized可以用在方法、类、同步代码块等,使用范围比较广。(要说明的是,java里不能直接使用synchronized声明一个变量,而是使用synchronized去修饰一个代码块或一个方法或类。)
(2)volatile只能保证可见性和有序性,不能保证原子性。而可见性、有序性、原子性synchronized都可以保证。
(3)volatile不会造成线程阻塞。synchronized可能会造成线程阻塞。
reentrantlock和synchronized的区别:
锁的实现:Synchronized是依赖于JVM实现的,而ReenTrantLock是JDK实现的。使用 synchronized 时,退出同步代码块锁会自动释放,而使用 Lock 时,需要手动调用 unlock 方法释放锁
ReenTrantLock独有的能力:
1、ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。
2、ReenTrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。
3、ReenTrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。
进程是资源分配的最小单位,线程是资源调度的最小单位。系统运行一个程序即是一个进程从创建,运行到消亡的过程。
线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。
多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。
(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不可剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之
一不满足,就不会发生死锁。
死锁避免的基本思想:系统对进程发出的每一个系统能够满足的资源申请进行动态检查,并根据检查结果决定是否分配资源,如果分配后系统可能发生死锁,则不予分配,否则予以分配,这是一种保证系统不进入死锁状态的动态策略。
如果操作系统能保证所有进程在有限时间内得到需要的全部资源,则系统处于安全状态否则系统是不安全的。
利用银行家算法避免死锁:所谓银行家算法,是指在分配资源之前先看清楚,资源分配后是否会导致系统死锁。如果会死锁,则不分配,否则就分配。
三种垃圾回收算法
标记清除法
解释:
要点:
标记整理法
解释:
特点:
标记复制法
解释:
特点:
新生代特点是内存空间相对老年代较小,对象存活率低。
标记复制算法的效率只和当前存活对象大小有关,因而很适用于年轻代的回收。而复制算法的内存利用率不高的问题,可以通过虚拟机中的两个Survivor
区设计得到缓解。
老年代的特点是内存空间较大,对象存活率高。
这种情况,存在大量存活率高的对象,标记复制算法明显变得不合适。一般是由标记清除或者是标记清除与标记整理的混合实现。
IOC
IoC(Inverse of Control:控制反转)是一种设计思想 或者说是某种模式。这个设计思想就是 将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理。 IoC 在其他语言中也有应用,并非 Spring 特有。IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种对象。
IoC 最常见以及最合理的实现方式叫做依赖注入(Dependency Injection,简称 DI)。
依赖注入:由IoC容器动态地将某个对象所需要的外部资源(包括对象、资源、常量数据)注入到组件(Controller, Service等)之中。简单点说,就是IoC容器会把当前对象所需要的外部资源动态的注入给我们。
Spring依赖注入的方式主要有四个,基于注解注入方式、set注入方式、构造器注入方式、静态工厂注入方式。推荐使用基于注解注入方式,配置较少,比较方便。
IoC 的思想就是两方之间不互相依赖,由第三方容器来管理相关资源。这样有什么好处呢?
AOP
AOP 即面向切面编程,简单地说就是将代码中重复的部分抽取出来,在需要执行的时候使用动态代理技术,在不修改源码的基础上对方法进行增强。
使用AOP的好处
setter方法注入、构造方法注入、注解注入(@Autowried)
在Java代码中可以使用@Autowired或@Resource注解方式进行Spring的依赖注入。两者的区别是:@Autowired默认按类型装配,@Resource默认按名称装配,当找不到与名称匹配的bean时,才会按类型装配。
@Autowired(自动注入)修饰符有三个属性:Constructor,byType,byName。默认按照byType注入。
constructor:通过构造方法进行自动注入,spring会匹配与构造方法参数类型一致的bean进行注入,如果有一个多参数的构造方法,一个只有一个参数的构造方法,在容器中查找到多个匹配多参数构造方法的bean,那么spring会优先将bean注入到多参数的构造方法中。
byName:被注入bean的id名必须与set方法后半截匹配,并且id名称的第一个单词首字母必须小写,这一点与手动set注入有点不同。
byType:查找所有的set方法,将符合符合参数类型的bean注入。
主要有四种注解可以注册bean,每种注解可以任意使用,只是语义上有所差异:
在Spring中对于事务的传播行为定义了七种类型分别是:REQUIRED、SUPPORTS、MANDATORY、REQUIRES_NEW、NOT_SUPPORTED、NEVER、NESTED
SQL 标准定义了四个隔离级别:
MySQL InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读)
InnoDB的默认隔离级别RR(可重读)是可以解决幻读问题发生的,主要有下面两种情况:
MySQL数据库中innodb存储引擎,B+树索引可以分为:
聚簇索引(也称聚集索引,clustered index):找到了索引就找到了需要的数据,那么这个索引就是聚簇索引,所以主键就是聚簇索引,修改聚簇索引其实就是修改主键
非聚簇索引(辅助索引):索引的存储和数据的存储是分离的,也就是说找到了索引但没找到数据,需要根据索引上的值(主键)再次回表查询,非聚簇索引也叫做辅助索引。。
这两种索引内部都是B+树,聚集索引的叶子节点存放着一整行的数据。
主键索引是一种聚簇索引。
唯一索引是非聚簇索引
主键一定是聚簇索引,MySQL的InnoDB中一定有主键,即便研发人员不手动设置,则会使用unique索引,没有唯一索引,则会使用数据库内部的一个行的id来当作主键索引,其它普通索引需要区分SQL场景,当SQL查询的列就是索引本身时,我们称这种场景下该普通索引也可以叫做聚簇索引,MyisAM引擎没有聚簇索引。
所谓三次握手(Three-way Handshake),是指建立一个TCP连接时,需要客户端和服务器总共发送3个包。
三次握手的目的是连接服务器指定端口,建立TCP连接,并同步连接双方的序列号和确认号并交换 TCP 窗口大小信息.在socket编程中,客户端执行connect()时。将触发三次握手。
第一次握手:
客户端发送一个TCP的SYN标志位置1的包指明客户打算连接的服务器的端口,以及初始序号X,保存在包头的序列号(Sequence Number)字段里。
第二次握手:(服务器确认服务器可以接受到客户端的请求,但客户端还不知道)
服务器发回确认包(ACK)应答。即SYN标志位和ACK标志位均为1同时,将确认序号(Acknowledgement Number)设置为客户的I S N加1以.即X+1。
第三次握手:(客户端确认服务器可以收到自己的信息)
客户端再次发送确认包(ACK) SYN标志位为0,ACK标志位为1.并且把服务器发来ACK的序号字段+1,放在确定字段中发送给对方.并且在数据段放写ISN的+1
当三次握手都成功的时候,我们发现此时客户端发送的信息服务端能够收到并且服务端发送的信息客户端也能收到,通信双方连接成功。
1、图中的发送请求中的发送标识SYN、ACK表示的是发送报文中两个标识位!而Seq和Ack分别代表发送序号和确认号。
2、服务端在接收到了客户端的连接请求后,回复中同时发送了SYN、ACK两个标识位,将建立连接的请求和对客户端的确认应答在同一个数据包中发送了,这也是为什么只需要三次握手,就能建立连接。
TCP 四次挥手
TCP的连接的拆除需要发送四个包,因此称为四次挥手(four-way handshake)。客户端或服务器均可主动发起挥手动作,在socket编程中,任何一方执行close()操作即可产生挥手操作。
1.第一次挥手:客户端向服务端发送断开连接的请求,告诉服务端我这边不需要再请求你的数据了,tcp报文头中发送标识FIN=1(表示客户端请求跟服务端断开连接),序号Seq=u
2.第二次挥手:服务端在接收到客户端发送的断开请求后,需告诉客户端已收到请求,tcp报文头中发送标识ACK=1(ACK表示对客户端的断开连接的请求进行应答),序号Seq=v,确认号Ack=u+1(表示对客户端发送的序号Seq=u的请求进行确认)。
3.第三次挥手:当服务端数据传输完毕之后,向客户端发起断开连接的请求,告诉客户端我这边也不需要再发送数据了,tcp报文头中发送标识FIN=1,ACK=1(FIN表示服务端请求跟客户端断开连接,ACK表示对上一次客户端的断开连接的请求进行应答),序号Seq=w,确认号Ack=u+1(表示对客户端发送的序号Seq=u的请求进行确认)
4.第四次挥手:客户端接收到服务发送的断开连接请求后,需告诉服务端已收到信息,作出应答,tcp报文头中发送标识ACK=1(ACK表示对服务端的断开连接的请求进行应答),序号Seq=u+1,确认号Ack=w+1(表示对服务端发送的序号Seq=w的请求进行确认)
第二次挥手的时候。为什么不能像握手的时候一样,服务端对客户端断开连接的请求做确认应答的时候,同时向客户端发送断开连接的请求。这样“三次挥手”不就可以了么???
*:当服务端数据传输完毕之后,向客户端发起断开连接的请求,告诉客户端我这边也不需要再发送数据了,tcp报文头中发送标识FIN=1,ACK=1(FIN表示服务端请求跟客户端断开连接,ACK表示对上一次客户端的断开连接的请求进行应答),序号Seq=w,确认号Ack=u+1(表示对客户端发送的序号Seq=u的请求进行确认)
4.第四次挥手:客户端接收到服务发送的断开连接请求后,需告诉服务端已收到信息,作出应答,tcp报文头中发送标识ACK=1(ACK表示对服务端的断开连接的请求进行应答),序号Seq=u+1,确认号Ack=w+1(表示对服务端发送的序号Seq=w的请求进行确认)
第二次挥手的时候。为什么不能像握手的时候一样,服务端对客户端断开连接的请求做确认应答的时候,同时向客户端发送断开连接的请求。这样“三次挥手”不就可以了么???
解答:在实际的网络中,服务端在接收到客户端断开连接的请求的时候,此时服务端可能还有数据没有传输完毕,不能立即向客户端发送断开连接的请求!所以当客户端主动发起断开请求的时候,服务器先回应一个确认,等所有数据传输完毕后再发送服务器断开的请求。