文章目录
集合类图
集合的原理,区别
1、ArrayList和LikedList的区别?
- ArrayList的底层实现是Object数组。当我们new
// 初始化数组的长度
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 初始化的数组
transient Object[] elementData; // non-private to simplify nested class access
// list中存在多少个元素
private int size;
// 创建一个默认大小为 10 的list集合
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 扩容机制,每次都是以当前容量大小的1.5倍进行扩容
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
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);
}
- LinkedList的底层实现是 Node节点,将数据通过前后指针链接起来
transient int size = 0;
/**
* Pointer to first node.
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first;
/**
* Pointer to last node.
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last;
/**
* Constructs an empty list.
*/
public LinkedList() {
}
// 当add进行数据添加的时候,每次都是将数据添加在Node节点的最后一个位置
public boolean add(E e) {
linkLast(e);
return true;
}
/**
* Links e as last element.
*/
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++;
}
// Node 节点
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;
}
}
2、hashSet和LinkedHashSet的区别?
hashSet的底层实现就是HashMap
LinkedHashset的底层实现其实就是 LinkedHashMap
所以这2个的区别其实就是 HashMap 与 LinkedHashMap 的区别
private transient HashMap<E,Object> map;
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
// new HashSe的时候其实就是创建一个 大小为16,负载因子为0.75的 HashMap
public HashSet() {
map = new HashMap<>();
}
// 添加元素的时候,其实就是将传入的对象作为key进行存储,value其实是 new Object() 对象
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
3、HashSet与TreeSet的区别?
HashSet 的底层是 HashMap
TreeSet 的底层是 TreeMap
所以它们的区别其实就是 HashMap 与 TreeMap的区别
TreeMap的底层实现,是基于红黑树实现的
// TreeMap属性
private final Comparator<? super K> comparator;
private transient TreeMap.Entry<K, V> root;
private transient int size = 0;
private transient int modCount = 0;
private transient TreeMap<K, V>.EntrySet entrySet;
private transient TreeMap.KeySet<K> navigableKeySet;
private transient NavigableMap<K, V> descendingMap;
private static final Object UNBOUNDED = new Object();
private static final boolean RED = false;
private static final boolean BLACK = true;
// TreeMap构造器
public TreeMap() {
this.comparator = null;
}
public TreeMap(Comparator<? super K> var1) {
this.comparator = var1;
}
public TreeMap(Map<? extends K, ? extends V> var1) {
this.comparator = null;
this.putAll(var1);
}
public TreeMap(SortedMap<K, ? extends V> var1) {
this.comparator = var1.comparator();
try {
this.buildFromSorted(var1.size(), var1.entrySet().iterator(), (ObjectInputStream)null, (Object)null);
} catch (IOException var3) {
} catch (ClassNotFoundException var4) {
}
}
// 存储数据的Entry
static final class Entry<K,V> implements Map.Entry<K,V> {
K key;
V value;
Entry<K,V> left; // 左节点
Entry<K,V> right; // 右节点
Entry<K,V> parent; // 父节点
boolean color = BLACK; // 节点颜色
/**
* Make a new cell with given key, value, and parent, and with
* {@code null} child links, and BLACK color.
*/
Entry(K key, V value, Entry<K,V> parent) {
this.key = key;
this.value = value;
this.parent = parent;
}
/**
* Returns the key.
*
* @return the key
*/
public K getKey() {
return key;
}
/**
* Returns the value associated with the key.
*
* @return the value associated with the key
*/
public V getValue() {
return value;
}
/**
* Replaces the value currently associated with the key with the given
* value.
*
* @return the value associated with the key before this method was
* called
*/
public V setValue(V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
}
public boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
return valEquals(key,e.getKey()) && valEquals(value,e.getValue());
}
public int hashCode() {
int keyHash = (key==null ? 0 : key.hashCode());
int valueHash = (value==null ? 0 : value.hashCode());
return keyHash ^ valueHash;
}
public String toString() {
return key + "=" + value;
}
}
TreeMap.put()添加元素的时候,会对key进行排序。默认是正序排序。
/**
* 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 {@code key}, or
* {@code null} if there was no mapping for {@code key}.
* (A {@code null} return can also indicate that the map
* previously associated {@code null} with {@code key}.)
* @throws ClassCastException if the specified key cannot be compared
* with the keys currently in the map
* @throws NullPointerException if the specified key is null
* and this map uses natural ordering, or its comparator
* does not permit null keys
*/
public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) {
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
else {
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
4、HashMap与ConcurrentHashMap的区别?
HashMap不是线程安全的
ConcurrentHashMap是线程安全的
在7中ConcurrentHashMap是通过分段锁(segment来实现并发的)
在8中通过数组+cas+红黑树来实现并发
下面是java8中 ConcurrentHashMap 的底层实现
1、Node节点用来存储数据,hash,key都是 final;val 和 next 都是用 volatile 修饰。
所以在高并发时,可以保证内存数据可见性。
/**
* Key-value entry. This class is never exported out as a
* user-mutable Map.Entry (i.e., one supporting setValue; see
* MapEntry below), but can be used for read-only traversals used
* in bulk tasks. Subclasses of Node with a negative hash field
* are special, and contain null keys and values (but are never
* exported). Otherwise, keys and vals are never null.
*/
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
volatile V val;
volatile Node<K,V> next;
Node(int hash, K key, V val, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.val = val;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return val; }
public final int hashCode() { return key.hashCode() ^ val.hashCode(); }
public final String toString(){ return key + "=" + val; }
public final V setValue(V value) {
throw new UnsupportedOperationException();
}
public final boolean equals(Object o) {
Object k, v, u; Map.Entry<?,?> e;
return ((o instanceof Map.Entry) &&
(k = (e = (Map.Entry<?,?>)o).getKey()) != null &&
(v = e.getValue()) != null &&
(k == key || k.equals(key)) &&
(v == (u = val) || v.equals(u)));
}
/**
* Virtualized support for map.get(); overridden in subclasses.
*/
Node<K,V> find(int h, Object k) {
Node<K,V> e = this;
if (k != null) {
do {
K ek;
if (e.hash == h &&
((ek = e.key) == k || (ek != null && k.equals(ek))))
return e;
} while ((e = e.next) != null);
}
return null;
}
}
下面的代码是往 concurrentHashMap 中存放数据的逻辑
public V put(K key, V value) {
return putVal(key, value, false);
}
/** Implementation for put and putIfAbsent */
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> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++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 (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
5、HashMap 与 LinkedHashMap 区别
-
HashMap 是无序的,如果我们希望有顺序的去存储 key-value类型的数据,就需要用到 LinkedHashMap、
-
LinkedHashMap 继承了 HashMap,当调用 LinkedHashMap.put() 的时候,实际上是调用了HashMap.put(); 保存的数据都是在Node节点中。但是由于 LinkedHashMap.Entry 同时也继承了 HashMap.Node; 此时Entry 除了存储的数据之外,还有了前后指针 before,after
/**
* HashMap.Node subclass for normal LinkedHashMap entries.
*/
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
在调用HashMap的时候,最后会将数据插入至前一个数据的节点上
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry<K,V> first;
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
LinkedHashMap 在添加数据的时候,下面标红的几行都是继承了HashMap,此时就会在LinkedHashMap 的尾部节点上添加数据,链式的存数据。
6、ConcurrentHashMap
jdk1.8 ConcurrentHashMap 与 jdk1.8 HashMap的结构大体是一致的
jdk1.7 采用的是 分段锁(segmengt)的机制,一个分段 segmengt 就是一个 数组,segmengt继承了 ReentrantLock,实现了加锁。
但是在jdk1.8,摒弃了分段锁(segmengt),通过使用 CAS + Synchronized 的方式来保证并发安全性。
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> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
// 计算出的 f 就是经过位与运算计算出的值,如果为空,表示数据可以尝试写入,
// 写入的时候,利用 CAS 尝试写入,失败则自旋保证成功。
}
concurrentHashMap在添加元素的时候,多线程之间,以volatile的方式读取sizeCtl属性,
来判断ConcurrentHashMap当前所处的状态。通过cas设置sizeCtl属性,告知其他线程ConcurrentHashMap的状态变更。
扩容:
未初始化:
sizeCtl=0:表示没有指定初始容量。
sizeCtl>0:表示初始容量。
初始化中:
sizeCtl=-1,标记作用,告知其他线程,正在初始化
正常状态:
sizeCtl=0.75n ,扩容阈值
扩容中:
sizeCtl < 0 : 表示有其他线程正在执行扩容
sizeCtl = (resizeStamp(n) << RESIZE_STAMP_SHIFT) + 2 :表示此时只有一个线程在执行扩容