1.Map,用于保存K-V(双列元素)
2.Map中的Key Value可以是任意引用分类型的数据,会封装到HashMap的Node对象中
3.Map的key不允许重复。原因和HashSet一样。
4.Map中的value可以重复
5.Map的key可以为null,value也可以为null。
6.常用String类作为Map的key
7.key value一对一,通过指定的key可以得到value
8.Map存放数据的key-value示意图,一对k-v是放在HashMap$Node中的,因为Node实现了Entry接口,所以一对k-v就是一个Entry
就是说:
为了方便管理,遍历数据
把node封装成一个entry,再把entry放在entryset集合中,因为entry中有方法适合遍历
从entryset中取出数据,是node(因为node实现了entry),我们做个转型,把node型转化
为entry型。再调用方法
关于entryset总结
1. .k-v最后是存放在:
HashMap$Node node =newNode(hash, key, value, null);
2.k-v为了方便程序员的遍历,还会创建EntrySet集合,这个集合存放的数据元素类型是Entry,Entry对象就有k,v EntrySet
3.
EntrySet中,定义的类型是Entry,但是实际上还是存放的Node。因为Node实现了Map.Entry
4.把node对象存放在entrySet中,为了方便我们遍历,因为Map.entry提供了重要的方法 k getkey() v getvalue();
Map map= new HashMap<>();
map.put("no1","汉顺");
map.put("no2","顺");
map.put("no1","张三");//替换old值
map.put("no3","张三");
//.k-v为了方便程序员的遍历,还会创建EntrySet集合,
// 这个集合存放的数据元素类型是Entry,Entry对象就有k,v
// EntrySet>
Set set = map.entrySet();
System.out.println(set.getClass());//HashMap$EntrySet
//获得EntrySet中的元素
//虽然定义的类型是Entry,但是实际上还是存放的Node
//因为Node实现了Map.Entry
for(Object obj : set)
{
System.out.println(obj.getClass());//HashMap$Node
//为了从HashMap$Node中取出k-v
//先做转型
//先转化为entry类型,这里面有方法取得k-v
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey()+"-"+entry.getValue());
}
Map遍历方式
1.containsKey:查找键是不是存在
2.keySet:获得所有的键
3.entrySet:获得所有的关系k-v
4.values:获取所有的值
public class MapFor {
public static void main(String[] args) {
Map map = new HashMap();
map.put("邓超","孙丽");
map.put("王宝强","马蓉");
map.put("宋喆","马蓉");
map.put("孙胜顺","刘秋丽");
map.put(null,"刘亦菲");
map.put(null,"刘令");
map.put("鹿晗","关晓彤");
//第一组
//先取出所有的key
Set keyset = map.keySet();
//(1).增强for
System.out.println("----第一种方式--");
for(Object key: keyset){
System.out.println(key+"-"+map.get(key));
}
//(2)迭代器
Iterator iterator = keyset.iterator();
System.out.println("----第一种方式迭代器--");
while (iterator.hasNext()) {
Object key = iterator.next();
System.out.println(key+"-"+map.get(key));
}
//第二组
//把所有的values取出
Collection values = map.values();
//这里可以使用所有
//(1)增强for
System.out.println("----取出所有的values,增强for---");
for(Object value:values){
System.out.println(value);
}
//(2)迭代器
System.out.println("----取出所有的values,迭代器---");
Iterator iterator1 = values.iterator();
while (iterator1.hasNext()) {
Object value = iterator1.next();
System.out.println(value);
}
//第三组
//通过EntrySet
Set set = map.entrySet();
System.out.println("------entryset的增强for---");
//(1)增强for
for(Object obj : set){
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey()+"-"+entry.getValue());
}
//(2)迭代器
Iterator iterator2 = set.iterator();
System.out.println("------entryset的增强迭代器---");
while (iterator2.hasNext()) {
Object next = iterator2.next();//取出的是Node,Node实现了Map.entry
Map.Entry entry = (Map.Entry)next;
System.out.println(entry.getKey()+"-"+entry.getValue());
}
}
}
HashMap小结
小结
1.HashMap底层维护了Node类型的数组table,默认为null。
2.当创建对象时,将加载因子(loadfactor)初始化为0.75.
3.当添加key-value时,通过key的哈希值得到在table的索引。
然后判断该索引处是否有元素,如果没有元素直接添加。如果该索引处有元素,继续判断该元素的key和准备加入的ke y是不是相等。如果相等,则直接替换value;如果不相等需要判断是树结构还是链表结构,做出相应处理。如果添加时发现容量不够,则需要扩容。
4.第一次添加,扩容的容量为16,临界值为12
5.以后再扩容,则需要扩容table容量为原来的2倍,临界值为原来的2倍,即24,依次类推.
6.在Java8中,,如果一条链表的元素个数超过TREEIFY THRESHOLD(默认是8),并且table的大小 >= MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)
HashMap
1.执行构造器 new HashMap()
初始化加载因子,loadfactor = 0.75
HashMap$Node[] table = null;
2.执行put方法
会调用hash(key)计算key的哈希值
k=“java” value =10
hash(key) :得到哈希值
算法如下:
3.执行putVal方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node[] tab; Node p; int n, i;
//第一次put执行到这里的时候,table==null
if ((tab = table) == null || (n = tab.length) == 0)
//table为null,进入resize()方法,给table申请16个空间大小
n=16
n = (tab = resize()).length;
//根据hash值,计算索引位置,如果这个位置没有元素,直接挂上去
//(第二次执行put方法的时候,table不为null,就直接进入这里)(不超过12次都是直接进入这里)
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.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;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
//看table中元素的个数是否到达12,如果到达就扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
执行resize()方法的时候,
newCap=16 newThr=12;
Node[] newTab = new Node[16];
return newTable
自己总结:
执行构造方法,获得加载因子0.75
进入put方法,里面有pubVal方法,putval方法的第一个参数是一个hash(key)方法
进去发现,根据key得到hash值。
(第一次put的时候会进行下面两步,从第二次开始只要不超过12次,直接跳过)
进去putval,判断table==null就进入resize方法扩容。
进去resize,定义了两个变量A=16 B=12, new了一个含有16个空间大小的数组newNode,返回这个数组,所以table=16个空间大小了
再在putval方法里,根据hash值,计算索引位置,发现索引位置上没有元素,直接插入。
(如果索引上有元素,进入下面三种情况)
判断 table中元素的大小是不是>12,如果大于,就进入resize方法扩容
else {
Node e; K k;
//put("java",20)的时候
//满足这个条件
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//如果是个红黑树了,就按照红黑树的方式处理
else if (p instanceof TreeNode)
e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);
//如果找到的位置,后面是链表,就循环比较
else {
for (int binCount = 0; ; ++binCount) {
//如果循环到了最后一个元素
if ((e = p.next) == null) {
//直接插入到最后一个位置
p.next = newNode(hash, key, value, null);
//如果到了8个,就变成红黑树
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;
p = e;
}
}
//满足
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
//直接替换
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)