1. java集合深入理解
https://blog.csdn.net/u011240877/article/category/6447444
http://www.cnblogs.com/skywang12345/p/3323085.html
1.1 迭代器的快速失败机制 fast-fail
https://blog.csdn.net/chewbee/article/details/78314661
1. 是java集合的一种错误检测机制
2. 防止一个线程在对集合进行迭代时候,另一个线程在结构上
对集合进行修改,不是单纯的修改集合元素值
1.2 collection 集合
https://blog.csdn.net/justloveyou_/article/details/52948661
1.3 List集合
https://blog.csdn.net/justloveyou_/article/details/52955619
1.3.1 ArrayList
a. 内部数据结构
private transient Object[] elementData; // 瞬时域
private int size;
b. 扩容操作 这是一个public
函数,默认每次扩容是 1.5倍 + 1
// 调整数组容量
public void ensureCapacity(int minCapacity) {
modCount++;
int oldCapacity = elementData.length;
if (minCapacity > oldCapacity) {
Object oldData[] = elementData;
int newCapacity = (oldCapacity * 3)/2 + 1;
if (newCapacity < minCapacity)
newCapacity = minCapacity;
// minCapacity is usually close to size, so this is a win:
// 最终会调用 System.ArrayCopy() 这是一个内存块拷贝函数
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
c. 序列化 虽然内部数据结构一个是瞬态数据和一个基本数据类型 ,但是通过自定义的readObject()/writeObject()
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// Write out all elements in the proper order.
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in capacity
s.readInt(); // ignored
if (size > 0) {
// be like clone(), allocate array based upon size not capacity
int capacity = calculateCapacity(elementData, size);
SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
ensureCapacityInternal(size);
Object[] a = elementData;
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
d. ArrayList 允许值为null
// 移除此列表中 “首次” 出现的指定元素(如果存在)。这是因为 ArrayList 中允许存放重复的元素。
public boolean remove(Object o) {
// 由于ArrayList中允许存放null,因此下面通过两种情况来分别处理。
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
// 类似remove(int index),移除列表中指定位置上的元素。
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
}
1.3.2 LinkList
a. 头结点不存放任何数据
b. 新增元素放在链表尾部
c. 只能遍历访问,不能随机访问
2. Map 综述
https://blog.csdn.net/justloveyou_/
2.1 彻头彻尾理解 HashMap
https://blog.csdn.net/justloveyou_/article/details/62893086
初始容量: 桶的数量
负载因子: 扩容阈值=负载因子 * 初始容量
- 有两个重要的数据: 容量(默认是16),负载因子(默认是0.75)
- 使用
数组链表
的结构来存储 - put操作
- get操作
- resize扩容操作 多线程的情况下出现
死循环
- 非线程安全的
迭代器的快速失败机制
fast-fail机制
在使用迭代器遍历Hashmap的过程中,别的线程不允许修改hashmap,要不然就会出现快速失败,内部是通过modCount计算器来实现的.
多线程并发HashMap,写操作导致扩容
- 内部的链表产生死循环 cpu100%
- 获取null
- 元素丢失
https://www.cnblogs.com/dongguacai/p/5599100.html
https://blog.csdn.net/u013668852/article/details/77141842
/**
* Associates the specified value with the specified key in this map.
* If the map previously contained a mapping for the key, the old
* value is replaced.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return the previous value associated with key, or null if there was no mapping for key.
* Note that a null return can also indicate that the map previously associated null with key.
*/
public V put(K key, V value) {
//当key为null时,调用putForNullKey方法,并将该键值对保存到table的第一个位置
if (key == null)
return putForNullKey(value);
//根据key的hashCode计算hash值
int hash = hash(key.hashCode()); // ------- (1)
//计算该键值对在数组中的存储位置(哪个桶)
int i = indexFor(hash, table.length); // ------- (2)
//在table的第i个桶上进行迭代,寻找 key 保存的位置
for (Entry<K,V> e = table[i]; e != null; e = e.next) { // ------- (3)
Object k;
//判断该条链上是否存在hash值相同且key值相等的映射,若存在,则直接覆盖 value,并返回旧value
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue; // 返回旧值
}
}
modCount++; //修改次数增加1,快速失败机制
//原HashMap中无该映射,将该添加至该链的链头
addEntry(hash, key, value, i);
return null;
}
2.2彻头彻尾理解 LinkedHashMap
https://blog.csdn.net/justloveyou_/article/details/71713781
0. 增加属性: 链表头节点header , 标志位accessOrder,默认是保持插入顺序
1. 在HashMap的基础上增加双向链表
2. 默认是保持插入顺序
3. LUR算法(最近最少使用)
1. HashMap与LinkHashMap 的get/set方法会访问accessOrder() ,但是HashMap的accessOrder()是空操作
2. LinkHashMap的accessOrder(),如果对应的accessOrder设置为true recordAccess()函数在访问内部节点的时候就会把访问节点放到队列的尾部,并删除当前节点
新增节点
/**
* This override alters behavior of superclass put method. It causes newly
* allocated entry to get inserted at the end of the linked list and
* removes the eldest entry if appropriate.
*
* LinkedHashMap中的addEntry方法
*/
void addEntry(int hash, K key, V value, int bucketIndex) {
//创建新的Entry,并插入到LinkedHashMap中
createEntry(hash, key, value, bucketIndex); // 重写了HashMap中的createEntry方法
//双向链表的第一个有效节点(header后的那个节点)为最近最少使用的节点,这是用来支持LRU算法的
Entry<K,V> eldest = header.after;
//如果有必要,则删除掉该近期最少使用的节点,
//这要看对removeEldestEntry的覆写,由于默认为false,因此默认是不做任何处理的。
if (removeEldestEntry(eldest)) {
removeEntryForKey(eldest.key);
} else {
//扩容到原来的2倍
if (size >= threshold)
resize(2 * table.length);
}
}
void createEntry(int hash, K key, V value, int bucketIndex) {
// 向哈希表中插入Entry,这点与HashMap中相同
//创建新的Entry并将其链入到数组对应桶的链表的头结点处,
HashMap.Entry<K,V> old = table[bucketIndex];
Entry<K,V> e = new Entry<K,V>(hash, key, value, old);
table[bucketIndex] = e;
//在每次向哈希表插入Entry的同时,都会将其插入到双向链表的尾部,
//这样就按照Entry插入LinkedHashMap的先后顺序来迭代元素(LinkedHashMap根据双向链表重写了迭代器)
//同时,新put进来的Entry是最近访问的Entry,把其放在链表末尾 ,也符合LRU算法的实现
e.addBefore(header);
size++;
}
recordAccess()
HashMap是一个空函数,LinkHashMap如下所示:
void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
//如果链表中元素按照访问顺序排序,则将当前访问的Entry移到双向循环链表的尾部,
//如果是按照插入的先后顺序排序,则不做任何事情。
if (lm.accessOrder) {
lm.modCount++;
//移除当前访问的Entry
remove();
//将当前访问的Entry插入到链表的尾部
addBefore(lm.header);
}
}
- 使用LinkHashMap实现LRU算法
public class LRU<K,V> extends LinkedHashMap<K, V> {
private static final long serialVersionUID = 1L;
public LRU(int initialCapacity,float loadFactor, boolean accessOrder) {
super(initialCapacity, loadFactor, accessOrder);
}
/* LinkHashMap中的实现就是单纯的返回一个false,也就是不删除节点
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}
*/
@Override
protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) {
// TODO Auto-generated method stub
if(size() > 6){
return true;
}
return false;
}
public static void main(String[] args) {
LRU<Character, Integer> lru = new LRU<Character, Integer>(16, 0.75f, true);
String s = "abcdefghijkl";
for (int i = 0; i < s.length(); i++) {
lru.put(s.charAt(i), i);
}
System.out.println("LRU中key为h的Entry的值为: " + lru.get('h'));
System.out.println("LRU的大小 :" + lru.size());
System.out.println("LRU :" + lru);
}
}
2.3 彻头彻尾理解 ConcurrentHashMap
https://blog.csdn.net/justloveyou_/article/details/72783008
a. segment继承ReentrantLock ,使得对象具备了锁的功能
b. 初始容量,负载因子,并发级别 三个重要参数 . 默认值分别为16 , 0.75 , 16
构造函数 : 根据初始容量,并发级别,确定 多少各segment 以及每个segment多少个桶
public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) {
if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
throw new IllegalArgumentException();
if (concurrencyLevel > MAX_SEGMENTS)
concurrencyLevel = MAX_SEGMENTS;
// Find power-of-two sizes best matching arguments
int sshift = 0; // 大小为 lg(ssize)
int ssize = 1; // 段的数目,segments数组的大小(2的幂次方)
// 并发级别可能不是2的幂次方 ,转换一下
// 假设并发级别是5,那么就会分为8段
while (ssize < concurrencyLevel) {
++sshift;
ssize <<= 1;
}
segmentShift = 32 - sshift; // 用于定位段
segmentMask = ssize - 1; // 用于定位段
//
this.segments = Segment.newArray(ssize); // 创建segments数组
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
int c = initialCapacity / ssize; // 总的桶数/总的段数 = 平均每个段中的桶数目 ,但是不是 2 幂次方
if (c * ssize < initialCapacity)
++c;
int cap = 1; // 每个段所拥有的桶的数目(2的幂次方)
while (cap < c)
cap <<= 1;
for (int i = 0; i < this.segments.length; ++i) // 初始化segments数组
this.segments[i] = new Segment<K,V>(cap, loadFactor);
}
c. put操作
d. get操作
// Segment 类
V get(Object key, int hash) {
if (count != 0) { // read-volatile,首先读 count 变量
HashEntry<K,V> e = getFirst(hash); // 获取桶中链表头结点
while (e != null) {
if (e.hash == hash && key.equals(e.key)) { // 查找链中是否存在指定Key的键值对
V v = e.value;
if (v != null) // 如果读到value域不为 null,直接返回
return v;
// 如果读到value域为null,说明发生了重排序,加锁后重新读取
return readValueUnderLock(e); // recheck
}
e = e.next;
}
}
return null; // 如果不存在,直接返回null
}
e. rehash操作
f. size() 统计大小操作
- 不加锁重试2次,每次去统计segment中的数量的时候,计入modCount的大小, 所有的段统计完成,去比较一下每个段中的modCount是否产生变化
- 如果产生变化,那么就对所有的段加锁,再次统计一下
g. 与JDK1.8实现的ConcurrentHashMap对比
https://blog.csdn.net/fouy_yun/article/details/77816587
1. 取消了分段
2. 链表长度默认超过8 ,变成红黑树
put操作 部分添加操作加锁
1. 若table[]未创建,则初始化。 2. 当table[i]后面无节点时,直接创建Node(无锁操作)。 cas原子操作 3. 如果当前正在扩容,则帮助扩容并返回最新table[]。 4. 然后在链表或者红黑树中追加节点。 锁住当前table[i] 5. 最后还回去判断是否到达阀值,如到达变为红黑树结构。
get操作 整个过程都没有加锁
1. 首先定位到table[]中的i。 2. 若table[i]存在,则继续查找。 3. 首先比较链表头部,如果是则返回。 4. 然后如果为红黑树,查找树。 5. 最后再循环链表查找。
2.4 彻头彻尾理解 HashTable
https://blog.csdn.net/justloveyou_/article/details/72862373
- Hashtable不同于HashMap,前者既不允许key为null,也不允许value为null;
- HashMap中用于定位桶位的Key的hash的计算过程要比Hashtable复杂一点,没有Hashtable如此简单、直接;
- 在HashMap的插入K/V对的过程中,总是先插入后检查是否需要扩容;而Hashtable则是先检查是否需要扩容后插入;
- Hashtable不同于HashMap,前者的put操作是线程安全的
put操作 :- 方法同步
- 不允许空value
- 没有对key做出null处理,暗示key不能为null
- 计算桶的位置使用的是取模运算,也不是HashMap的&运算,也就是说HashTable的初始容量没有什么要求
- 先判断是否需要扩容,再插入节点
- 方法同步
public synchronized V put(K key, V value) { // 加锁同步,保证Hashtable的线程安全性
// Make sure the value is not null
if (value == null) { // 不同于HashMap,Hashtable不允许空的value
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry tab[] = table;
int hash = key.hashCode(); // key 的哈希值,同时也暗示Hashtable不同于HashMap,其不允许空的key
int index = (hash & 0x7FFFFFFF) % tab.length; // 取余计算节点存放桶位,0x7FFFFFFF 是最大的int型数的二进制表示
// 先查找Hashtable上述桶位中是否包含具有相同Key的K/V对
for (Entry<K, V> e = tab[index]; e != null; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
V old = e.value;
e.value = value;
return old;
}
}
// 向Hashtable中插入目标K/V对
modCount++; // 发生结构性改变,modCount加1
if (count >= threshold) { //在插入目标K/V对前,先检查是否需要扩容(不同于HashMap的插入后检查是否需要扩容)
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
index = (hash & 0x7FFFFFFF) % tab.length; // 扩容后,重新计算K/V对插入的桶位
}
// Creates the new entry.
Entry<K, V> e = tab[index];
tab[index] = new Entry<K, V>(hash, key, value, e); // 将K/V对链入对应桶中链表,并成为头结点
count++; // Hashtable中Entry数目加1
return null;
}
get操作
- 同步方法
- 取模的方式定位桶位置
- 在链表中查找指定的key ,没找到返回null
public synchronized V get(Object key) { // 不同于HashMap,Hashtable的读取操作是同步的
Entry tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length; // 定位K/V对的桶位
for (Entry<K, V> e = tab[index]; e != null; e = e.next) { // 在特定桶中依次查找指定Key的K/V对
if ((e.hash == hash) && e.key.equals(key)) {
return e.value;
}
}
return null; // 查找失败
}
3. java面试锦集
https://blog.csdn.net/justloveyou_/article/details/78653660
https://blog.csdn.net/justloveyou_/article/details/78653929
https://blog.csdn.net/justloveyou_/article/details/78313167
4. String
https://blog.csdn.net/justloveyou_/article/details/52556427
https://blog.csdn.net/justloveyou_/article/details/60983034
1.String
对String的一般认识编译期间就会把字面字符串放到字符串常量池 字符串拼接操作
- 类使用了final修饰,不能被继承
- 内部字段也被final修饰,初始化就不能再次修改
- String是一个不可变类,线程安全
- 内部使用的是char[]来表示
引用拼接的本质:
- 字符串字面量拼接,标量替换
- 字符串引用拼接 ,还是会调用StringBuilder.append toString返回一个String对象
String s4 = s1 + s2 + s3;
====>
String s4 = (new StringBuilder(String.valueOf(s1))).append(s2).append(s3).toString();
2.StringBuilder
- 不是线程安全的类
- 是单线程下替换StringBuffer的一个类
3. StringBuffer
- 是一个线程安全的类
4. 总结
- 使用字面值形式创建字符串时,不一定会创建对象,但其引用一定指向位于字符串常量池的某个对象;
- 使用 new String(“…”)方式创建字符串时,一定会创建对象,甚至可能会同时创建两个对象(一个位于字符串常量池中,一个位于堆中);
- String 对象是不可变的,对String 对象的任何改变都会导致一个新的 String 对象的产生,而不会影响到原String 对象;
- StringBuilder 与 StringBuffer 具有共同的父类,具有相同的API,分别适用于单线程和多线程环境下。特别地,在单线程环境下,StringBuilder 是 StringBuffer 的替代品,前者效率相对较高;
5. 泛型
https://blog.csdn.net/s10461/article/details/53941091
泛型遇上多态
https://blog.csdn.net/lonelyroamer/article/details/7868820
6.注解
https://blog.csdn.net/briblue/article/details/73824058
https://www.cnblogs.com/Qian123/p/5256084.html
7.java io
7.1 File
7.2 字节流字符流
7.3 Pushback流
7.4 RandomAccessFile
7.5 序列化
自定义readObject函数的调用过程
https://blog.csdn.net/xiaoanian/article/details/9064635
https://www.cnblogs.com/yoohot/p/6019767.html
a.序列化算法
- 所有的对象都有一个序列化编号
- 检查对象是否已经被序列化
- 如果已经序列化,那么只输出序列化编号
note: 注意可变对象的序列化
b.反序列化创建对象
- 获取反序列化对象的元数据(Class文件) ——> 该类还没有被加载,那就执行类加载机制
- 在内存中创建内存空间
- 直接内存赋值
- 不需要调用构造函数
c.自定义序列化
- 实现
Serializable
接口,该接口就是一个标记,内部没有任何东西 - 实现
Externalizable
接口
d.注意事项
- 父类实现了序列化,子类自动实现序列化
- 基类没有实现序列化
- 如果 存在无参构造函数,那么序列化过程中不会报错,但是不会保存实例变量值
- 如果不存在无参构造函数,那么序列化过程中直接报错
- transient修饰的变量不参与序列化,类变量也不参与序列化
8 线程
8.1 创建线程的三种方式
- 继承Thread
- 实现Runnable接口
- 实现Callable接口
8.2 Daemon线程
- 需要在线程启动之前设置为后台线程,否则线程启动之后,线程状态是Runnable,而在setDaemon函数中会判断线程状态是不是new状态,不是就报错了
- 所有的前台线程退出了,那么后台线程也就退出了
- Daemon线程中再次创建线程也是Daemon线程
8.3 interrupt的理解
https://www.ibm.com/developerworks/cn/java/j-jtp05236.html
https://www.cnblogs.com/timlearn/p/4008783.html
1. java中断是一种协调机制
2. 线程对象调用interrupt函数向线程发送一个中断信号
3. 在线程内部,如果线程处于阻塞,那么就会抛出InterruptException异常;如果处于运行状态,那么就会设置一下该线程的中断状态为真
4. isInterrupted()函数返回线程现在是不是中断状态,但是不会影响中断状态
5. interrupted()静态函数,会重置中断状态
8.4 synchronized volatile
- volatile
- 保证各线程的可见性,但是不保证原子性.线程的工作内存马上同步到主内存中
- 禁止指令重排序
- 轻量级锁
https://www.cnblogs.com/wq3435/p/6220751.html
https://blog.csdn.net/justloveyou_/article/details/53672005
- synchronized可以保证
8.5 wait notify
https://www.cnblogs.com/hapjin/p/5492645.html
线程状态转移
https://blog.csdn.net/pange1991/article/details/53860651
问题1
: 为什么会假唤醒?
问题2
: 为什么需要需要同步?
synchronized(object){
// 使用while防止假唤醒
while(条件不满足){
object.wait();
}
//逻辑处理;
}
synchronized(object){
//改变条件
object.notifyAll();
}
- 对象的wait notify方法必须在同步快内部,也就是说需要获取对象锁才可以调用执行
- notify()执行,
出了同步快,那么就会唤醒该对象等待队列上的线程,进入同步队列
- wait()执行,
放弃cpu资源,释放锁,线程放进等待队列中,等待唤醒
4. Lock
5. 并发容器
6. 偏向锁,轻量锁,重量级锁
7 内存屏障
http://ifeve.com/disruptor-memory-barrier/
8 可重入锁解析 ReentrantLock
http://www.cnblogs.com/xrq730/p/4979021.html
https://blog.csdn.net/lsgqjh/article/details/63685058
1.加锁过程
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) { //如果没有线程获取锁,则当前线程CAS获取锁。并设置自己为当前锁的独占线程
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {// 如果存在锁竞争,判断获取锁的线程是否是当前线程, 如果是 那么根据可重入的含义, 使当前state+1;
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}如果不是当前线程,则不能获取同步状态
return false;
}
- 获取volatile修饰的state值
- 如果state的值为0,说明还没有线程持有锁
- 使用CAS操作设置state的值
- 设置当前线程持有锁
state不为0,说明当前有线程持有该锁,那么判断是不是当前线程想重入
- 如果是,重新设置state的值
- 如果不是,获取锁失败
2.解锁过程
protected final boolean tryRelease(int releases) { int c = getState() - releases; // 只有获得锁的线程自己才能释放锁 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) {//c==0说明已经无锁 free = true; setExclusiveOwnerThread(null); } setState(c); //否则更新state为state-1, 也就是加锁多少次,就得释放多少次, lock unlock配对使用。 return free; }
- 当前线程不持有锁,解锁异常抛出
- 获取state值
- 如果state的值为0了,那么说明解锁完就没有那个对象持有该锁了,需要设置锁没有持有任何线程
- 重新设置state
9 final
https://www.cnblogs.com/senlinyang/p/7875468.html
1. 写final域的重排序规则:禁止把final域的写重排序到构造函数之外——>对象的引用在任意线程可见之前,final域一定是已经正确初始化过的
通过下面2种方式保证
a. JMM禁止编译器把final域写重排序到构造函数之外
b. 编译器会在final域的写之后,构造函数return之前,插入一个StoreStore内存屏障
2. 读final域的重排序规则: 初次读对象引用和初次读对象包含的final域两个操作之间禁止重排序,编译器会在两个操作之间插入LoadLoad内存屏障——–>在读一个对象的final域之前,一定会先读取包含这个final域的对象的引用
3. final域是一个引用类型,final域对象内部成员的初始化和使用对象之间禁止重排序
final域可以给我们一下保证: 只要对象正确的构造,那么不需要同步,任意线程都可以看到final域在构造函数中初始化值