java.util.Map
集合
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集合底层的数据结构是一个二叉树。
public V put(K key,V value):
把指定的键与指定的值添加到Map集合中。
public V remove(Object key):
把指定的键所对应的键值对元素在Map集合中删除,返回被删除元素的值
public V get(Object key):
根据指定的键,在Map集合中获取对应的值。
boolean containsKey(Object key):
判断集合中是否包含指定的键
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, 冷风=孔晓云}
}
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}
// 注意问题 :多使用包装类接收,少使用基本数据类型
}
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
}
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集合中的方法:
Set
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 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对象中获取的属性值,这种方式比较适合于大数据量。
Map集合中的方法:
Set
> 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集合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()方法时有一定的技巧。
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存储自定义类型键值
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);
}
}
}
java.util.LinkedHashMap
entends HashMap 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}
}
java.util.Hashtable
集合 implements Map 接口
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是一个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 poker:存储牌的扑克集合
ArrayList 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();//打印完每一个玩家的牌换行
}
}