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

java基础-13 : 双列集合(Map)

一.双列集合的体系

二.双列集合的通用方法

1.put:添加/覆盖
添加数据时:

  • 如果键不存在,则添加键值对,返回null
  • 如果键存在,则覆盖原有的值,返回被覆盖的值

2.remove:删除

  • 如果键存在,删除指定的键值对,返回被删除的值
  • 如果键不存在,则返回null

3.get:获取
如果键存在,根据键,返回对应的值
如果键不存在,则返回null

4.containsKey:判断是否包含键
判断集合是否包含指定的键
如果包含,则返回true
如果不包含,则返回false

package 双列集合;import java.util.HashMap;
import java.util.Map;public class test {public static void main(String[] args) {System.out.println("====put:添加/覆盖====");Map<String, String> map = new HashMap<>();map.put("1", "1");map.put("2", "2");map.put("3", "3");String value = map.put("1","2");System.out.println(value);System.out.println(map);System.out.println("====remove:删除====");String result = map.remove("1");System.out.println(result);System.out.println(map);System.out.println("====get:获取====");String value1 = map.get("2");System.out.println(value1);System.out.println("====containsKey:判断是否包含键====");boolean valueresult = map.containsKey("2");System.out.println(valueresult);}
}
====put:添加/覆盖====
1
{1=2, 2=2, 3=3}
====remove:删除====
2
{2=2, 3=3}
====get:获取====
2
====containsKey:判断是否包含键====
true

三.遍历

1.map.keySet()

package 双列集合;import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;public class 遍历 {public static void main(String[] args) {Map<String, String> map = new HashMap<>();map.put("一", "1");map.put("二", "2");map.put("三", "3");Set<String> keys = map.keySet();
//        增强forSystem.out.println("====1.增强for遍历====");for (String key : keys) {String value = map.get(key);System.out.println(key + "=" + value);}
//        迭代器System.out.println("====2.迭代器遍历====");Iterator<String> it = keys.iterator();while (it.hasNext()) {String key = it.next();String value = map.get(key);System.out.println(key + "=" + value);}
//        lambdaSystem.out.println("====3.lambda遍历====");keys.forEach(k -> {String v = map.get(k);System.out.println(k + "=" + v);});}
}
====1.增强for遍历====
一=1
三=3
二=2
====2.迭代器遍历====
一=1
三=3
二=2
====3.lambda遍历====
一=1
三=3
二=2

2.map.entrySet()

package 双列集合;import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;public class 遍历2 {public static void main(String[] args) {Map<String,String> map = new HashMap<>();map.put("一", "1");map.put("二", "2");map.put("三", "3");
//      通过entrySet方法获取所有的键值对对象,返回一个Set对象Set<Map.Entry<String, String>> entrySet = map.entrySet();
//        增强for遍历System.out.println("1.增强for遍历:");for (Map.Entry<String, String> entry : entrySet) {String key = entry.getKey();String value = entry.getValue();System.out.println(key + "=" + value);}
//        迭代器System.out.println("2.迭代器遍历:");Iterator<Map.Entry<String, String>> it = entrySet.iterator();while(it.hasNext()){Map.Entry<String, String> entry = it.next();String key = entry.getKey();String value = entry.getValue();System.out.println(key + "=" + value);}
//        foeEach遍历System.out.println("3.foeEach遍历:");entrySet.forEach(entry -> {String key = entry.getKey();String value = entry.getValue();System.out.println(key + "=" + value);});}
}
1.增强for遍历:
一=1
三=3
二=2
2.迭代器遍历:
一=1
三=3
二=2
3.foeEach遍历:
一=1
三=3
二=2

3.forEach(new BiConsumer)

package 双列集合;import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;public class 遍历3 {public static void main(String[] args) {Map<String,String> map = new HashMap<>();map.put("一","1");map.put("二","2");map.put("三","3");map.forEach(new BiConsumer<String, String>() {@Overridepublic void accept(String Key, String Value) {System.out.println(Key + "=" + Value);}});map.forEach((k,v)->{System.out.println(k + "=" + v);});}
}
一=1
三=3
二=2
一=1
三=3
二=2

三.HashMap

与hashlist一样,在jdk8以后使用数组+链表+红黑树存储数据,当链表长度超过8&数组长度超过64时,会转换为红黑树存储,只有当键为自定义对象时,需要重写hashcode和equals方法

package 双列集合;import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;public class hashmaptest {public static void main(String[] args) {Map<Student,String> map = new HashMap<>();map.put(new Student("张三",18),"北京");map.put(new Student("李四",19),"上海");map.put(new Student("王五",20),"广州");map.put(new Student("赵六",21),"深圳");System.out.println(map);Set<Map.Entry<Student,String>> entrySet = map.entrySet();Iterator<Map.Entry<Student,String>> iterator = entrySet.iterator();while(iterator.hasNext()){Map.Entry<Student,String> entry = iterator.next();System.out.println(entry.getKey() + "=" + entry.getValue());}}
}
package 双列集合;import java.util.Objects;public class Student {private String name;private int age;public Student() {}public Student(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Student student = (Student) o;return age == student.age && Objects.equals(name, student.name);}@Overridepublic int hashCode() {return Objects.hash(name, age);}public String toString() {return "Student{name = " + name + ", age = " + age + "}";}
}
{Student{name = 赵六, age = 21}=深圳, Student{name = 张三, age = 18}=北京, Student{name = 王五, age = 20}=广州, Student{name = 李四, age = 19}=上海}
Student{name = 赵六, age = 21}=深圳
Student{name = 张三, age = 18}=北京
Student{name = 王五, age = 20}=广州
Student{name = 李四, age = 19}=上海

1.练习
某班存在80名同学,轮流对ABCD进行投票,选出投票数最多的人

package 双列集合;import java.util.*;public class hashmaptest2 {public static void main(String[] args) {Random random = new Random();String[] name = {"A", "B", "C", "D"};Map<String,Integer> map = new HashMap<>();ArrayList<String> list = new ArrayList<>();for (int i = 0; i < 80; i++) {int index = random.nextInt(4);list.add(name[index]);}System.out.println(list);for (String n : list) {if(map.containsKey(n)){map.put(n,map.get(n) + 1);}else{map.put(n,1);}}System.out.println(map);int max = 0;Set<Map.Entry<String,Integer>> entrySet = map.entrySet();for (Map.Entry<String, Integer> entry : entrySet) {if(entry.getValue() > max){max = entry.getValue();}}System.out.println("投票数最多的人是:" + max);for (Map.Entry<String, Integer> entry : entrySet) {if(entry.getValue() == max){System.out.println(entry.getKey());}}}
}

四.TreeMap

TreeMap 提供了有序的键值对存储,通过实现 Comparable 接口或提供 Comparator 来定义排序规则

package 双列集合;import java.util.TreeMap;public class Tree_test {public static void main(String[] args) {TreeMap<Student_TreeMap,String> map = new TreeMap<>();map.put(new Student_TreeMap("张三",18),"111");map.put(new Student_TreeMap("李四",19),"222");map.put(new Student_TreeMap("王五",20),"333");System.out.println(map);}
}
package 双列集合;public class Student_TreeMap implements Comparable<Student_TreeMap>{private String name;private int age;public Student_TreeMap() {}public Student_TreeMap(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String toString() {return "Student_TreeMap{name = " + name + ", age = " + age + "}";}@Overridepublic int compareTo(Student_TreeMap o) {int i = this.age - o.age;if(i == 0){i = this.name.compareTo(o.name);}return i;}
}
{Student_TreeMap{name = 张三, age = 18}=111, Student_TreeMap{name = 李四, age = 19}=222, Student_TreeMap{name = 王五, age = 20}=333}

1.练习

package 双列集合;import java.util.StringJoiner;
import java.util.TreeMap;
import java.util.function.BiConsumer;public class Tree_test2 {public static void main(String[] args) {String str = "absaaabbbcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcde";TreeMap<Character,Integer> map = new TreeMap<Character,Integer>();for (int i = 0; i < str.length(); i++) {char c = str.charAt(i);if (map.containsKey(c)){map.put(c,map.get(c)+1);}else{map.put(c,1);}}System.out.println(map);StringBuilder sb = new StringBuilder();map.forEach(new BiConsumer<Character, Integer>() {@Overridepublic void accept(Character character, Integer integer) {sb.append(character).append("(").append(integer).append(")");}});System.out.println(sb);StringJoiner sj = new StringJoiner("", "", "");map.forEach((k,v)->{sj.add(k + "(" + v + ")");});System.out.println(sj);}
}
{a=12, b=12, c=9, d=9, e=9, s=1}
a(12)b(12)c(9)d(9)e(9)s(1)
a(12)b(12)c(9)d(9)e(9)s(1)

五.源码分析

1.HashMap源码解读

1.看源码之前需要了解的一些内容Node<K,V>[] table   哈希表结构中数组的名字DEFAULT_INITIAL_CAPACITY:   数组默认长度16DEFAULT_LOAD_FACTOR:        默认加载因子0.75HashMap里面每一个对象包含以下内容:
1.1 链表中的键值对对象包含:  int hash;         //键的哈希值final K key;      //键V value;          //值Node<K,V> next;   //下一个节点的地址值1.2 红黑树中的键值对对象包含:int hash;         		//键的哈希值final K key;      		//键V value;         	 	//值TreeNode<K,V> parent;  	//父节点的地址值TreeNode<K,V> left;		//左子节点的地址值TreeNode<K,V> right;	//右子节点的地址值boolean red;			//节点的颜色2.添加元素
HashMap<String,Integer> hm = new HashMap<>();
hm.put("aaa" , 111);
hm.put("bbb" , 222);
hm.put("ccc" , 333);
hm.put("ddd" , 444);
hm.put("eee" , 555);添加元素的时候至少考虑三种情况:
2.1数组位置为null
2.2数组位置不为null,键不重复,挂在下面形成链表或者红黑树
2.3数组位置不为null,键重复,元素覆盖//参数一:键
//参数二:值//返回值:被覆盖元素的值,如果没有覆盖,返回null
public V put(K key, V value) {return putVal(hash(key), key, value, false, true);
}//利用键计算出对应的哈希值,再把哈希值进行一些额外的处理
//简单理解:返回值就是返回键的哈希值
static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}//参数一:键的哈希值
//参数二:键
//参数三:值
//参数四:如果键重复了是否保留
//		   true,表示老元素的值保留,不会覆盖
//		   false,表示老元素的值不保留,会进行覆盖
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {//定义一个局部变量,用来记录哈希表中数组的地址值。Node<K,V>[] tab;//临时的第三方变量,用来记录键值对对象的地址值Node<K,V> p;//表示当前数组的长度int n;//表示索引int i;//把哈希表中数组的地址值,赋值给局部变量tabtab = table;if (tab == null || (n = tab.length) == 0){//1.如果当前是第一次添加数据,底层会创建一个默认长度为16,加载因子为0.75的数组//2.如果不是第一次添加数据,会看数组中的元素是否达到了扩容的条件//如果没有达到扩容条件,底层不会做任何操作//如果达到了扩容条件,底层会把数组扩容为原先的两倍,并把数据全部转移到新的哈希表中tab = resize();//表示把当前数组的长度赋值给nn = tab.length;}//拿着数组的长度跟键的哈希值进行计算,计算出当前键值对对象,在数组中应存入的位置i = (n - 1) & hash;//index//获取数组中对应元素的数据p = tab[i];if (p == null){//底层会创建一个键值对对象,直接放到数组当中tab[i] = newNode(hash, key, value, null);}else {Node<K,V> e;K k;//等号的左边:数组中键值对的哈希值//等号的右边:当前要添加键值对的哈希值//如果键不一样,此时返回false//如果键一样,返回trueboolean b1 = p.hash == hash;if (b1 && ((k = p.key) == key || (key != null && key.equals(k)))){e = p;} else if (p instanceof TreeNode){//判断数组中获取出来的键值对是不是红黑树中的节点//如果是,则调用方法putTreeVal,把当前的节点按照红黑树的规则添加到树当中。e = ((TreeNode<K,V>)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,如果超过8,就会调用方法treeifyBin//treeifyBin方法的底层还会继续判断//判断数组的长度是否大于等于64//如果同时满足这两个条件,就会把这个链表转成红黑树if (binCount >= TREEIFY_THRESHOLD - 1)treeifyBin(tab, hash);break;}//e:			  0x0044  ddd  444//要添加的元素: 0x0055   ddd   555//如果哈希值一样,就会调用equals方法比较内部的属性值是否相同if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))){break;}p = e;}}//如果e为null,表示当前不需要覆盖任何元素//如果e不为null,表示当前的键是一样的,值会被覆盖//e:0x0044  ddd  555//要添加的元素: 0x0055   ddd   555if (e != null) {V oldValue = e.value;if (!onlyIfAbsent || oldValue == null){//等号的右边:当前要添加的值//等号的左边:0x0044的值e.value = value;}afterNodeAccess(e);return oldValue;}}//threshold:记录的就是数组的长度 * 0.75,哈希表的扩容时机  16 * 0.75 = 12if (++size > threshold){resize();}//表示当前没有覆盖任何元素,返回nullreturn null;}

2.TreeMap源码解读

1.TreeMap中每一个节点的内部属性
K key;					//键
V value;				//值
Entry<K,V> left;		//左子节点
Entry<K,V> right;		//右子节点
Entry<K,V> parent;		//父节点
boolean color;			//节点的颜色2.TreeMap类中中要知道的一些成员变量
public class TreeMap<K,V>{//比较器对象private final Comparator<? super K> comparator;//根节点private transient Entry<K,V> root;//集合的长度private transient int size = 0;3.空参构造//空参构造就是没有传递比较器对象public TreeMap() {comparator = null;}4.带参构造//带参构造就是传递了比较器对象。public TreeMap(Comparator<? super K> comparator) {this.comparator = comparator;}5.添加元素public V put(K key, V value) {return put(key, value, true);}参数一:键
参数二:值
参数三:当键重复的时候,是否需要覆盖值true:覆盖false:不覆盖private V put(K key, V value, boolean replaceOld) {//获取根节点的地址值,赋值给局部变量tEntry<K,V> t = root;//判断根节点是否为null//如果为null,表示当前是第一次添加,会把当前要添加的元素,当做根节点//如果不为null,表示当前不是第一次添加,跳过这个判断继续执行下面的代码if (t == null) {//方法的底层,会创建一个Entry对象,把他当做根节点addEntryToEmptyMap(key, value);//表示此时没有覆盖任何的元素return null;}//表示两个元素的键比较之后的结果int cmp;//表示当前要添加节点的父节点Entry<K,V> parent;//表示当前的比较规则//如果我们是采取默认的自然排序,那么此时comparator记录的是null,cpr记录的也是null//如果我们是采取比较去排序方式,那么此时comparator记录的是就是比较器Comparator<? super K> cpr = comparator;//表示判断当前是否有比较器对象//如果传递了比较器对象,就执行if里面的代码,此时以比较器的规则为准//如果没有传递比较器对象,就执行else里面的代码,此时以自然排序的规则为准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 {V oldValue = t.value;if (replaceOld || oldValue == null) {t.value = value;}return oldValue;}} while (t != null);} else {//把键进行强转,强转成Comparable类型的//要求:键必须要实现Comparable接口,如果没有实现这个接口//此时在强转的时候,就会报错。Comparable<? super K> k = (Comparable<? super K>) key;do {//把根节点当做当前节点的父节点parent = t;//调用compareTo方法,比较根节点和当前要添加节点的大小关系cmp = k.compareTo(t.key);if (cmp < 0)//如果比较的结果为负数//那么继续到根节点的左边去找t = t.left;else if (cmp > 0)//如果比较的结果为正数//那么继续到根节点的右边去找t = t.right;else {//如果比较的结果为0,会覆盖V oldValue = t.value;if (replaceOld || oldValue == null) {t.value = value;}return oldValue;}} while (t != null);}//就会把当前节点按照指定的规则进行添加addEntry(key, value, parent, cmp < 0);return null;}	private void addEntry(K key, V value, Entry<K, V> parent, boolean addToLeft) {Entry<K,V> e = new Entry<>(key, value, parent);if (addToLeft)parent.left = e;elseparent.right = e;//添加完毕之后,需要按照红黑树的规则进行调整fixAfterInsertion(e);size++;modCount++;}private void fixAfterInsertion(Entry<K,V> x) {//因为红黑树的节点默认就是红色的x.color = RED;//按照红黑规则进行调整//parentOf:获取x的父节点//parentOf(parentOf(x)):获取x的爷爷节点//leftOf:获取左子节点while (x != null && x != root && x.parent.color == RED) {//判断当前节点的父节点是爷爷节点的左子节点还是右子节点//目的:为了获取当前节点的叔叔节点if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {//表示当前节点的父节点是爷爷节点的左子节点//那么下面就可以用rightOf获取到当前节点的叔叔节点Entry<K,V> y = rightOf(parentOf(parentOf(x)));if (colorOf(y) == RED) {//叔叔节点为红色的处理方案//把父节点设置为黑色setColor(parentOf(x), BLACK);//把叔叔节点设置为黑色setColor(y, BLACK);//把爷爷节点设置为红色setColor(parentOf(parentOf(x)), RED);//把爷爷节点设置为当前节点x = parentOf(parentOf(x));} else {//叔叔节点为黑色的处理方案//表示判断当前节点是否为父节点的右子节点if (x == rightOf(parentOf(x))) {//表示当前节点是父节点的右子节点x = parentOf(x);//左旋rotateLeft(x);}setColor(parentOf(x), BLACK);setColor(parentOf(parentOf(x)), RED);rotateRight(parentOf(parentOf(x)));}} else {//表示当前节点的父节点是爷爷节点的右子节点//那么下面就可以用leftOf获取到当前节点的叔叔节点Entry<K,V> y = leftOf(parentOf(parentOf(x)));if (colorOf(y) == RED) {setColor(parentOf(x), BLACK);setColor(y, BLACK);setColor(parentOf(parentOf(x)), RED);x = parentOf(parentOf(x));} else {if (x == leftOf(parentOf(x))) {x = parentOf(x);rotateRight(x);}setColor(parentOf(x), BLACK);setColor(parentOf(parentOf(x)), RED);rotateLeft(parentOf(parentOf(x)));}}}//把根节点设置为黑色root.color = BLACK;}

六.思考

1.TreeMap添加元素时,是否需要重写hashCode和equals方法?

不需要,因为源码中没有用到hashcode方法

2.HashMap是哈希表结构的,jdk8以后石油4数组+链表+红黑树组成的,既然有红黑树,HashMap的键是否需要实现compareable接口或者传递比较器对象呢?

不需要,因为HashMap的底层默认是利用哈希值的大小来创建红黑树的

3.TreeMap和HashMap谁的效率更高?

最坏情况即是添加的元素形成了链表,此时TreeMap的效率更高,但是几率很小

一般来说是HashMap效率更高(数组)直接查询

4.在Map集合中,java是否会提供一个如果键存在了而不覆盖的方法

会,因为在TreeMap的源码中,第四个变量默认为false,那么也就存在变量默认为true的方法
传递一个思想:
代码中的逻辑都有两面性,如果我们只知道了其中的A面,而且代码中还发现了有变量可以控制两面性的发生。
那么该逻辑一定会有B面。

习惯:
boolean类型的变量控制,一般只有AB两面,因为boolean只有两个值
int类型的变量控制,一般至少有三面,因为int可以取多个值。

5.三种双列集合该如何选择

三种双列集合既HashMap,linkedHashMap,Treemap

默认情况使用:HashMap,效率最高

若要存储有序使用:linkedHashMap

如果要排序使用:Treemap

http://www.dtcms.com/a/528355.html

相关文章:

  • 【十年后台管理系统】Redis的使用
  • SSM框架-MyBatis2
  • 深入理解JVM垃圾回收机制:从原理到实践
  • Spring的后处理器
  • 本地佛山顺德网站设计深圳市宝安区西乡街道
  • 监控 Linux 系统上的内存使用情况
  • 湖北省住房与建设厅网站高品质的网站开发
  • 智慧校园建设方案-6PPT(32页)
  • Spring的@Cacheable取缓存默认实现
  • MySQL-TrinityCore异步连接池的学习(七)
  • 2020应该建设什么网站建网站的论坛
  • 华为OD机考双机位A卷 - Excel单元格数值统计 (C++ Python JAVA JS GO)
  • SpringBoot集成Elasticsearch | Elasticsearch 7.x专属HLRC(High Level Rest Client)
  • 广东省住房城乡建设厅门户网站免费下载手机app
  • 信创入门指南:一文掌握信息技术应用创新的核心要点
  • 基于鸿蒙UniProton的物联网边缘计算:架构设计与实现方案
  • 基于Swin Transformer的脑血管疾病中风影像诊断系统研究
  • 宝安第一网站东莞关键词优化软件
  • 篮球论坛|基于SprinBoot+vue的篮球论坛系统(源码+数据库+文档)
  • SQL 进阶:触发器、存储过程
  • ansible快速准备redis集群环境
  • 公司网站制作效果长沙网站制造
  • 数据结构之堆
  • 【Linux学习笔记】日志器与线程池设计
  • 【Linux系统编程】编辑器vim
  • 鸿蒙ArkTS入门教程:小白实战“易经”Demo,详解@State、@Prop与List组件
  • 扩散模型与UNet融合的创新路径
  • 从入门到精通的鸿蒙学习之路——基于鸿蒙6.0时代的生态趋势与实战路径
  • 704.力扣LeetCode_二分查找
  • 如何做企业网站宣传wordpress 显示空白