集合类---Map
Map常用的子类:
一、HashMap详解
1.特点
1)线程不安全。如果想要得到线程安全的HashMap,可以使用Collections的静态方法:Map map = Collections.synchronizedMap(new HashMap());
2)允许null键和null值。只能有一个键为null,可以有多个值为null。如果get()返回null,可以表示没有该键,也可以表示该键所对应的值为null。所以应该用containsKey()或containsValue()来判断是否存在。
3)时间复杂度是o(1)。
4)初始容量和装填因子影响其性能。
5)无序。即不能按照输入的顺序输出,而LinkedHashMap可以(双向链表对其进行保证)。
6)没有contains()方法
2.数据结构
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
HashMap的底层是哈希数组,数组元素为Entry。HashMap通过key的hashCode来计算hash值,当hashCode相同时,通过“拉链法”解决冲突。当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。原本Map.Entry接口的实现类Entry改名为了Node。转化为红黑树时改用另一种实现TreeNode。
Node类:
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;//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 K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
//重写hashCode
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
} public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
//比较key和value是否equals相等
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
3.源码解读
(1)主要属性
//hash表的桶,用一个数组模拟,数组中每个元素都是一个指针,可以是单链表的头指针,也可以是红黑树的根节点的指针
transient Node<K,V>[] table; //用来实现遍历map的set,依次遍历所有的node或者treeNode
transient Set<Map.Entry<K,V>> entrySet; //该map中所有key-value对的个数
transient int size; //修改次数,用来判断该map是否同时被多个线程操作,多线程环境下会抛出异常ConcurrentModificationException
transient int modCount; //= table.size()* loadFactor,当table中实际占用量(不是table中占用的bin的个数,而是所有bin中的Node的总数)超过threshold时,就会进行resize()操作
//eg:table.size()=16,loadFactor=0.75,则当所有Bin中的node的个数超过12时会进行resize.
//table的容量,如果没有设置,则默认等于DEFAULT_INITIAL_CAPACITY=16,且必须为2的整数次幂。
int threshold; //加载因子<=1,当table中实际占用的容量超过table.size()* loadFactor时,会进行table的扩容。默认加载因子为DEFAULT_LOAD_FACTOR=0.75
final float loadFactor; //table默认初始容量 16,必须是2的整数次幂
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; //table的最大容量
static final int MAXIMUM_CAPACITY = 1 << 30; //默认加载因子,当table数组的容量超过table.length* loadFactor时,会调用resize()进行扩容。
static final float DEFAULT_LOAD_FACTOR = 0.75f; //当table[i]中的node个数超过8个,会将单链表table[i]转化成红黑树
static final int TREEIFY_THRESHOLD = 8; //当table[i]中红黑树的节点数少于6时,会退化成单链表
static final int UNTREEIFY_THRESHOLD = 6; //当table的length大于64时,才会进行将某条node个数超过8的单链表转化成红黑树操作
static final int MIN_TREEIFY_CAPACITY = 64;
(2)构造方法
/**
* 根据初始化容量和加载因子构建一个空的HashMap.
*/
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
//设置的初始容量大于最大允许容量,则强制将initialCapacity = MAXIMUM_CAPACITY
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
//调用tableSizeFor将threshold设置成大于initialCapacity的最小的2的整数次幂
this.threshold = tableSizeFor(initialCapacity);
} /**
* 使用初始化容量和默认加载因子(0.75).
*/
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
} /**
* 使用默认初始化大小(16)和默认加载因子(0.75).
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
} /**
* 用已有的Map构造一个新的HashMap.
*/
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
(3)重要方法
1)put方法
- 如果是首个插入map的节点,map进行初始化,在resize中进行
- 如果是put一个新节点,则插入结束后检查对应的bin是否需要转化成红黑树
- 插入或更新结束后,检查该table是否需要resize扩容
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> p; int n, i;
//如果table为null或者table的长度为0,则调用resize()对table进行初始化
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//如果hash号桶中没有node,则将该节点作为首节点放入该桶中
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
//如果hash号桶中有node,则遍历后添加
else {
Node<K,V> e; K k;
//如果key和value都相等,则直接覆盖
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//在红黑树中查找,e指向查找到的treeNode,向红黑树中添加
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
//在单链表中查找
else {
for (int binCount = 0; ; ++binCount) {
//当遍历该桶没有找到相同的key时,就新new一个Node,加入到链表末尾
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
//检查该链表中Node的个数是否>=8,是则要将单链表转化成红黑树
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
break;
}
//在单链表如果key和value都相等,则直接覆盖
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
//更新Node的value
if (!onlyIfAbsent || oldValue == null)
e.value = value;
//回调接口,让LinkedHashMap执行对应的动作。在hashMap中没有动作:void
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
//检查table的size有没有超过临界值,超过要进行resize扩容
if (++size > threshold)
resize();
//回调接口,让LinkedHashMap执行对应的动作。在hashMap中没有动作:void
afterNodeInsertion(evict);
return null;
}
2)get方法
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
} /**
* Implements Map.get and related methods
*
* @param hash hash for key
* @param key the key
* @return the node, or null if none
*/
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
//如果table不为空,table.length>0,且hash对应的桶中有值
if ((tab = table) != null && (n = tab.length) > 0 &&
(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;
} // 遍历红黑树搜索节点
/**
* Calls find for root node.
*/
final TreeNode<K,V> getTreeNode(int h, Object k) {
return ((parent != null) ? root() : this).find(h, k, null);
} /**
* Returns root of tree containing this node.
*/
final TreeNode<K,V> root() {
for (TreeNode<K,V> r = this, p;;) {
if ((p = r.parent) == null)
return r;
r = p;
}
} /**
* Finds the node starting at root p with the given hash and key.
* The kc argument caches comparableClassFor(key) upon first use
* comparing keys.
*/
final TreeNode<K,V> find(int h, Object k, Class<?> kc) {
TreeNode<K,V> p = this;
do {
int ph, dir; K pk;
TreeNode<K,V> pl = p.left, pr = p.right, q;
if ((ph = p.hash) > h) // 当前节点hash大
p = pl; // 查左子树
else if (ph < h) // 当前节点hash小
p = pr; // 查右子树
else if ((pk = p.key) == k || (k != null && k.equals(pk)))
return p; // hash、key都相等,即找到,返回当前节点
else if (pl == null) // hash相等,key不等,左子树为null,查右子树
p = pr;
else if (pr == null)
p = pl;
else if ((kc != null ||
(kc = comparableClassFor(k)) != null) &&
(dir = compareComparables(kc, k, pk)) != 0)
p = (dir < 0) ? pl : pr;
else if ((q = pr.find(h, k, kc)) != null)
return q;
else
p = pl;
} while (p != null);
return null;
}
3)remove方法
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
//removeNode:删除指定的key所对应的Node,matchValue为true表示只有key和value都对应才删除;movable表示在删除时不移动其他node final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Node<K,V>[] tab; Node<K,V> p; int n, index;
//只有在table不为空,table的length大于0,hash&(n-1)号桶中有值时才进行查找删除
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
Node<K,V> node = null, e; K k; V v;
//如果头结点是查找的节点则令node指向它
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p;
else if ((e = p.next) != null) {
//如果这个桶中存储的是红黑树,则通过getTreeNode找到对应的node
if (p instanceof TreeNode)
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
else {
//如果是单链表,则依次查找node,知道找到,令node指向它
do {
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
node = e;
break;
}
p = e;
} while ((e = e.next) != null);
}
}
//当Node==null表示没有找到该节点
//只有当不要求匹配value或者要求匹配并且node的value也相等时,才进行删除
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
//在红黑树中删除节点,把movable传递进去
if (node instanceof TreeNode)
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
//头结点删除
else if (node == p)
tab[index] = node.next;
//p指向node的前一个节点
else
p.next = node.next;
++modCount;
--size;
//回调接口,让LinkedHashMap执行对应的动作。在hashMap中没有动作:void afterNodeRemoval(Node<K,V> p) { }
afterNodeRemoval(node);
return node;
}
}
return null;
}
4)resize方法
- 若table没有初始化,则采用默认的cap和加载因子(或者使用new map对象时传递进来的thr和f)进行初始化,为table申请空间
- 若table已经初始化,则将cap*2,thr*2。同时若cap已经超过了MAX_CAP,则直接将thr设置与cap相等。
- 将oldTable中的node都映射到newTable中。OldTable[i]中的node要么映射到newTable的i中,要么是i+oldcap中,由node.hash的第oldCap二进制中1所在位置j的那个位置bit值决定。
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
//cap是数组的容量,thr是数组进行扩容的临界值
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
//不是数组初始化
if (oldCap > 0) {
//当原table的size已经达到MAXIMUM_CAPACITY时,不对cap进行调整,只是将扩容临界值thr调整为与cap相同,让table空间得到100%的利用
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
//否则就将cap和thr进行*2扩容
//即使newCap<<1,它的值也始终小于Integer.MAX_VALUE,因为newCap是一个int型的整数
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
//通过初始化传递了thr但是还没有进行table的初始化操作,这时将cap设置为oldCap的值
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
//初始化没有传递任何参数,cap和thr都采用默认值
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
//如果上面没有设置newThr的值,这里统一进行处理.
//主要有两个地方,第一个else if ((newCap = oldCap << 1),第二个else if (oldThr > 0);
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
//重新设置扩容后的临界值
threshold = newThr;
//利用上面计算的newCap重新为数组申请扩容后的空间
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
//将原数组中内容转移到新数组中
if (oldTab != null) {
//原table中每个桶中的node逐个进行重新hash处理
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
//将原table中每个链表的头指针置null
oldTab[j] = null;
//如果oldTable[i]这条链表只有头指针,则将该node重新hash映射到新链表的某个位置上
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
//如果该条链表是以红黑树的形式存储的,则调用红黑树的相关操作
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
//对oldTable[i]链表中每个元素逐一处理
else { // preserve order
//lo表示原链表中的node在新链表中的映射仍在同一位置table[i]
Node<K,V> loHead = null, loTail = null;
//hi 表示原链表中的node在新链表中的映射在table[i+oldCap]。只有这两种位置
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
//i=e.hash&(oldCap-1),那么e.hash&oldCap的值为e.hash在oldCap二进制形式中那个唯一(cap的值都为2的整数次幂,所以二进制形式中只有一个1)的1所在位置的值,要么为0,要么为1.。
//因此,oldTable中的table[i]中的node,在新链表中的位置要么为i,要么为i+oldCap
//之所以直接采用(e.hash & oldCap)是为了计算简便,避免每次都用e.hash&(newCap-1)
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
//在新table中映射位置为j的,相对顺序与oldTable中的相同
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
//在新链表中映射位置为j+oldCap的
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
5)treeifyBin和untreeify方法
treeifyBin将某个hash桶的单链表转化成红黑树;untreeify将红黑树转化成单链表,在删除红黑树节点时会用到
基本步骤:
a、检查table.length是否>=64,如果不成立,则进行resize扩容,结束。
b、通过hash&(n-1)定位到table相应的bin中,检查bin中是否有Node,将单链表中的Node类型依次转化成treenode类型,并链接在一个双链表中
c、调用treeNode.treeify方法将该桶中的treeNode双链表转化成红黑树。
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
//若table的len没有达到最小树化值,则进行扩容处理
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
//若该桶中有node,则将该桶中的单链表转化成红黑树
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null, tl = null;
do {
//将每个node节点的值重新生成一个TreeNode节点
TreeNode<K,V> p = replacementTreeNode(e, null);
//hd指向红黑树的根节点
if (tl == null)
hd = p;
else {
//prev指向前一个节点,next指向后一个节点
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
//上面的while循环将单链表转化成了双链表,节点类型由node编程了treeNode,hd指向头结点
if ((tab[index] = hd) != null)
//将该双链表构建成红黑树
hd.treeify(tab);
}
}
//红黑树退化成单链表
//a、通过treeNode.next遍历红黑树,并将节点依次replacementNode成Node类型。
//b、将转化后的节点依次链接成一条单链表,返回头结点的指针
final Node<K,V> untreeify(HashMap<K,V> map) {
Node<K,V> hd = null, tl = null;
for (Node<K,V> q = this; q != null; q = q.next) {
//将红黑树中的节点treeNode依次转化成node
Node<K,V> p = map.replacementNode(q, null);
if (tl == null)
hd = p;
else
tl.next = p;
tl = p;
}
//返回单链表的头结点
return hd;
}
常用遍历方法:
https://blog.csdn.net/shenshen123jun/article/details/9074523
Hashmap 不是线程安全的,所以如果在使用迭代器的过程中有其他线程修改了 map, 则会抛出 ConcurrentModificationException,这就是所谓的 fast-fail 策略,是通过 modcount 实现,对 Hashmap 内容的修改都将增加这个值,那么在迭代器初始化时会将这个值赋给迭代器的 exceptedmodcount。在迭代过程中,判断 modcount 和 exceptedmodcount 是否相等, 若不相等,则说明其他线程修改了 map。Modcount 是 volatile 的。
二、HashTable详解
1.特点
1)线程安全。put,get,remove等方法上都有synchronized关键字保持同步。
2)不允许null键和null值,如果存入会报空指针异常。
2.数据结构
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable
3.源码解读
(1)主要属性
private transient Entry<?,?>[] table
private transient int count;//table中所有entry的个数
private int threshold;//扩容的临界值count >= threshold,threshold=loadFactor*capacity,
private float loadFactor;//加载因子threshold=loadFactor*capacity,加载因子越大,table的利用率越高
private transient int modCount = 0;//单线程,多线程操作可能会引起Hashtable fail-fast,抛出ConcurrentModificationException异常 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;//table的最大size //三种遍历方式
private transient volatile Set<K> keySet;
private transient volatile Set<Map.Entry<K,V>> entrySet;
private transient volatile Collection<V> values;
(2)构造方法
//传递一个table的初始容量和加载因子,table的初始化在构造函数中进行(与HashMap不同,hashmap在put第一个node中进行)。
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;
//设置loadFactor,threshold的值,为table申请空间
this.loadFactor = loadFactor;
//与HashMap不同,table的size可以随意指定,不用为2的整数次幂,甚至都不用超过11(默认的初始容量)
table = new Entry<?,?>[initialCapacity];
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}
//默认的loadFactor=0.75
public Hashtable(int initialCapacity) {
this(initialCapacity, 0.75f);
}
//默认的capacity=11
public Hashtable() {
this(11, 0.75f);
}
//2*t.size()保证不用插进去就需要扩容
public Hashtable(Map<? extends K, ? extends V> t) {
this(Math.max(2*t.size(), 11), 0.75f);
putAll(t);
}
(3)不同方法
1)rehash:扩容,不是同步的
思路:
将capatity*2,根据loadfactor重新设置threshold
依次逆序遍历oldMap中所有Bin中的节点,重新映射到newMap的bin中。这里是从链表头插入,所以顺序与OldMap中的相反。hashMap是插入链表尾部,顺序相同。
与hashMap的resize不同点:
(1)hashMap的resize中可能对table进行初始化,这里初始化在构造函数中进行
(2)扩容方式:hashmap中 newCap=oldCap*2,这样保证新容量依然是2的整数次幂;hashTable中,newCap=oldCap*2+1
(3)映射方式:hashMap中oldMap中同一个bin中node只可能映射到i和i+oldCap两个桶中;hashTable中由于不要求cap是2的整数次幂,所以oldCap中同一个bin中可能映射到newMap的各个Bin中
(4)转移数据方式:hashMap中oldMap和newMap中node在链表中相对顺序不变,是在链表尾部插入的;hashTable中顺序逆向了,在链表首部插入的。
(5)效率:hashMap效率更高,采用&操作映射;HashTable效率低,采用%
(6)桶的结构:hashMap中当节点数大于8时,单链表会转化成红黑树;HashTable始终以单链表存储
protected void rehash() {
int oldCapacity = table.length;
Entry<?,?>[] oldMap = table; // 扩容方法:新容量=就容量*2+1
int newCapacity = (oldCapacity << 1) + 1;
if (newCapacity - MAX_ARRAY_SIZE > 0) {
if (oldCapacity == MAX_ARRAY_SIZE)
// Keep running with MAX_ARRAY_SIZE buckets
return;
newCapacity = MAX_ARRAY_SIZE;
}
Entry<?,?>[] newMap = new Entry<?,?>[newCapacity]; modCount++;
//确定新的临界值threshold
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
table = newMap;
//逆序遍历oldMap的桶中的所有节点,并依次映射到newMap中的桶
for (int i = oldCapacity ; i-- > 0 ;) {
for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
Entry<K,V> e = old;
old = old.next;
//oldMap映射到newMap的bin的方法:直接取余
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
//每次都从newMap的链表的头部插入
e.next = (Entry<K,V>)newMap[index];
newMap[index] = e;
}
}
}
三、TreeMap详解
1.特点
1)有序
2)非线程安全
3)时间复杂度是o(lgn)
2.数据结构
public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable
四、LinkedHashMap详解
1.特点
1)保证插入顺序或访问顺序有序。
2)key和value都允许为空。
3)非线程安全。
4)LinkedHashMap=LinkedList+HashMap,多态实现。
https://blog.csdn.net/justloveyou_/article/details/71713781
注: HashSet子类依靠hashCode()和equal()方法来区分重复元素。
HashSet内部使用Map保存数据,即将HashSet的数据作为Map的key值保存,这也是HashSet中元素不能重复的原因。而Map中保存key值的,会去判断当前Map中是否含有该Key对象,内部是先通过key的hashCode,确定有相同的hashCode之后,再通过equals方法判断是否相同。
集合类---Map的更多相关文章
- 集合类 Map接口 HashTable
集合类的另外一种重要实现为Map接口,Map接口提供的方法如下: Map接口一个不常见实现为HashTable,HashTable对所有有并发访问问题的方法通过 synchronized 关键字进行并 ...
- 集合类——Map集合、Properties属性文件操作
1.Map集合 Collection集合的特点是每次进行单个对象的保存,若要对一对对象来进行保存就只能用Map集合来保存.即Map集合中一次可以保存两个对象,且这两个对象的关系是key = value ...
- JavaSE高级之集合类
下面的内容是对java中的集合类进行的总结,过段时间会分享java的网路编程,多线程等内容,欢迎批评指正. 1.Java中的集合是用来存放对象的,即集合是对象的集合,对象是集合的元素,java AP ...
- Set,List,Map,Vector,ArrayList的区别(转)
JAVA的容器---List,Map,Set Collection ├List │├LinkedList │├ArrayList │└Vector │ └Stack └Set Map ├Hashtab ...
- Go语言学习笔记十三: Map集合
Go语言学习笔记十三: Map集合 Map在每种语言中基本都有,Java中是属于集合类Map,其包括HashMap, TreeMap等.而Python语言直接就属于一种类型,写法上比Java还简单. ...
- java如何从方法返回多个值
本文介绍三个方法,使java方法返回多个值. 方法1:使用集合类 方法2:使用封装对象 方法3:使用引用传递 示例代码如下: import java.util.HashMap; import java ...
- tomcat websocket 实现网页在线即时聊天
背景介绍 近一个月完成了公司的一个项目,负责即时聊天部分 寻找了一下,决定使用websocket,要问原因的话,因为tomcat 自带相关消息收发的API,用起来方便 闲话少叙,进入实现步骤 使用工具 ...
- JavaOOP-集合框架
1.Java集合框架包含的内容 Java集合框架为我们提供了一套性能优良,使用方便的接口和类,它们都位于在java.util包中. Collection 接口存储一组不唯一,无序的对象. List 接 ...
- java 容器 集合 用法
Set,List,Map,Vector,ArrayList的区别 JAVA的容器---List,Map,Set Collection ├List │├LinkedList │├ArrayList │└ ...
随机推荐
- winform全局异常捕获
/// <summary> /// 应用程序的主入口点. /// </summary> public static ApplicationContext context; [S ...
- Python爬取B站视频信息
该文内容已失效,现已实现scrapy+scrapy-splash来爬取该网站视频及用户信息,由于B站的反爬封IP,以及网上的免费代理IP绝大部分失效,无法实现一个可靠的IP代理池,免费代理网站又是各种 ...
- 【刷题】洛谷 P3804 【模板】后缀自动机
题目描述 给定一个只包含小写字母的字符串 \(S\) , 请你求出 \(S\) 的所有出现次数不为 \(1\) 的子串的出现次数乘上该子串长度的最大值. 输入输出格式 输入格式: 一行一个仅包含小写字 ...
- bzoj 3217: ALOEXT
将此神题作为博客园的第一篇文章,至此,数据结构基本学完了(或者说数据结构轮流虐了我一次!) 人生第一道7K代码题! 没什么,就是treap套个trie,然后tle是因为一定要用指针当时p党谁会用那么丑 ...
- BZOJ2427:[HAOI2010]软件安装——题解
https://www.lydsy.com/JudgeOnline/problem.php?id=2427 https://www.luogu.org/problemnew/show/P2515 现在 ...
- TCP中三次握手建立和四次握手释放以及相关问题
本文基于个人所学和网上博文所整理,若有不妥处,欢迎留言指出 TCP连接过程中标志位的意义: 字符缩写 描述 SYN 同步序号,表示此报文是一个连接请求或连接接受报文 ACK 确认位,对接收到的报文的确 ...
- php ul li 分类
<?phpfunction do_tree($arr,$pid){ echo "<ul>"; foreach ($arr as $key => $value ...
- 利用caffe的solverstate断点训练
你可以从系统 /tmp 文件夹获取,名字是什么 caffe.ubuntu.username.log.INFO.....之类 ====================================== ...
- swift4.0闭包
http://blog.csdn.net/bddzzw/article/details/78276054
- HDU 4576 DP
Robot Time Limit: 8000/4000 MS (Java/Others) Memory Limit: 102400/102400 K (Java/Others)Total Sub ...