Map集合
概述
java.util.Map<k,v>集合 Map集合的特点: 1.Map集合是一个双列集合,一个元素包含两个值(一个key,一个value)。 2.Map集合中的元素,key和value的数据类型可以相同,也可以不同。 3.Map集合中的元素,key是不允许重复的,value是可以重复的。 4.Map集合中的元素,key和value是一一对应。
结构继承图
继承图详细介绍
Map集合介绍: 1、Map集合和Collection集合没有关系。 2、Map集合以key和value的这种键值对的方式存储元素。 3、key和value都是存储java对象的内存地址。 4、所有Map集合的kev特点:无序不可重复的。 Map集合的key和Set集合存储元素特点相同。
HashMap集合介绍: HashMap集合底层是哈希表数据结构,查询速度快,是非线程安全的。 在JDK8之后,如果哈希表单向链表中元素超过8个,单向链表这种数据结构会变成红黑树数据结构。当红黑树上的节点数量小于6时,会重新把红黑树变成单向链表数据结构。这种方式也是为了提高检索效率,二叉树的检索会再次缩小扫描范围。提高效率。初始化容量16默认加载因子.75 JDK1.8之前:数组+单向链表 JDK1.8之后:数组+单向链表/红黑树(链表的长度超过8):提高查询的速度 HashMap集合是一个无序的集合,存储元素和取出元素的顺序有可能不一致 扩容是:扩容之后的容量是原容量的2倍。
LinkedHashMap集合介绍: 1.底层是哈希表+链表(保证迭代的顺序) 2.是一个有序的集合,存储元素和取出元素的顺序是一致的
Hashtable集合介绍: Hashtable集合底层也是哈希表数据结构,是线程安全的,其中所有的方法都带有 synchronized关键字,效率较低,现在使用较少了,因为控制线程安全有其它更好的方案.
Properties属性类介绍: Properties是线程安全的,因为继承Hashtable,另外Properties存储元素的时候也是采用key和value的形式存储,并且key和value只支持String类型不支持其它类型。 Properties被称为属性类。
SortedMap集合介绍: SortedMap集合的key存储元素的特点: 首先是无序不可重复的,另外放在SortedMap集合key部分的元素会自动按照大小顺序排序称为可排序称为可排序的集合。
TreeMap集合底层的数据结构是一个二叉树。
Map接口中常用方法
public V put(K key,V value):把指定的键与指定的值添加到Map集合中。 public V remove(Object key):把指定的键所对应的键值对元素在Map集合中删除,返回被删除元素的值 public V get(Object key):根据指定的键,在Map集合中获取对应的值。 boolean containsKey(Object key):判断集合中是否包含指定的键
put方法
public V put(K key,V value):把指定的键与指定的值添加到Map集合中。 返回值:v 存储键值对的时候,key不重复,返回值v是null。 存储键值对的时候,kev重复,会使用新的value替换map中重复的value,返回被替换的value值。
举例
private static void show01() { //创建Map集合对象 多态 Map<String,String> map = new HashMap<>() ; String v1 = map.put("小飞","冰冰1") ; System.out.println("v1:"+v1); //key不重复,返回值是null。输出:v1:null String v2 = map.put("小飞","冰冰2") ; System.out.println("v2:"+v2); //键重复 返回被替换的值。输出:v2:冰冰1 System.out.println(map); // 输出:{小飞=冰冰2} map.put("冷风","孔晓云"); map.put("杨过","小龙女"); System.out.println(map);//输出:{杨过=小龙女, 小飞=冰冰2, 冷风=孔晓云} }
remove方法
public V remove(Object key):把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值。 返回值:V key存在,v返回被删除的值。 key不存在,v返回null。
举例
private static void show02() { //创建Map集合对象 Map<String,Integer> map = new HashMap<>(); map.put("丽颖",168); map.put("音",165); map.put("玲",178); System.out.println(map); //{玲=178, 丽颖=168, 音=165} Integer v1 = map.remove("玲"); System.out.println("v1:"+v1); //v1:178 key存在,返回被删除的值 System.out.println(map); //{丽颖=168, 音=165} Integer v2 = map.remove("林志"); System.out.println("v2:"+v2);//v2:null key不存在 返回null System.out.println(map); //{丽颖=168, 音=165} // 注意问题 :多使用包装类接收,少使用基本数据类型 }
get方法
public V get(Object key):根据指定的键,在Map集合中获取对应的值。 返回值:key存在,返回对应的value值。key不存在,返回null。
举例
private static void show03() { //创建Map集合对象 Map<String,Integer> map = new HashMap<>(); map.put("丽颖",168); map.put("音",165); map.put("玲",178); Integer v1 = map.get("丽颖"); System.out.println("v1:"+v1);// v1:168 有对应的key返回对应的值 Integer v2 = map.get("热巴"); System.out.println("v2:"+v2); //v2:null 没有对应的key返回null }
containsKey方法
boolean containsKey(Object key):判断集合中是否包含指定的键,包含返回true,不包含返回false
举例
private static void show04() { //创建Map集合对象 Map<String,Integer> map = new HashMap<>(); map.put("丽颖",168); map.put("音",165); map.put("玲",178); boolean v1 = map.containsKey("丽颖"); System.out.println("v1:"+v1); //v1:true 有这个key 返回ture boolean v2 = map.containsKey("颖"); System.out.println("v2:"+v2); //v2:false 没有这个key 返回false }
Map集合两种遍历方式【重点】
键找值方式
Map集合的第一种遍历方式:通过键找值的方式 Map集合中的方法: Set<k> keySet(): 返回此映射中包含的键的 Set视图。 实现步骤: 1.使用Map集合中的方法keySet(),把Map集合所有的key取出来,存储到一个Set集合中。 2.遍历set集合,获取Map集合中的每一个key。 3.通过Map集合中的方法get(key),通过key找到value。
举例
public static void main(String[] args) { //创建Map集合对象 Map<String,Integer> map=new HashMap<>(); map.put("丽颖",168); map.put("音",165); map.put("玲",178); //1.使用keySet方法,把map集合所有的key取出来,存储到set集合中 Set<String> set = map.keySet(); //2.遍历set集合,获取map集合中每一个key //使用迭代器遍历 Iterator<String> it = set.iterator(); while (it.hasNext()){ String next = it.next(); //获取map集合中每一个key //3. 通过Map集合中的get方法,通过key找到value Integer integer = map.get(next); System.out.println(next+"="+integer); } System.out.println("================="); //使用增强for遍历 for (String s : set) { //3. 通过Map集合中的get方法,通过key找到value Integer integer = map.get(s); System.out.println(s+"="+integer); } //简化增强for遍历 // Set<String> set = map.keySet(); map.keySet()就相当于set集合 for (String s : map.keySet()) { //3. 通过Map集合中的get方法,通过key找到value Integer integer = map.get(s); System.out.println(s+"="+integer); } }
键值对方式
Map集合遍历的第二种方式:使用Entry对象遍历。这种方式效率比较高,因为获取的key和value都是直接从Entry对象中获取的属性值,这种方式比较适合于大数据量。
Entry键值对对象:
Map集合中的方法: Set<Map.Entry<K,V >> entrySet()返回此映射中包含的映射关系的 Set 视图。
实现步骤: 1.使用Map集合中的方法entrySet(),把Map集合中多个Entry对象取出来,存储到一个Set集合中 2.谝历Set集合,获取每一个Entry对象 3.使用Entry对象中的方法getKey()和getValue()获取键与值
举例
public static void main(String[] args) { //创建Map集合对象 Map<String,Integer> map=new HashMap<>(); map.put("丽颖",168); map.put("音",165); map.put("玲",178); //1.使用Map集合中的方法entrySet(),把Map集合中多个Entry对象取出来,存储到一个set集合中 Set<Map.Entry<String, Integer>> set = map.entrySet(); //2.遍历set集合 //使用迭代器遍历 Iterator<Map.Entry<String, Integer>> it = set.iterator(); while (it.hasNext()){ Map.Entry<String, Integer> next = it.next(); //3.使用Entry对象中的方法getKey(),getValue()获取键和值 String key = next.getKey(); Integer value = next.getValue(); System.out.println(key+"="+value); } System.out.println("================"); //使用增强for遍历 for (Map.Entry<String, Integer> aa : set) { //3.使用Entry对象中的方法getKey(),getValue()获取键和值 String key = aa.getKey(); Integer value = aa.getValue(); System.out.println(key+"="+value); } }
HashMap和哈希表数据结构
HashMap集合key部分允许null吗? 允许 但是要注意:HashMap集合的key null值只能有一个。有可能面试的时候遇到这样的问题。
HashMap集合: 1、HashMap集合底层是哈希表/散列表的数据结构。 2、哈希表是一个怎样的数据结构呢? 哈希表是一个数组和单向链表的结合体。 数组:在查询方面效率很高,随机增删方面效率很低。 单向链表:在随机增删方面效率较高,在查询方面效率很低。 哈希表将以上的两种数据结构融合在一起,充分发挥它们各自的优点。
3、最主要掌握的是: map.put(k,v) map.get(k) 以上这两个方法的实现原理,是必须掌握的。
4、HashMap集合的key部分特点:无序,不可重复。 为什么无序?因为不一定挂到哪个单向链表上。 不可重复是怎么保证的?equals方法来保证HashMap集合的key不可重复。如果key重复了,value会覆盖。 放在HashMap集合key部分的元素其实就是放到HashSet集合中了。所以HashSet集合中的元素也需要同时重写hashCode()+equals()
5、哈希表HashMap使用不当时无法发挥性能! 假设将所有的hashCode()方法返回值固定为某个值,那么会导致底层哈希表变成了纯单向链表。这种情况我们成为:散列分布不均匀。什么是散列分布均匀? 假设有100个元素,10个单向链表,那么每个单向链表上有10个节点,这是最好的。是散列分布均匀的。 假设将所有的hashCode()方法返回值都设定为不一样的值,可以吗,有什么问题? 不行,因为这样的话导致底层哈希表就成为一维数组了,没有链表的概念了。也是散列分布不均匀。 散列分布均匀需要你重写hashCode()方法时有一定的技巧。
同时重写hashCode和equals方法
1、向Map集合中存,以及从Map集合中取,都是先调用key的hashCode方法,然后再调用equals方法! equals方法有可能调用,也有可能不调用。 拿put(k,v)举例,什么时候equals不会调用? k.hashCode()方法返回哈希值 哈希值经过哈希算法转换成数组下标。 数组下标位置上如果是null,eauals不需要执行。 拿get(k)举例,什么时候equals不会调用? k.hashCode()方法返回哈希值, 哈希值经过哈希算法转换成数组下标。 数组下标位置上如果是null,equals不需要执行。
2、注意:如果一个类的equals方法重写了,那么hashCode()方法必须重写。并且equals方法返回如果是true,hashCode()方法返回的值必须一样。 equals方法返回true表示两个对象相同,在同一个单向链表上比较。那么对于同一个单向链表上的节点来说,他们的哈希值都是相同的。所以hashCode()方法的返回值也应该相同。
3、hashCode()方法和equals()方法不用研究了,直接使用IDEA工具生成,但是这两个方法需要同时生成。
4、终极结论: 放在HashMap集合key部分的,以及放在HashSet集合中的元素,需要同时重写hashCode方法和equals方法。
5、对于哈希表数据结构来说: 如果o1和o2的hash值相同,一定是放到同一个单向链表上。 当然如果o1和o2的hash值不同,但由于哈希算法执行结束之后转换的数组下标可能相同,此时会发生“哈希碰撞”。
HashMap存储自定义类型键值
HashMap存储自定义类型键值 Map集合保证key是唯一的: 作为key的元素,必须重写hashCode方法和equals方法,以保证key唯一
举例
Person类
public class Person { private String name; private int age; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Person{" + "name=" + name + + ", age=" + age + }; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return age == person.age && Objects.equals(name, person.name); } @Override public int hashCode() { return Objects.hash(name, 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 class 主方法 { public static void main(String[] args) { show01(); show02(); } /* HashMap存储自定义类型键值 key:String类型 String类重写了hashCode和equals方法,可以保证key唯一 value:Person类型 value可以重复(同名同年龄的人视为同一个) */ private static void show01() { //创建HashMap集合 HashMap<String,Person> map =new HashMap<>(); //往集合添加元素 key有重复会把新的value替换原来的value map.put("北京",new Person("张三",18)); map.put("上海",new Person("李四",19)); map.put("广州",new Person("王五",20)); map.put("北京",new Person("赵六",18)); //使用keySet加增强for遍历map集合 Set<String> set = map.keySet(); for (String s : set) { Person s1 = map.get(s); System.out.println(s+"-->"+s1); } } /* HashMap存储自定义类型键值 key:Person类型 Person类必须重写hashCode和equals方法,以保证key唯一 value:String类型 可以重复 */ private static void show02() { //创建HashMap集合 HashMap<Person,String> map =new HashMap<>(); //往集合添加元素 map.put(new Person("女王",18),"英国"); map.put(new Person("秦始皇",18),"秦国"); map.put(new Person("普京",30),"俄罗斯"); map.put(new Person("女王",18),"b国"); //使用entryset和增强for遍历map集合 Set<Map.Entry<Person, String>> entries = map.entrySet(); for (Map.Entry<Person, String> entry : entries) { Person key = entry.getKey(); String value = entry.getValue(); System.out.println(key+"-->"+value); } } }
LinkedHashMap集合
java.util.LinkedHashMap<K, V>entends HashMap<K.,V> Map 接口的哈希表和链接列表实现,具有可预知的迭代顺序。 底层原理:哈希表+链表(记录元素的顺序)
举例
public static void main(String[] args) { HashMap<String,String> map = new HashMap<>(); map.put("a","a"); map.put("b","b"); map.put("c","c"); map.put("a","d"); System.out.println(map); //{a=d, b=b, c=c} key不允许重复,无序 LinkedHashMap<String,String> linked = new LinkedHashMap<>(); linked.put("a","a"); linked.put("b","b"); linked.put("c","c"); linked.put("a","d"); System.out.println(linked);// key 不允许重复,有序{a=d, b=b, c=c} }
Hashtable集合
java.util.Hashtable<K,V>集合 implements Map<K,V>接口
HashMap:底层是一个哈希表,是一个线程不安全的集合,是多线程的集合,速度快。 Hashtable:底层也是一个哈希表,是一个线程安全的集合,是单线程集合,速度慢 。
HashMap集合(之前学的所有的集合):可以存储null值,null键 。 Hashtable集合,不能存储nul值,null键。
Hashtable和Vector集合一样,在idk1.2版本之后被更先进的集合(HashMap,ArrayList)取代了。Hashtable的子类Properties依然活跃在历史舞台 Properties集合是一个唯一和IO流相结合的集合
举例
public class Hashtable集合 { public static void main(String[] args) { HashMap<String,String> map =new HashMap<>(); map.put(null,"a"); map.put("a",null); map.put(null,null); System.out.println(map); //{null=null, a=null} Hashtable<String,String> tab = new Hashtable<>(); tab.put(null,"a");//NullPointerException 空指针异常 tab.put("a",null);//NullPointerException 空指针异常 tab.put(null,null);//NullPointerException 空指针异常 } }
Properties属性类
目前只需要掌播Properties属性类对象的相关方法即可。 Properties是一个Map集合,继承Hashtable,Properties的key和value都是string类型。 Properties被称为属性类对象。 Properties是线程安全的。
练习
计算一个字符串中每个字符出现的次数
示例
/* 练习:计算一个字符串中每个字符出现的次数 分析: 1.使用Scanner获取用户输入的字符串 2.创建Map集合,key是字符串中的字符,value是字符个数 3.遍历字符串,获取每一个字符 4.使用获取到的字符,去Map集合判断key是否存在 key存在: 通过字符串(key),获取value(字符个数) value++ put(key,value) key不存在: put(key,1) 5.遍历Map集合,输出结果 */ public class 计算一个字符串中每个字符出现的次数 { public static void main(String[] args) { //1.使用Scanner获取用户输入的字符串 Scanner sc =new Scanner(System.in); System.out.println("请输入一个字符串:"); String next = sc.next(); //获取字符串 //2.创建Map集合,key是字符串中的字符,value是字符个数 HashMap<Character,Integer> map =new HashMap<>(); //3.遍历字符串,获取每一个字符 for (Character c:next.toCharArray()){ //4.使用获取到的字符,去Map集合判断key是否存在 if (map.containsKey(c)){ //key已经存在,通过字符串(key),获取value(字符个数),让字符个数加一 Integer value = map.get(c); value++;//让它加一 map.put(c,value);//把新的覆盖上去 }else { //key不存在:put(key,1) map.put(c,1); //集合中没这个key,让他存进集合,初始值为1 } } // 不遍历 直接输出也ok System.out.println(map); //5.遍历Map集合,输出结果 for (Character key:map.keySet()){ Integer value = map.get(key); System.out.println(key+"="+value); } } }
斗地主案例
/* 斗地主综合案例:有序版本 1.准备牌 2.洗牌 3.发牌 4.排序 5.看牌 */ public class 斗地主案例 { public static void main(String[] args) { //1.准备牌 //创建一个Map集合,存储牌的索引和组装好的牌 HashMap<Integer,String> poker = new HashMap<>(); //创建一个List集合,存储牌的索引 ArrayList<Integer> pokerIndex = new ArrayList<>(); //定义两个集合,存储花色和牌的序号 List<String> colors = List.of("♠", "♥", "♣", "♦"); List<String> numbers = List.of("2", "A", "K", "Q", "J", "10", "9", "8", "7", "6", "5", "4", "3"); //把大王和小王存储到集合中 //定义一个牌的索引 int index=0; poker.put(index,"大王"); pokerIndex.add(index); index++; poker.put(index,"小王"); pokerIndex.add(index); index++; //循环嵌套遍历两个集合,组装52张牌,存储到集合中 for (String nu:numbers){ for (String co:colors){ poker.put(index, nu + co); pokerIndex.add(index); index++; } } // System.out.println(poker); // System.out.println(pokerIndex); //2.洗牌 Collections.shuffle(pokerIndex); // System.out.println(pokerIndex); //3.发牌 //定义四个集合,存储玩家牌的索引和底牌的索引 ArrayList<Integer> dipai = new ArrayList<>(); ArrayList<Integer> wanjia1 = new ArrayList<>(); ArrayList<Integer> wanjia2 = new ArrayList<>(); ArrayList<Integer> wanjia3 = new ArrayList<>(); //遍历存储牌索引的List集合,获取每一个牌的索引 for (int i = 0; i < pokerIndex.size(); i++) { Integer s = pokerIndex.get(i); //先判断底牌 if (i>=51){ dipai.add(s); }else if (i%3==0){ wanjia1.add(s); }else if (i%3==1){ wanjia2.add(s); }else if (i%3==2){ wanjia3.add(s); } } //4.排序 Collections.sort(dipai); Collections.sort(wanjia1); Collections.sort(wanjia2); Collections.sort(wanjia3); // System.out.println(wanjia1); //5.看牌 lookPoker("飞飞",poker,wanjia1); lookPoker("东海",poker,wanjia2); lookPoker("彬彬",poker,wanjia3); lookPoker("底牌",poker,dipai); } /* 5.看牌 定义一个看牌的方法,提高代码的复用性 参数: String name:玩家名称 HashMap<Integer,String> poker:存储牌的扑克集合 ArrayList<Integer> list: 存储玩家和底牌的List集合 查表法: 遍历玩家或底牌集合,获取牌的索引 使用牌的索引,去Map集合找到对应的牌 */ public static void lookPoker(String name,HashMap<Integer,String> poker,ArrayList<Integer> list){ //输出玩家名称不换行 System.out.print(name+": "); //遍历玩家或底牌集合,获取牌的索引 for (Integer integer : list) { //使用牌的索引,去Map集合找到对应的牌 String value = poker.get(integer); System.out.print(value+" "); } System.out.println();//打印完每一个玩家的牌换行 } }
JDK9对集合添加的优化_of方法
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/291022.html