当前位置: 首页 > news >正文

集合--Map、HashMap

4.1 Map接口和常用方法

Map接口实现类的特点:

注意:这里讲的是JDK8的Map接口特点

  1. Map与Collection并列存在。用于保存具有映射关系的数据:Key-Value(双列数据)

    Map map = new HashMap();
    map.put("no1","韩顺平"); //k-v
    map.put("no1","张三丰"); //当有相同的k,就等价于替换
    
  2. Map 中的 key 和 value 可以是任何引用类型的数据,会封装 HashMap$Node 对象中

  3. Map 中的 key 不允许重复,原因和 HashSet 一样

  4. Map 中的 value 可以重复

  5. Map 的key 可以为 null,value 也可以为null,注意 key 为null,只能有一个,value 为null,可以多个

  6. 常用String类作为 Map 的 key

  7. key 和value 之间存在单向一对一关系,即通过指定的 key 总能找到对应

    //通过get方法,传入 key,会返回对应的value
    System.out.println(map.get("no1"));
    
  8. Map存放数据的 key-value 示意图,一对 k-v 是放在一个 HashMap$Node (实际存放)中的,又因为 Node 实现了 Entry 接口,有些书上也说 k-v 就是一个 Entry (为了遍历方便)

    Map map = new HashMap();
    map.put("no1","韩顺平"); //k-v
    map.put("no2","张三丰"); //k-v//1.k-v 最后是 HashMap$newNode(hash,key,value,null)
    //2.k-v为了方便程序员的遍历,还会创建 EntrySet 集合,该集合存放的元素类型是 Entry,而一个 Entry对象就有k,v EntrySet<Entry<K,V>>,即: transient Set<Map.Entry<K,V>> entrySet
    //3.entrySet 中,定义的类型是 Map.Entry,但实际上存放的还是 HashMap$Node
    //  这是因为 static class Node<K,V> implements Map.Entry<K,V>
    //4.当把 HashMap$Node 对象 存放到 entrySet 就方便我们的遍历,因为 Map.Entry 提供了重要方法
    //  K getKey(); V getValue();Set set = map.entrySet();
    System.out.println(set.getClass());// HashMap$EntrySet
    for(Object obj : set){//System.out.println(obj.getClass());//为了从 HashMap$Node 取出k-v//1.先做一个向下转型Map.Entry entry = (Map.Entry) obj;System.out.println(entry.getKey() + "-" + entry.getValue() );
    }

Map接口常用方法:

  • put:添加

  • remove:根据键删除映射关系

  • get:根据键获取值

  • size:获取元素个数

    Map map = new HashMap();
    map.size();
    
  • isEmpty:判断个数是否为0

  • clear:清除

  • containsKey:查找键是否存在

4.2 Map接口遍历元素方式

  • containsKey:查找键是否存在

  • keySet:获取所有的键

    Map map = new HashMap();//先取出所有的 key ,再通过 key 取出对应的 value
    Set keyset = map.keySet(); //(1)第一种方式:增强 for
    for(Object key : ketset){System.out.println(key + "-" + map.get(key));
    }//(2)第二种方式:迭代器
    Iterator iterator = keyset.iterator();
    while(iterator.hasNext()){Object key = iterator.next();System.out.println(key);
    }
    
  • entrySet:获取所有的关系

    Map map = new HashMap();//通过 EntrySet 来获取 k-v
    Set entrySet = map.entrySet();// EntrySet<Map.Entry<K,V>>//(1)增强for
    for (Object entry : entrySet) {//将entry 转成 Map.EntryMap.Entry m = (Map.Entry) entry;System.out.println(m.getKey() + m.getValue());//首先通过map中的entrySet方法将K-V封装成Entry,使用增强for循环遍历,需要将接收的entrySet向下转型为Map.Entry(因为Map.entry能够使用get方法获取K-v)}//(2)迭代器
    Iterator iterator3 = entrySet.iterator();
    while (iterator3.hasNext()){Object entry= iterator3.next();//System.out.println(next.getctass()); //HashHap$Node -实现-> Map.Entry (getKev,getvalue)//向下转型 Map.Entry(Node没有对应方法)Map.Entry m = (Map.Entry) entry;System.out.println(m.getKey() + "-" + m.getValue());/*编译阶段:编译器检查 entry 的编译时类型 Map.Entry。它发现 Map.Entry 接口里声明了 getKey() 方法,所以语法正确,允许通过。运行阶段:JVM 找到 entry 变量实际指向的堆内存中的 HashMap$Node 对象,然后调用这个对象所属类(HashMap.Node)的 getKey() 方法。*/}
    
  • values:获取所有的值

    Map map = new HashMap();//把所有的 values 取出来
    Collection values = map.values();//这里可以使用所有Collection使用的遍历方式//(1)增强for
    for (Object value : values) {System.out.println(value);
    }//(2)选代器
    Iterator iterator2 = values.iterator();
    while (iterator2.hasNext()) {Object value = iterator2.next();System.out.println(value);
    }
    

练习:

使用HashMap添加3个员工对象,要求
键:员工id
值:员工对象
并遍历显示工资>18000的员工(遍历方式最少两种)
员工类:姓名、工资、员工id

public static MapExercise{public static void main(String[] args){//完成代码Map hashMap = new HashMap(); hashMap.put(1, new Emp("jack", 300000, 1));hashMap.put(2, new Emp("tom", 2300, 2));hashMap.put(3, new Emp("john", 5000, 3));//遍历//并遍历显示工资>18000的员工(遍历方式最少两种)//1.使用keySet -> 增强forSet keySet = hashMap.keySet();for (Object key : keySet) {//先获取valueEmp emp =(Emp) hashMap.get(key);if(emp.getSal() > 18000){System.out.println(emp);}}//2.使用EntrySet -> 迭代器Set entrySet = hashMap.entrySet();Iterator iterator = entrySet.iterator();while (iterator.hasNext()){Map.Entry entry = (Map.Entry)iterator.next();//通过entry 取得key 和 valueEmp emp = (Emp) entry.getValue();if(emp.getSal() > 18000){System.out.println(emp);     }}
}class Emp{private String name;private double sal;private int id;public Emp(String name, double sal, int id){this.name = name;this.sal = sal;this.id = id;}//get/set,toString省略
}

4.3 HashMap小结

  1. Map接口的常用实现类:HashMap、 Hashtable 和 Properties。
  2. HashMap是Map 接口使用频率最高的实现类。
  3. HashMap 是以 key-val 对的方式来存储数据 ( HashMap$Node 类型 )
  4. key 不能重复,但是值可以重复,允许使用 null 键和 null 值。
  5. 如果添加相同的key,则会覆盖原来的key-val,等同于修改.(key不会替换,val会替换)
  6. 与HashSet一样,不保证映射的顺序,因为底层是以 hash 表的方式来存储的(jdk 8 的HashMap 底层 数组+链表+红黑树)
  7. HashMap没有实现同步,因此是线程不安全的,方法没有做同步互斥的操作,没有 synchronized

4.4 HashMap底层结构和源码分析

HashMap 底层机制:

  1. (k.v)是一个Node 实现了 Map.Entry<K,V>,查看HashMap 的源码可以看到.
  2. jdk 7.0 的 hashmap 底层实现[数组+链表],jdk8.0底层[数组+链表+红黑树]

HashMap 扩容机制:(和HashSet相同)

  1. HashMap底层维护了Node类型的数组 table ,默认为null
  2. 当创建对象时,将加载因子 ( loadfactor ) 初始化为 0.75
  3. 当添加 key - val 时,通过 key 的哈希值得到在table的索引。然后判断该索引处是否有元素,如果没有元素直接添加。如果该索引处有元素,继续判断该元素的 key 是否和准备加入的 key 相等:如果相等,则直接替换 val ;如果不相等,需要判断是树结构还是链表结构,做出相应处理。如果添加时发现容量不够,则需要扩容。
  4. 第 1 次添加,则需要扩容table容量为 16,临界值(threshold)为 12
  5. 以后再扩容,则需要扩容table容量为原来的 2 倍,临界值为原来的 2 倍,即24,依次类推
  6. Java 8 中,如果一条链表的元素个数超过 TREEIFY _ THRESHOLD ( 默认是8 ),并且 table 的大小 >= MIN _ TREEIFY _ CAPACITY ( 默认64 ),就会进行树化 ( 红黑树 )

HashMap 源码解读:

Map hashMap = new HashMap(); 
map.put("java",10);
map.put("php",10);
map.put("java",20);/*解读 + 图解:
1.执行构造器初始化加载因子 loadfactor = 0.75HashMap$Node[] table = null;
2.执行 put方法	调用 hash 方法,计算 key 的 hash值(h = key.hashCode()) ^ (h >>> 16)public V put(K key, V value){ //k = "java" v = 10return putVal(hash(key), key, value, false, true);}
3.执行 putValfinal 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,或者 length = 0,就扩容到16if((tab = table) == null || (n = tab.length) == 0)n =(tab = resize()).length;//取出 hash 值对应的 table 表的索引位置的 Node,如果为 null,就直接把加入的 k-v,创建成一个 Node ,加入到该位置即可if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);else {Node<K,V> e;K k;////如果table 的索引位置的key 的hash相同和新的key的hash值相同//并 满足(table现有的结点的key和准备添加的key是同一个对象 || equals 返回真)//就认为不能加入新的k-vif (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))e=p;else if (p instanceof TreeNode) //如果当前的table的已有的Node 是红黑树,就按照红黑树的方式处理e=((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);else {//如果找到的结点,后面是链表,就循环比较for (int binCount = θ; ; ++binCount) {//死循环if ((e = p.next)== null) { //如果整个链表,没有和它相同,就加到该链表的最后p.next = newNode(hash,key, value, null);//加入后,判断当前链表的个数,是否已经到8个,到8个后//就调用 treeifyBin 方法进行红黑树的转换if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);break;}if(e.hash == hash && //如果在循环比较过程中,发现有相同,就break,只是替换value((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}  }if (e != null) { // existing mapping for keyV oldValue = e.value;if (!onlyIfAbsent || oLdValue == null)e.value= value; //替换,key对应的valueafterNodeAccess(e);return oldValue;           }++modCount; //每增加一个Node,就size++if (++size > threshold) //如果size > 临界值,就扩容resize();afterNodeInsertion(evict);return null;}5.关于树化
//如果table 为null,或者大小还没有到64,暂时不树化,而是进行扩容
//否则才会真正的树化 -> 剪枝final void treeifyBin(Node<K,V>[] tab, int hash) {int n, index; Node<K,V> e;if (tab == null || (n = tab.length) <  MIN_TREEIFY_CAPACITY)resize();}*/

HashMap 扩容树化触发:

public class HashMapSource{public static void main(String[] args){HashMap hashMap = new HashMap();for(int i=1; i<=12;1++){hashMap.put(new A(i),"hello");}System.out.println(hashMap);  //12个k-v}
}class A{private int num;public A(int num){this.num = num;}//所有的A对象的hashCode都是100,hash值相同代表挂在同一个位置@overridepublic int hashCode() {return 100;}
}//循环到9时,table扩容但不树化(单条链表到达8,但是table大小没到64)
//大小到64后树化

4.5 Hashtable底层结构和源码分析

Hashtable基本介绍:

  1. 存放的元素是键值对:即 K - V
  2. Hashtable 的键和值都不能为 null,否则会抛出 NullPointerException
  3. Hashtable 使用方法基本上和 HashMap 一样
  4. Hashtable 是线程安全的,HashMap 是线程不安全的

HashTable底层结构:

Hashtable table = new Hashtable();
table.put("john",100);
table.put(null, 100);//异常
table.put("john", null);//异常 NuLLPointerException 
table.put("Lucy",100);//ok
table.put("uic",100);
table.put("uic",88);//替换//1.底层有数组 Hashtable$Entry[] 初始化大小为 11
//2.临界值 threshold = 数组大小 * 0.75
//3.扩容:按自己的机制

HashTable扩容机制:

/*
4.执行方法 addEntry(hash, key value, index);添加K-V封装到Entry
5.当 if(count >= threshold) 满足时就进行扩容
6.按照 int newCapacity = (oldCapacity << 1) + 1 的大小扩容,即 原大小* 2 + 1
*/

HashTable 和 HashMap 对比:

版本 线程安全(同步) 效率 允许 null键 null值
HashMap 1.2 不安全 可以
Hashtable 1.0 安全 较低 不可以
http://www.sczhlp.com/news/56543/

相关文章:

  • 在线药店网站建设wordpress 首页标题
  • 宁波网站建设公司网站建设中html下载
  • 自适应网站做mip改造桂林整站优化
  • 自己做网站难吗wordpress响应式菜单
  • 做调查问卷赚钱的网站初级网络推广培训
  • 阿里巴巴公共DoH配置
  • 精进方向
  • 杭州网站建设的公司wordpress 简书风格
  • 北京优化网站网站图标素材
  • 安庆做网站电话wordpress七牛云存储
  • 建成局网站建设美食网站开发环境
  • 优秀网站有哪些seo网站推广实例
  • 做视频网站要什么软件下载WordPress任务悬赏插件
  • 怎样管理一个俄语网站网站维护有多长时间
  • Why框架,是怎么形成的
  • 网站建设工作总结6win10 电脑做网站服务器吗
  • 阜平网站建设wordpress 图片名乱码
  • 精利手表网站cms wordpress 企业
  • 旅游景点网站模板wordpress怎么让手机端好看
  • 网站怎么做301重定向东莞微网站
  • 去哪找做塑料的网站网络运行维护
  • 网站建设选择题题库延边住房和城乡建设局网站
  • 学校网站html模板做全景哪个网站不模糊
  • 如何组织公司做网站ppt汇精美ppt模板免费下载网站
  • 微调数据集
  • 9 月做题记录
  • Day10Scanner的进阶认识与使用
  • 英国软件开发职业指南:顶尖城市、技能需求与薪资解析
  • 非常神秘的题,使我的大脑旋转两年半
  • 网上写作文的网站点击器原理