常用集合List、Set、Map
集合中存在两种数据存储方式:顺序存储和链式存储
List
接口,interface List<E> extends Collection<E>
实现类ArrayList,LinkdList、Vector
ArrayList底层实际维护的是数组,对集合的操作最后都是对数组的操作,所以查找快,增删慢(要改变增删元素后面的所有元素的位置下标)
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
size = elementData.length;
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
Vector的底层实现跟ArrayList基本一样,唯一不同的地方在于Vector的大部分方法加了synchronized
public Vector(Collection<? extends E> c) {// vector 底层
elementData = c.toArray();
elementCount = elementData.length;
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
}
public synchronized E set(int index, E element) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
LinkedList 底层维护的是一个双向链表,每一个表项都为一个节点Node,一个节点包含有前驱表项、本表项和后驱表项。无论链表里是否有元素都会有有一个header表项,既是一个链表的开始,也是一个链表的结束。
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;
}
}
表项位置的变动只需维护此表项的前驱表项和后驱表项的信息即可,因此增删快,查找慢,要一个一个查
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
Map
public interface Map<K,V> Map实际上并未继承Collection接口
实现类 HashMap、Hashtable、LinkedHashMap、TreeMap
HashMap的主干部分实际上是Entry<K,V>实体组成的数组,外加单向链表,链表的每一节点都是一个Entry<K,V>,由于存在不同的key计算出来的hash值有可能一样,也就是常说的哈希碰撞,才加的链表。
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;// 单向链表,只记录下一个表项的信息
int hash;// 当前表项的哈希值
/**
* Creates new entry.
*/
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
public final K getKey() {
return key;
}
public final V getValue() {
return value;
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry)o;
Object k1 = getKey();
Object k2 = e.getKey();
if (k1 == k2 || (k1 != null && k1.equals(k2))) {
Object v1 = getValue();
Object v2 = e.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2)))
return true;
}
return false;
}
public final int hashCode() {// 计算hashcode值
return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
}
public final String toString() {
return getKey() + "=" + getValue();
}
/**
* This method is invoked whenever the value in an entry is
* overwritten by an invocation of put(k,v) for a key k that's already
* in the HashMap.
*/
void recordAccess(HashMap<K,V> m) {
}
/**
* This method is invoked whenever the entry is
* removed from the table.
*/
void recordRemoval(HashMap<K,V> m) {
}
}
从put(k,v)看hashmap原理
public V put(K key, V value) {
if (table == EMPTY_TABLE) {// 如果是空,则创建一定容量大小的hashMap(默认16)
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);// hashMap 可以有一个key的值为null,并放在首位
int hash = hash(key);// 计算key的哈希值
int i = indexFor(hash, table.length);// 根据哈希值和当前表长度计算存放位置下标
// 判断当前位置是否含有元素
for (Entry<K,V> e = table[i]; e != null; e = e.next) {// 遍历该位置的链表
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {// key的hash值和key的equal方法相等时进行value的替换操作
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);// 没有元素时,将此元素存放在此位置
return null;
}
整个流程为 计算key的hashcode值----->计算hash值------>计算存放下标i
hashtable 底层跟hashMap基本差不多,最大的区别就是hashtable大部分方法前加了synchronized
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 = hash(key);
int index = (hash & 0x7FFFFFFF) % tab.length;
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;
}
}
modCount++;
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
hash = hash(key);
index = (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry.
Entry<K,V> e = tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
return null;
}
LinkedHashMap 有序无重复
class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>
相比HashMap而言,LinkedHashMap的Entry<K,V> 多了Entry<K,V> before, after;两个成员属性,分别代表前一个和后一个加入的Entry实体,也就是说在HashMap的数组加单链表的存储结构的基础上加了双链表,正是由于双链表的加入使得LinkedHashMap变得有序
/**
* LinkedHashMap entry.
*/
private static class Entry<K,V> extends HashMap.Entry<K,V> {
// These fields comprise the doubly linked list used for iteration.
Entry<K,V> before, after;
Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
super(hash, key, value, next);
}
/**
* Removes this entry from the linked list.
*/
private void remove() {
before.after = after;
after.before = before;
}
/**
* Inserts this entry before the specified existing entry in the list.
*/
private void addBefore(Entry<K,V> existingEntry) {
after = existingEntry;
before = existingEntry.before;
before.after = this;
after.before = this;
}
/**
* This method is invoked by the superclass whenever the value
* of a pre-existing entry is read by Map.get or modified by Map.set.
* If the enclosing Map is access-ordered, it moves the entry
* to the end of the list; otherwise, it does nothing.
*/
void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
if (lm.accessOrder) {
lm.modCount++;
remove();
addBefore(lm.header);
}
}
void recordRemoval(HashMap<K,V> m) {
remove();
}
}
HashMap与LinkedhashMap
TreeMap是基于二叉树的红黑树,工作上基本没有碰到过,在此先略过...
Set
接口 public interface Set<E> extends Collection<E>
实现类 HashSet、LinkedHashSet、TreeSet
HashSet 可以理解成一个Value为固定值的HashMap
public HashSet() {// 底层实际是HasMap
map = new HashMap<>();
}
public boolean add(E e) {// 添加实体最终还是HashMap的添加方法
return map.put(e, PRESENT)==null;
}
但是HashSet和HashMap的方法不一样,添加时,HashSet为add(E e),HashMap为put(K k,V v),获取时HashSet只能迭代获取元素,HashMap有get(K k)。
LinkedHashSet 继承了HashSet,LinkedHashSet底层维护的实际是LinkedHashMap
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
同样的逻辑,TreeSet可以理解成一个Value为固定值的TreeMap