# 容器概述
# List,Set,Map三者区别:
- List: 线性,有序,可重复
- Set: 线性,无序,不可重复
- Map: 键值对存储。 key时无序,不可重复;value是无序,可重复;
# Collection集合定义及其子类
- 抽象结构定义
public interface Collection<E> extends Iterable<E> {
int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
Object[] toArray();
boolean add(E e);
boolean remove(Object o);
void clear();
//...省略部分抽象方法
}
- List:
- ArrayList数组
- LinkedList链表
- Vector数组
public interface List<E> extends Collection<E> {
//... 省略部分父类抽象定义
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}
E get(int index);
E set(int index, E element);
void add(int index, E element);
E remove(int index);
int indexOf(Object o);
int lastIndexOf(Object o);
List<E> subList(int fromIndex, int toIndex);
}
- Set:
- HashSet: 无序,唯一;基于HashMap实现,底层采用HashMap保存元素;
- LinkedHashSet: 基于LinkedHashMap实现;
- TreeSet: 有序,唯一;红黑树实现;
public interface Set<E> extends Collection<E> {
//... 完美继承Collection抽象定义
//注意,它没有 get(index) 方法
}
# Map字典定义及其子类
- HashMap: java8以前是: 数组+链表实现;java8以后是: 数组+链表+红黑树;
- LinkedHashMap: 增加了双向链表,使Map结构可以保持键值对的插入顺序;
- Hashtable(t小写): 数组+链表;
- TreeMap: 红黑树
public interface Map<K,V> {
int size();
boolean isEmpty();
boolean containsKey(Object key);
boolean containsValue(Object value);
V get(Object key);
V put(K key, V value);
V remove(Object key);
void clear();
Set<K> keySet();
Collection<V> values();
Set<Map.Entry<K, V>> entrySet();
default V getOrDefault(Object key, V defaultValue) {
V v;
return (((v = get(key)) != null) || containsKey(key))
? v
: defaultValue;
}
default V putIfAbsent(K key, V value) {
V v = get(key);
if (v == null) {
v = put(key, value);
}
return v;
}
default V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction) {
Objects.requireNonNull(mappingFunction);
V v;
if ((v = get(key)) == null) {
V newValue;
if ((newValue = mappingFunction.apply(key)) != null) {
put(key, newValue);
return newValue;
}
}
return v;
}
interface Entry<K,V> {
K getKey();
V getValue();
V setValue(V value);
}
//... 省略部分抽象定义
}
# List列表
# ArrayList与Vector的区别
- ArrayList: 线程不安全
- Vector: 线程安全
# ArrayList 与 LinkedList 的区别
- 底层数据结构: -> ArrayList插入删除慢,查找快,LinkedList相反.
- ArrayList: 采用对象数组存储
- LinkedList: 采用双向链表,JDK7取消了循环
- ArrayList查找(随机访问)速度快于LinkedList原因:
// ArrayList的查找方法
class ArrayList{
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
//按位置查找
E elementData(int index) {
return (E) elementData[index];
}
//按值查找
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
}
//LinkedList的查找方法
class LinkedList{
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
//按位置查找
Node<E> node(int index) {
//从头指针开始移动
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
//从尾指针开始移动
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
//按值查找
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
}
- ArrayList插入删除慢于LinkedList原因(个人感觉不一定):
//ArrayList的插入方法
class ArrayList{
//直接插入
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
//按位置插入
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
}
//LinkedList的插入方法
class LinkedList{
//按值插入
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
//按位置添加
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
}
- 内存占用的区别
- ArrayList: 采用对象数组,数量不够时,采用扩容机制进行扩容;
- LinkedList: 采用双向链表,采取新增节点的方式进行存储;节点除了包含数据本身外,还包括前序节点,后续节点;
# ArrayList的扩容机制
ArrayList的构造函数
- 空参构造: jdk7直接构造了大小为10的对象数组; jdk8构造了空数组,在第一次插入数据时扩容为大小为10的对象数组;
- 带初始容量参数的构造: 用户自己指定初始化数组的大小;
- 构造包含指定元素的构造: 初始化数组的大小,就是指定元素的大小;
动态扩容
- 扩容大小: 扩容至1.5倍左右; 原大小是偶数时,就是扩容1.5倍; 原大小是奇数时,就是扩容1.5倍左右;
//ArrayList动态扩容方法
class ArrayList{
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// 新大小 = 原大小*1.5
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
# Set集合
# Comparable 与 Comparator的区别
- Comparable: 出自java.lang包, 使用 compareTo(obj) 进行排序
- Comparator: 出自java.util包, 使用 compare(obj1,obj2) 进行排序
# HashSet,LinkedHashSet,TreeSet异同
- HashSet: 底层采用 HashMap,线程不安全,可存储null值;
- LinkedHashSet: HashSet的子类,能够按照添加的顺序遍历
- TreeSet: 底层使用红黑树
# HashSet
- 抽象结构:
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
}
# LinkedHashSet
- 抽象结构
public class LinkedHashSet<E>
extends HashSet<E>
implements Set<E>, Cloneable, java.io.Serializable {
@Override
public Spliterator<E> spliterator() {
return Spliterators.spliterator(this, Spliterator.DISTINCT | Spliterator.ORDERED);
}
//... 省略部分抽象定义
}
# TreeSet
- 抽象结构
public class TreeSet<E> extends AbstractSet<E>
implements NavigableSet<E>, Cloneable, java.io.Serializable
{
//hashMap
private transient NavigableMap<E,Object> m;
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
private static final Object PRESENT = new Object();
}
# Map字典
# HashMap
- 抽象结构:
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
static final float DEFAULT_LOAD_FACTOR = 0.75f;
final float loadFactor;
transient Node<K,V>[] table;
transient int size;
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
}
//结果为 比 cap大的,最小二次幂数
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
//通过 key值计算 hash。
// 从这里可以看出,key是可以为空的。 value自然没有要求
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
//通过hash定位,如果有值,才进行冲突检测等后续查询
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
//如果是红黑树,则通过红黑树特有的方法进行获取节点
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
//如果是普通的链表,则直接往后循环即可
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> firstNode; int tableLength, i;
if ((tab = table) == null || (tableLength = tab.length) == 0)
tableLength = (tab = resize()).length;
//通过hash定位,没有冲突的情况下,直接插入(node表)
if ((firstNode = tab[i = (tableLength - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
//存在冲突,则采用拉链法,或者红黑树处理冲突
Node<K,V> e; K k;
//如果hash冲突了,但是key相等,则直接覆盖
if (firstNode.hash == hash &&
((k = firstNode.key) == key || (key != null && key.equals(k))))
e = firstNode;
//如果hash冲突了,但是key不等,并且当前节点为 红黑树节点,则调用红黑树进行插入
else if (firstNode instanceof TreeNode)
e = ((TreeNode<K,V>)firstNode).putTreeVal(this, tab, hash, key, value);
else {
//如果hash冲突了,先用拉链法,进行处理
for (int binCount = 0; ; ++binCount) {
if ((e = firstNode.next) == null) {
firstNode.next = newNode(hash, key, value, null);
//如果拉链法,到达了临界值,则采用红黑树
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//如果在使用拉链法时,遇到了相同的节点,则跳出循环
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
//往链条尾部推进
firstNode = e;
}
}
//如果在拉链法过程中,遇到了相同的key,根据是否允许覆盖,从而做出具体的操作
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
//调整表格大小
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
//当单链表中的元素,有8个及以上的时候,则调整为红黑树
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> firstNode;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
else if ((firstNode = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> treeHead = null, tail = null;
//将链表节点,转换为树节点,并将原有的单链表,组成双链表
do {
TreeNode<K,V> curNode = replacementTreeNode(firstNode, null);
if (tail == null)
treeHead = curNode;
else {
curNode.prev = tail;
tail.next = curNode;
}
tail = curNode;
} while ((firstNode = firstNode.next) != null);
//调整红黑树
if ((tab[index] = treeHead) != null)
treeHead.treeify(tab);
}
}
final Node<K,V>[] resize() {
//进行初始化,或者扩容为原来的2倍
//扩容后,需要为原有的元素复制到新表
//此处省略...
return null;
}
}
# Hashtable
- 抽象结构
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable {
private transient Entry<?,?>[] table;
private transient int count;
private float loadFactor;
public Hashtable(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal Load: "+loadFactor);
if (initialCapacity==0)
initialCapacity = 1;
this.loadFactor = loadFactor;
table = new Entry<?,?>[initialCapacity];
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}
public synchronized V get(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return (V)e.value;
}
}
return null;
}
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
addEntry(hash, key, value, index);
return null;
}
private void addEntry(int hash, K key, V value, int index) {
modCount++;
Entry<?,?> tab[] = table;
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
hash = key.hashCode();
index = (hash & 0x7FFFFFFF) % tab.length;
}
Entry<K,V> e = (Entry<K,V>) tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
}
}
# ConcurrentHashMap
- 抽象结构
public interface ConcurrentMap<K, V> extends Map<K, V> {
//...省略抽象
}
public abstract class AbstractMap<K,V> implements Map<K,V> {
public V put(K key, V value) {
throw new UnsupportedOperationException();
}
public abstract Set<Entry<K,V>> entrySet();
public V get(Object key) {
Iterator<Entry<K,V>> i = entrySet().iterator();
if (key==null) {
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (e.getKey()==null)
return e.getValue();
}
} else {
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (key.equals(e.getKey()))
return e.getValue();
}
}
return null;
}
}
public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
implements ConcurrentMap<K,V>, Serializable {
private static final sun.misc.Unsafe U;
public V put(K key, V value) {
return putVal(key, value, false);
}
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> firstNode; int n, currentHash, firstHash;
//表未初始化,则先进行初始化
if (tab == null || (n = tab.length) == 0)
tab = initTable();
//没有存在hash冲突时,
else if ((firstNode = tabAt(tab, currentHash = (n - 1) & hash)) == null) {
//使用原子操作,插入当前节点,这种操作是不需要加锁的
if (casTabAt(tab, currentHash, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
else if ((firstHash = firstNode.hash) == MOVED)
tab = helpTransfer(tab, firstNode);
else {
//存在hash冲突时
V oldVal = null;
//添加同步锁
synchronized (firstNode) {
if (tabAt(tab, currentHash) == firstNode) {
if (firstHash >= 0) {
binCount = 1;
//将当前节点加入链表
for (Node<K,V> e = firstNode;; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
//如果当前链表为红黑树,则采用红黑树的方法插入节点
else if (firstNode instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)firstNode).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
//如果链表超过了指定长度,则调整为红黑树
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, currentHash);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
public V get(Object key) {
//获取节点时,未进行加锁
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
int h = spread(key.hashCode());
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
if ((eh = e.hash) == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
else if (eh < 0)
return (p = e.find(h, key)) != null ? p.val : null;
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
Node<K,V> c, Node<K,V> v) {
return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}
}
# HashMap与Hashtable区别
- 线程安全性: Hashtable是线程安全的,HashMap不是
- 效率: HashMap的效率更高
- null key 与 null value的支持: HashMap支持null Key以及null Value; Hashtable 不支持null Key与null Value
- 初始容量与扩容大小不同:
- 初始大小: Hashtable直接使用指定的大小; HashMap扩充为最小的2的幂次方大小;
- 初始时不指定大小时: Hashtable默认初始大小11,扩容为2n+1; HashMap默认16,扩容为2n;
- 底层数据结构不同: Hashtable采用数组+链表(拉链法解决hash冲突); HashMap采用数组+链表实现(链表可以调整为红黑树,拉链法解决hash冲突);
# HashMap 与 HashSet区别
- hashCode值的计算: HashMap通过键计算; HashSet通过 成员对象 计算;
class HashSet{
private static final Object PRESENT = new Object();
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
}
class HashMap{
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
}
# HashMap的底层实现
- JDK8以前: 数组+链表
- JDK8以后: 数组+链表+红黑树
# HashMap的长度为什么是2的幂次方
- 运算效率: 取余操作,使用二进制&操作,具有更高的运算效率;
# HashMap的遍历方式
- 迭代器遍历
- forEach方法遍历
- lambda方式遍历
- Streams方式遍历
# ConcurrentHashMap 与 Hashtable 的区别
- 底层数据结构: ConcurrentHashMap采用 数组+链表+红黑树(java8及以后); Hashtable采用数组+链表实现;
- 实现线程安全的方式:
- ConcurrentHashMap:
- java7对数组的不同部分进行分割,每一把锁,只锁容器的一部分数据;
- java8直接使用 数组+链表+红黑树结构实现,并发控制通过 方法内部synchronized和原子操作实现;
- Hashtable: 全表使用同一把锁来保证线程安全,效率非常低下;
- ConcurrentHashMap:
# ConcurrentHashMap的具体实现方式:
- JDK7:
- 一个ConcurrentHashMap里面包含一个 Segment数组,是一个可重入锁;
- Segment的结构也是数组+链表
- JDK8:
- synchronize 之锁定当前链表或者红黑树的首节点;
- 在hash没有冲突的情况下,就不会产生并发,效率可提升N倍;
# HashMap疑难篇
# 底层数据结构
- java7: 数组+链表
- java8: 数组+链表+红黑树
- 红黑树的条件
- 链表长度大于阈值(8) 并且 数组长度超过64;
- 红黑树的条件
# 存值过程
- java7:
- key的hashCode经过扰动函数后,得到hash值
- (n-1)&hash,判断元素存放位置
- 如果存在元素,判断该元素的key是否与即将插入的key相同;
- 相同则进行覆盖,不同则采用拉链法进行解决(头插法);
- java8:
- 前三步一样
- 相同则覆盖,不同则新增节点。如果原有节点是树节点,则直接通过红黑树插入,否则通过拉链法插入;
- 拉链法插入后,需要判断是否满足转为红黑树的条件,满足则转为红黑树,不满足则进行扩容;
# 构造方法
- 空参构造
- 包含另一个Map的构造
- 包含 初始数组大小的构造
- 包含 容量大小 和加载因子 的构造;
# 主要方法
- put方法
- get方法
- resize方法: 会伴随一次重新hash分配,会遍历表中的所有元素,非常耗时; 编写程序时,应该尽量避免;
# 扩容条件
- java7:
- 发送hash冲突
- 或者表中元素超过了 初始阈值(16)
- java8:
- 转换二叉树前,都会进行扩容;
# ConcurrentHashMap疑难篇
# 底层结构
- java7: Segment数组+HashEntry数组+链表
- Segment数组,不可变,默认16;
- 每个Segment管理一个 HashMap结构;
- java8:
- Node数组+链表、红黑树
# 构造函数
- java7: 空参构造,内部调用有参构造。
- 初始化容量
- 负载因子
- 默认并发级别
- java8: 空参构造;
# 存值过程
- java7:
- 计算要push的key的位置,获取指定位置的Segment
- 指定位置的 Segment为空,则初始化
- 调用 Segment.put插入
- java8: 1.根据key计算出 hashCode 2.找到key定位的Node,为空则表示可以写入,利用 CAS尝试写入,失败则自旋保证成功 3. 有冲突,则勇synchronized锁写入数据
# 容器疑难
# 数组转换为ArrayList的方法
- 通过Arrays工具类: new ArrayList(Arrays.asList("a","b","c"));
- java8的stream语法: Arrays.stream(myArray).collect(Collectors.toList());
- 使用guava: ImmutableList.of(myArray) 或 ImmutableList.copyOf(myArray)
# 数组的编辑操作
- 不能再 forEach循环里 进行集合元素的 remove/add 操作;
- 使用迭代器进行删除、新增操作
- java8开始支持 removeIf()操作