Map是什么
概述:map是一个集合
将键映射到值的对象
一个映射不能包含重复的键
每个键最多只能映射到一个值
Map接口和Collection接口的不同
Map是双列的,Collection是单列的
Map的键唯一,Collection的子体系Set是唯一的
Map集合的数据结构针对键有效,跟值无关;Collection集合的数据结构是针对元素有效
HashMap怎么用
/**
* 散列表 : 可以理解为数组保存元素是个链表
*
* 散列表中保存的是键值对(K和V)
*
* hashCode : hash算法,是把不定长的数据改变为定长的数据,是一种安全的加密算法,但不保证唯一
* 同一个对象生成多次hash值,那么值一定是相同的,
* 不同对象也有可能生成相同的hash值
*
* 添加过程 :
* 1 先调用添加的K,调用hashCode生成hash值
* 2 根据hash值计算数组下标
* 3 判断数组中该下标对应的位置上是否有元素
* 3.1 如果没有保存数据,就把该对象放到对应的下标中
* 3.2 如果保存了数据,此时调用添加的K的equals方法,和数组中该下标对应的所有数据的key进行比较
* 3.3 如果和数组下标对应的链表中 的数据 都不相等,就把该数据添加到对应的链表中
* 3.4 如果和链表中的数据一致了,则key不添加,把value值替换(用新的替换原来的)
* 4 java1.8新改动,如果该链表中,节点个数大于7,则该链表被转换为红黑树
*
* 在java把 没有散列表这个说法,只是把散列表封装为了HashMap和HashTable,并且HashTable已经过时
* 并且 HashMap的默认容量为 16
*
* HashSet 底层就是一个HashMap,并且只是Map的key部分,没有value
*
*/
public class HashSet_1 {
public static void main(String[] args) {
// 创建HashSet对象
HashSet<String> set = new HashSet<String>();
set.add("xxx");
set.add("xxx");
set.add("x1");
set.add("x2");
set.add("x3");
set.add("x4");
set.add("x5");
System.out.println(set.size());
}
}
简单练习
import java.util.HashSet;
import java.util.Set;
public class HashSet_2 {
public static void main(String[] args) {
// 规定姓名相同认为是同一个对象
User u1 = new User(18,"lisa");
User u2 = new User(27,"lisa");
Set<User> users = new HashSet<>();
users.add(u1);
users.add(u2);
System.out.println(users.size());
for (User user : users){
//User{age=18, name='lisa'}说明不添加而不是替换
System.out.println(user);
}
}
}
class User{
private int age;
private String name;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof User)) return false;
User user = (User) o;
return name.equals(user.name);
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public String toString() {
return "User{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
public User(int age, String name) {
this.age = age;
this.name = name;
}
}
Map特性好处
1.包含键值对
2.键唯一
3.键对应的值唯一
Map继承体系图
Map接口常用方法
V put(K key, V value) 向Map集合中添加键值对
V get(Object key)通过可以获取value
void clear()清空Map集合
boolean containsKey(Object key)判断Map中是否包含某个key
boolean containsValue(Object value)判断Map中是否包含某个value
boolean isEmpty()判断Map集合中元素个数是否为0
Set<K> keySet()获取Map集合所有的key(所有的键是一个set集合)
V remove(Object key) 通过key删除键值对
int size() 获取Map集合中键值对的个数。
Collection<V> values() 获取Map集合中所有的value,返回一个Collection
Set<Map.Entry<K,V>> entrySet() b 将Map集合转换成Set集合
注意:
Map.Entry和String一样,都是一种类型的名字,只不过:Map.Entry是静态内部类,是Map中的静态类
Map集合通过entrySet()的方法转换成的这个Set集合,Set集合中的元素的类型都是Map.Entry<K,V>
遍历Map集合
import java.util.*;
public class MapText {
public static void main(String[] args) {
//第一种方式:获取所有Key,通过遍历key。来遍历value
Map<Integer,String> map = new HashMap<>();
map.put(1,"zhangsan");
map.put(2,"lisi");
map.put(3,"wangwu");
map.put(4,"zhaoliu");
第一种方式:获取所有Key,通过遍历key。来遍历value
//遍历Map集合
//获取所有的key,所有的key是一个Set集合
Set<Integer> keys = map.keySet();
//遍历key,通过key返回value
//迭代器也可以
Iterator<Integer> it = keys.iterator();
while (it.hasNext()){
Integer key = it.next();
String value = map.get(key);
System.out.println(key + "=" + value);
}
//foreach
for (Integer key : keys){
System.out.println(key + "=" + map.get(key));
}
第二种方法:Set<Map.Entry<K,V>> entrySet() b
将Map集合转换成Set集合,Set集合中元素的类型是:Map.Entry
Map.Entry和String一样,都是一种类型的名字,只不过:Map.Entry是静态内部类,是Map中的静态类
Set<Map.Entry<Integer,String>> set = map.entrySet();
//遍历Set集合,每一次取出一个Node
//迭代器
Iterator<Map.Entry<Integer,String>> it = set.iterator();
while (it.hasNext()){
System.out.println(it.next());
}
或者
Set<Map.Entry<Integer,String>> set = map.entrySet();
//遍历Set集合,每一次取出一个Node
//迭代器
Iterator<Map.Entry<Integer,String>> it = set.iterator();
while (it.hasNext()){
Map.Entry<Integer,String> node = it.next();
Integer key = node.getKey();
String value = node.getValue();
System.out.println(key + "=" + value );
}
foreach:
- 这种方式效率比较高,因为获取key和value都是直接从node对象中获取的属性值
- 这种方式比较适合大数据
HashMap
HashMap集合底层是哈希表/散列表的数据结构。
在JDK8之后,如果哈希表单向链表中元素超过8个,单向链表这种数据结构会变成红黑树数据结构。当红黑树上的节点数量小于6时,会重新把红黑树变成单向链表数据结构。
这种方式也是为了提高检索效率,二叉树的检索会再次缩小扫描范围。提高效率。
哈希表
哈希表是一个一维数组,这个数组中每一个元素是一个单向链表。(数组和链表的结合体)
哈希表是一个数组和单向链表的结合体
数组:在查询方面效率很高,随机增删效率很低。
单向链表:在随机增删方面效率很高,在查询方面效率很低
哈希表将以上的两种数据结构融合在一起,充分发挥了它们各自的优点。
对于哈希表数据结构来说:
如果o1和o2的hash值相同,一定是放到同一个单向链表上。
当然如果o1和o2的hash值不相同,但由于哈希算法执行结束之后转换的数组下标可能相同,此时会发生“哈希碰撞”
HashMap简单使用
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* Map : 无序 , key不可重复,value值可重复
*
* Map和Collection不一样,但是操作基本上是一样的
*
* 集合保存单个对象,而map保存键值对映射关系
*
* 常用方法 :
* put(K,V) : 添加数据
* remove(K) : 删除数据
* clear() : 清空
* size() : 个数
* isEmpty() : 判断是否为空
* get(K) : 根据Key获取value
* values() : 获取所有的value,返回集合
* containsKey(K) : 判断是否包含某个key
* containsValue(V) : 判断是否包含某个value
* Set keySet() : 获取map中所有的key,返回set
* Set entrySet() : 获取map中的键值对,返回set
*/
public class HashMap_ {
public static void main(String[] args) {
// 创建map
Map<String,Integer> map = new HashMap<String,Integer>();
map.put("a",32);
map.put("a",14);
map.put("A",1);
map.put("A",5);
map.put("65",7);
map.put("65",45);
//打印个数
System.out.println(map.size());
// 根据key获取value , 2 因为key重复,value替换
System.out.println(map.get("a"));
// 是否包含某个value
System.out.println(map.containsKey("65"));
System.out.println(map.containsValue(5));
// 根据key删除该映射关系(K和V都删除,在链表中把该节点删除)
// map.remove("a");
//判断个数
System.out.println(map.size());
// map不能直接遍历
// 获取所有的value
Collection values = map.values();
// 遍历键值对中的值
for (Object obj : values){
System.out.println(obj);
}
//获取所有的key
Set<String> set = map.keySet();
for (String key : set){
System.out.println(key);
}
// 将map转换为set,并把key和value封装到了entry类对象中,然后把entry类对象保存到set中即可
Set<Map.Entry<String,Integer>> entries = map.entrySet();
for (Map.Entry<String,Integer> entry:entries){
System.out.println(entry);
// getKey 是获取key,getValue 是获取value
System.out.println(entry.getKey()+"->"+entry.getValue());
}
}
}
为什么哈希表的随机增删,以及查询效率都很高?
增删是在链表上完成的。
查询也不需要都扫描,只需要部分扫描。
HashMap集合的key特点
无序不可重复
为什么无序? 因为不一定挂在哪个单向链表上。
不可重复是怎么保证的? equals()方法来保证HashMap集合的key不可重复。如果key重复了,value会覆盖。
放在HashMap集合key部分的元素其实就是放到HashSet集合中了。
所以HashSet集合中的元素也需要同时重写hashCode()+equals()方法
哈希表HashMap使用不当时无法发挥性能
假设将所有的hashCode()
方法返回值固定为某个值,那么会导致底层哈希表变成了纯单向链表。这种情况我们称为:散列分布不均匀
什么实际散列分布均匀
假设有100个元素,10个单向链表,那么每个单向链表上有10个节点,这是最好的,是散列分布均匀的。
假设将所有的hashCode()方法返回值都设定为不一样的值,可以吗?
不可以,因为这样的话导致底层哈希表就变成了一维数组了,没有链表的概念了。也是散列分布不均匀。
必须同时重写hashCode和equals方法
放在HashMap集合key
部分的元素,以及放在HashSet集合中的元素,需要同时重写hashCode和equals方法
并且 equals() 方法返回如果是true,hasCode() 方法返回的值必须一样
HashMap集合容量
HashMap集合的默认初始化容量是16,默认加载因子是0.75
这个默认加载因子是当HashMap集合底层数组的容量达到75%的时候,数组开始扩容。
重点:HashMap集合的初始化容量必须是2的倍数,这也是官方推荐的,这是因为达到散列均匀,为了提高HashMap集合的存取效率,所必需的。
HashMap集合key允许为null
注意:但是HashMap集合的key
,null
只能有一个
HashMap和Hashtable的区别
Hashtable的key
和value
都是不能为null的。
HashMap集合的key
和value
都是可以为null的。
Hashtable集合初始化容量是11,集合扩容是:原容量*2+1
Properties
Properties是一个Map集合,继承Hashtable,Properties的key
和value
都是String
类型;
Properties被称为属性类对象;
Properties是线程安全的。
import java.util.Properties;
public class PropertiesText {
public static void main(String[] args) {
//创建一个Properties对象
Properties pro = new Properties();
//需要掌握Properties的两个方法,一个存,一个取
pro.setProperty("url","jdbc:mysql://localhost:3306/bjpowernode");
pro.setProperty("driver","com.mysql.jdbc.Driver");
pro.setProperty("username","root");
pro.setProperty("password","123");
//通过key获取value
String url = pro.getProperty("url");
String driver = pro.getProperty("driver");
String username = pro.getProperty("username") ;
String password = pro.getProperty("password");
System.out.println(url);
System.out.println(driver);
System.out.println(username);
System.out.println(password);
}
}
//Properties : key和value强制要求必须是字符串
import java.util.Properties;
public class Properties_ {
public static void main(String[] args) {
Properties ppts = new Properties();
// 添加数据
ppts.setProperty("driver", "mysql");
ppts.setProperty("username", "root");
// 获取数据
System.out.println(ppts.getProperty("driver"));//mysql
System.out.println(ppts.getProperty("username"));//root
// 不存在的key 得到null
System.out.println(ppts.getProperty("rwer"));//null
// 有个方法重载,第二个参数为默认值,假如根据key找不到数据的时候,返回该默认值,而不是null
// 并不会把该键值对添加进去
System.out.println(ppts.getProperty("rwer","默认值")); //默认值
}
}
TreeMap
TreeMap集合底层实际上是一个二叉树。
//导入三个核心类包
import java.util.Comparator;
import java.util.Set;
import java.util.TreeMap;
public class TreeMap_ {
public static void main(String[] args) {
//TreeMap : 保存的元素可以按照一定的规则进行排序
// 排序 :
// 1 要添加的元素 实现了Comparable接口
// 2 编写比较器类,实现Comparator接口
TreeMap<Integer,String> map = new TreeMap<Integer,String>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
//treeMap 在添加的时候 会自动调用key对象的compareTo方法,是用key进行比较,而不是value
map.put(1,"a");
map.put(2,"a");
map.put(14,"a");
map.put(13,"a");
map.put(51,"a");
Set set = map.entrySet();
for (Object obj : set){
// 51=a
// 14=a
// 13=a
// 2=a
// 1=a
System.out.println(obj);
}
}
}
ToList
import java.util.*;
/**
* Map转换为List存储,并且以value进行排序
*
* 因为map没有办法以value排序,因为treeMap中只是按照key排序的,所以想要以value排序,需要转换为list
*/
public class ToList {
public static void main(String[] args) {
Map<String,Integer> map = new HashMap<String,Integer>();
// 添加数据
map.put("a",13);
map.put("hhh",1);
map.put("bbb",3);
map.put("asd",1);
map.put("kpl",41);
// 把K和V封装到entry中,然后保存到set中
Set<Map.Entry<String,Integer>> set = map.entrySet();
// set转换为list
List<Map.Entry<String,Integer>> list = new ArrayList<Map.Entry<String,Integer>>(set);
// 匿名类
Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() {
@Override
public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
// 按照value值进行比较 o1-o2升序
return o1.getValue() - o2.getValue();
}
});
//[asd=1, hhh=1, bbb=3, a=13, kpl=41]
System.out.println(list);
}
}
练习题
需求
/** * 给出了一个字符串,只包括字母 "akshdasdbqwehhuigfahsbd" * * 计算字符串中每个字符出现的个数,并以value进行降序排序,加入value相等,则以字符升序排序 * * 最终输出格式为 z(5)a(3)b(3)c(2)d(1) * * 1 既然是字符和次数,肯定是键值对存储,key表示字符,value表示个数 * 2 遍历字符串,把每个字符取出 * 3 判断map中是否包含这个key,如果包含 value+1 , 不包含,添加进去value默认为1 * 4 通过entrySet转换为set * 5 通过new ArrayList(set) 转换为list * 6 通过collections.sort() 编写匿名内部类进行排序 * */
public class Test_ {
public static void main(String[] args) {
// 创建HashMap集合对象 key是Character包装类型,值是Integer包装类型
Map<Character,Integer> map = new HashMap<Character,Integer>();
String str = "aksUaIsdOhdasdGDJawIpoUqSHoiGhuigfaIaUwJsOfIhsKbKJLKd";
// 遍历字符串
for (int i = 0;i<str.length();i++){
// 获取每一个字符,调用charAt()方法
char c = str.charAt(i);
// 判断条件是'a'到'z'或'A'到'Z'的范围区间
if (c>='a'&&c<='z'||c>='A'&&c<='Z'){
// 再判断是否包含键值对中的键(key)
if (map.containsKey(c)){
// 获取集合中的字符元素赋给int类型,it来接收,it就是value
int it = map.get(c);
// 包含 那么 value值 it就+1 键保持不变
map.put(c,it+1);
}else {
// 不包含,添加进去value(it)默认为1
map.put(c,1);
}
}
}
// 通过entrySet转换为set
Set<Map.Entry<Character,Integer>> entries = map.entrySet();
// 通过new ArrayList(set) 转换为list
List<Map.Entry<Character,Integer>> list = new ArrayList<Map.Entry<Character,Integer>>(entries);
// 通过collections.sort() 编写匿名内部类进行排序
Collections.sort(list, new Comparator<Map.Entry<Character, Integer>>() {
@Override
public int compare(Map.Entry<Character, Integer> o1, Map.Entry<Character, Integer> o2) {
return o2.getValue()-o1.getValue();
}
});
System.out.println(list);
// 将集合元素保存的<K,V>遍历 K就是字母 V就是出现的次数
for (Map.Entry<Character,Integer> entry : list){
// 打印K,V
System.out.println("字母"+entry.getKey()+"出现了("+entry.getValue()+")次");
}
}
}