文章目录
Map集合概述和特点
Collection每次只能保存一个对象,所以属于单值保存父接口。而在类集中又提供了保存偶对象的集合——Map集合。利用Map集合可以保存一对关联数据(key=value),这样就可以实现根据key取得value的操作。
-
在Map中键(key)可以使任意类型的对象。Map中不能有重复的键(Key),每个键(key)都有一个对应的值(value)。
-
一个键(key)和它对应的值构成map集合中的一个元素。
-
Map中的元素是两个对象,一个对象作为键,一个对象作为值。键不可以重复,但是值可以重复。
-
Map中两个常用的子类:
- HashMap
- HashTable
Map接口和Collection的区别
-
Map与Collection在集合框架中属并列存在
-
Map存储的是键值对
-
Map存储元素使用put方法,Collection使用add方法
-
Map集合没有直接取出元素的方法,而是先转成Set集合,再通过迭代获取元素
-
Map集合中键要保证唯一性
-
Map是双列集合,Collection是双列集合
-
Map集合的数据结构针对键有效,跟值无关;Collection集合的数据结构是针对元素有效
-
Map接口数据是为了查询,而Collection接口数据是为了输出。
Map集合的功能概述
a:添加功能
V put(K key,V value):添加元素。这个其实还有另一个功能——替换
如果键是第一次存储,就直接存储元素,返回null
如果键不是第一次存在,就用值把以前的值替换掉,返回以前的值
b:删除功能
void clear():移除所有的键值对元素
V remove(Object key):根据键删除键值对元素,并把值返回
c:判断功能
boolean containsKey(Object key):判断集合是否包含指定的键
boolean containsValue(Object value):判断集合是否包含指定的值
boolean isEmpty():判断集合是否为空
d:获取功能
Set<Map.Entry<K,V>> entrySet(): 返回一个键值对的Set集合
V get(Object key):根据键获取值
Set<K> keySet():获取集合中所有键的集合
Collection<V> values():获取集合中所有值的集合
e:长度功能
int size():返回集合中的键值对的对数
Map存储数据的特点
import java.util.HashMap;
import java.util.Map;
public class Demo1 {
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<>();
map.put(1, "壹");
map.put(2, "贰");
map.put(3, "叁");
map.put(1, "壹壹");
map.put(null, "空");
map.put(4, null);
map.put(null, "空空");
System.out.println(map);
}
}
// 程序运行结果
{null=空空, 1=壹壹, 2=贰, 3=叁, 4=null}
这个程序实现了Map最基础的数据保存的操作,在实例化Map接口对象是首先呢需要明确的指定泛型类型,此处指定的key是Integer,指定的value是String,然后利用put方法对数据进行保存。
在保存数据时,如果出现了key值重复的情况,旧的数据就会被新数据覆盖。
通过观察程序运行结果我们可以发现Map如下特点:
- 使用HashMap定义的Map集合是无序存放的(顺序无用)。
- 如果发现了重复的key会进行覆盖value,使用新的内容替换旧的内容。
- 使用HashMap子类保存数据时key和value可以保存为null。
我们用Map来保存数据并不是为了进行输出操作,而是为了进行查找,即利用get()方法通过key值找到相应的value值。如果指定的key值存在那就返回value值,如果指定的key值不存在那就返回null。
使用Hashtable时的注意事项
Hashtable和HashMap都属于Map接口的子类,所以从本质上讲,它们最终都会利用子类向上转型为Map接口对象实例化。但是在使用Hashtable子类实例化的Map集合中,保存的key或value都不允许出现null,否则会报空指针异常。
HashMap和Hashtable的区别
区别点 | HashMap | Hashtable |
---|---|---|
推出时间 | JDK1.2 | JDK1.0 |
数据安全 | 非线程安全 | 线程安全 |
赋null值 | 允许key和value为null | 不允许设置null值 |
利用Iterator输出Map集合
首先要明确一个原则,集合的输出要用Iterator接口完成。
但是Map接口与Collection接口在定义上有所不同,Map接口并没有直接提供取得Iterator接口对象的方法。所以如果要用Iterator输出Map接口数据,就必须清除Collection接口和Map接口在数据保存形式上的区别:
即Collection集合保存数据时所有的对象都是直接保存的,而用Map集合保存数据时,所保存的key与value会自动包装成Map.Entry接口对象,也就是说如果利用Iterator进行迭代,那么每当使用next()方法读取数据时返回的都是一个Map.Entry接口对象,这个接口定义如下:public static interface Map.Entry<K,V>{}
,即Map.Entry接口属于Map接口中定义的一个static内部接口,相当于外部接口。Map.Entry接口定义的常用方法有:
方法 | 类型 | 描述 |
---|---|---|
public K getKey() | 普通 | 取得数据中的key |
public V getValue() | 普通 | 取得数据中value |
public V setValue(V value) | 普通 | 修改数据中的value |
清楚了Map.Entry接口的作用后就可以来用Iterator接口输出Map集合了,在这里我们就要用到Map接口中的entrySet()方法了:
Set<Map.Entry<K,V>> entrySet(): 返回一个键值对的Set集合
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class MapIterator {
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<>();
map.put(1, "张三");
map.put(2, "李四");
map.put(3, "王五");
//1. 利用entrySet()方法将Map接口数据中的数据转换为Set实例化进行保存
// 此时Set接口中所使用的泛型类型是Map.Entry<Integer, String>
// 而Map.Entry中的K与V就是map定义时的两个类型
Set<Map.Entry<Integer, String>> entrySet = map.entrySet();
//2. 获取Iterator接口实例
Iterator<Map.Entry<Integer, String>> iterator = entrySet.iterator();
//3. 迭代输出,每次迭代取得的都是Map.Entry接口实例,利用它里面的方法可以把key和value分离
while (iterator.hasNext()) {
Map.Entry<Integer, String> me = iterator.next();
System.out.println(me.getKey() + "=" + me.getValue());
}
}
}
这个程序中最关键的就是Iterator每次迭代返回的类型是Map.Entry(注意泛型类型的设置),然后利用getKey()和getValue()方法取出key和value。
当然还有其他方法可以遍历Map集合:
V get(Object key):根据键获取值
Set<K> keySet():获取集合中所有键的集合
import java.util.*;
public class MapIterator2 {
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<>();
map.put(1, "张三");
map.put(2, "李四");
map.put(3, "王五");
// 获取集合中所有键的集合
Set<Integer> keySet = map.keySet();
// 获取迭代器实例
Iterator<Integer> keyIterator = keySet.iterator();
// 根据key值利用Map.get()方法来获取value
while (keyIterator.hasNext()) {
Integer key = keyIterator.next();
System.out.println(key + "=" + map.get(key));
}
}
}
你也可以单独把所有值放到一个Collection集合里利用迭代器输出,但是这种方法没法获取到相应的key值:
Collection<V> values():获取集合中所有值的集合
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class MapIterator3 {
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<>();
map.put(1, "张三");
map.put(2, "李四");
map.put(3, "王五");
Collection<String> valuesColl = map.values();
Iterator<String> iterator = valuesColl.iterator();
while (iterator.hasNext()) {
String value = iterator.next();
System.out.println(value);
}
}
}
自定义Map集合的key类型
在使用Map接口时可以发现,几乎可以使用任意的类型作为key或者value的存在,也就表示可以使用自定义的类型作为key。作为key的自定义类必须重写Object类中的hashCode()方法和equals()方法。因为key值的唯一性就是靠这两个方法来保证的。
- 自定义类
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;
}
@Override
public 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);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
- 测试是否可以插入重复元素
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class putSatudents {
public static void main(String[] args) {
HashMap<Student, Integer> hashMap = new HashMap<>();
hashMap.put(new Student("张三", 19), 101);
hashMap.put(new Student("李四", 18), 102);
hashMap.put(new Student("王五", 19), 103);
hashMap.put(new Student("赵六", 21), 104);
// 插入重复元素,测试Student类重写的hashCode方法和equals方法
hashMap.put(new Student("张三", 19), 101);
hashMap.put(new Student("李四", 18), 102);
hashMap.put(new Student("王五", 19), 103);
hashMap.put(new Student("赵六", 21), 104);
// 1. 利用entrySet()方法将Map接口数据中的数据转换为Set实例化进行保存
Set<Map.Entry<Student, Integer>> entrySet = hashMap.entrySet();
// 2. 获取迭代器对象
Iterator<Map.Entry<Student, Integer>> iterator = entrySet.iterator();
// 3. 遍历输出
while (iterator.hasNext()) {
Map.Entry<Student, Integer> entry = iterator.next();
System.out.println(entry);
// 这两种输出结果是一样的
//System.out.println(entry.getKey()+"="+entry.getValue());
}
}
}
// 程序运行结果:
Student{name='王五', age=19}=103
Student{name='赵六', age=21}=104
Student{name='张三', age=19}=101
Student{name='李四', age=18}=102
- 提示:尽量不要使用自定义数据类型作为key
虽然Map集合可以将各种数据类型作为key进行设置,但是从实际开发来讲,不建议使用自定义类作为key,建议使用java中提供的系统类作为key,如String、Integer等,其中String作为key的情况是最常见的。
TreeMap
- TreeMap 键不允许插入null
- 键的数据结构是红黑树,可保证键的排序和唯一性
- 排序分为自然排序和比较器排序
- 线程不安全,效率比较高
TreeMap的排序,TreeMap可以对集合中的键进行排序。TreeMap是怎样实现键的排序呢?
- 方式一:元素自身具备比较性——自然排序
和TreeSet一样,需要让存储在键位置的对象实现Comparable接口,重写compareTo()方法,也就是说让元素自身具备比较性,这种方式叫做元素的自然排序,也是默认排序。 - 方式二:容器具备比较性——比较器排序
当元素自身不具备比较性,或者自身具备的比较性不是所需要的。那么此时可以让容器自身具备。需要定义一个类实现接口Comparator,重写compare方法,并将该接口的子类实例对象作为参数传给TreeMap集合的构造方法。
- 注意:当Comparable比较方式和Comparator比较方式同时存在时,以Comparator的比较方式为主.
- 注意:在重写compareTo或者compare方法时,必须要明确比较的主要条件相等时要比较次要条件。
这其实和我们讲TreeSet是介绍的知识是一致的。
自然排序
- 创建Book类,实现Comparable接口:
import java.util.Objects;
public class Book implements Comparable<Book> {
private String title;
private double price;
public Book() {
}
public Book(String title, double price) {
this.title = title;
this.price = price;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Book book = (Book) o;
// 价格和书名全部相同,我们判断他们是同一本书
return Double.compare(book.price, price) == 0 &&
Objects.equals(title, book.title);
}
@Override
public int hashCode() {
return Objects.hash(title, price);
}
@Override
// 优先按照价格升序排序,如果价格一致,再调用String类的cmopareTo来排序
public int compareTo(Book o) {
if (this.price > o.price) {
return 1;
} else if (this.price < o.price) {
return -1;
} else {
return this.title.compareTo(o.title);
}
}
@Override
public String toString() {
return "Book{" +
"title='" + title + '\'' +
", price=" + price +
'}';
}
}
- 测试:
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
public class Test {
public static void main(String[] args) {
TreeMap<Book, String> treeMap = new TreeMap<Book, String>();
treeMap.put(new Book("三字经", 60), "古代文学经典");
treeMap.put(new Book("礼记", 70), "古代文学经典");
treeMap.put(new Book("高数", 80), "大学教材");
treeMap.put(new Book("春秋左转", 90), "古代文学经典");
treeMap.put(new Book("飞鸟集", 75), "西方诗集");
// 插入重复元素
treeMap.put(new Book("三字经", 60), "古代文学经典");
treeMap.put(new Book("礼记", 70), "古代文学经典");
treeMap.put(new Book("高数", 80), "大学教材");
treeMap.put(new Book("春秋左转", 90), "古代文学经典");
treeMap.put(new Book("飞鸟集", 75), "西方诗集");
// 遍历输出
Set<Map.Entry<Book, String>> entrySet = treeMap.entrySet();
Iterator<Map.Entry<Book, String>> iterator = entrySet.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
// 程序运行结果:
Book{title='三字经', price=60.0}=古代文学经典
Book{title='礼记', price=70.0}=古代文学经典
Book{title='飞鸟集', price=75.0}=西方诗集
Book{title='高数', price=80.0}=大学教材
Book{title='春秋左转', price=90.0}=古代文学经典
比较器
自己构建一个比较器,优先用书名的字典顺序排序,然后再比较价格:
import java.util.*;
public class Test2 {
public static void main(String[] args) {
// Stirng类的compareTo()方法:
// 按字典顺序比较两个字符串。
// 该比较基于字符串中各个字符的 Unicode 值。
// 按字典顺序将此 String 对象表示的字符序列与参数字符串所表示的字符序列进行比较。
TreeMap<Book, String> treeMap = new TreeMap<>(new Comparator<Book>() {
@Override
public int compare(Book o1, Book o2) {
// String类的compareTo方法来比较
int num1 = o1.getTitle().compareTo(o2.getTitle());
if (num1 == 0) {
if (o1.getPrice() > o2.getPrice()) {
return 1;
} else if (o1.getPrice() < o2.getPrice()) {
return -1;
} else {
return 0;
}
} else {
return num1;
}
}
});
treeMap.put(new Book("三字经", 60), "古代文学经典");
treeMap.put(new Book("礼记", 70), "古代文学经典");
treeMap.put(new Book("高数", 80), "大学教材");
treeMap.put(new Book("春秋左转", 90), "古代文学经典");
treeMap.put(new Book("飞鸟集", 75), "西方诗集");
treeMap.put(new Book("三字经", 60), "古代文学经典");
treeMap.put(new Book("礼记", 70), "古代文学经典");
treeMap.put(new Book("高数", 80), "大学教材");
treeMap.put(new Book("春秋左转", 90), "古代文学经典");
treeMap.put(new Book("飞鸟集", 75), "西方诗集");
Set<Map.Entry<Book, String>> entrySet = treeMap.entrySet();
Iterator<Map.Entry<Book, String>> iterator = entrySet.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
// 程序运行结果:
Book{title='三字经', price=60.0}=古代文学经典
Book{title='春秋左转', price=90.0}=古代文学经典
Book{title='礼记', price=70.0}=古代文学经典
Book{title='飞鸟集', price=75.0}=西方诗集
Book{title='高数', price=80.0}=大学教材
案例
统计字符串中每个字符出现的次数
需求:统计字符串中每个字符出现的次数,比如输入"aababcabcdabcde",获取字符串中每一个字母出现的次数要求结果:a(5)b(4)c(3)d(2)e(1)。
- 方法一:
import java.util.*;
public class GetNumOfRepetitions {
public static void main(String[] args) {
System.out.println("请输入字符串:");
Scanner sc = new Scanner(System.in);
String text = sc.next();
// 新建一个<Character, Integer>的HashMap,Character代表字符,Integer代表出现次数
HashMap<Character, Integer> hashMap = new HashMap<>();
// 遍历字符串
for (int i = 0; i < text.length(); i++) {
// 取出每一个字符
char c = text.charAt(i);
// 判断hashMap中有没有这个字符,如果没有就保存起来,初始次数是1
if (!hashMap.containsKey(c)) {
hashMap.put(c, 1);
} else {
// 如果再次遍历到这个字符,就取出次数值+1,然后覆盖旧值
Integer num = hashMap.get(c);
num++;
hashMap.put(c, num);
}
}
// hashMap---->Set
Set<Map.Entry<Character, Integer>> entrySet = hashMap.entrySet();
// 获取set迭代器
Iterator<Map.Entry<Character, Integer>> iterator = entrySet.iterator();
// 遍历
while (iterator.hasNext()) {
Map.Entry<Character, Integer> c = iterator.next();
// 按照指定格式输出
System.out.println(c.getKey() + "(" + c.getValue() + ")");
}
}
}
// 程序运行结果:
请输入字符串:
aababcabcdabcde
a(5)
b(4)
c(3)
d(2)
e(1)
- 方法二:
import java.util.*;
public class GetNumOfRepetitions2 {
public static void main(String[] args) {
System.out.println("请输入字符串:");
Scanner sc = new Scanner(System.in);
String text = sc.next();
// 把字符串转成字符数组
String[] chars = text.split("");
// 利用Arrays.asList()方法把字符数组转成List
List<String> list = Arrays.asList(chars);
// HashSet(Collection<? extends E> c) 构造一个包含指定集合中的元素的新集合。
// HashSet的元素具有唯一性,利用这个构造方法去重
HashSet<String> set = new HashSet<>(list);
Iterator<String> iterator = set.iterator();
// 遍历,取出每一个不重复的单独的字符
while (iterator.hasNext()) {
String c = iterator.next();
int num = 0;
// 遍历字符数组,把它跟hashSet里面的字符作比较,并且统计重复次数
for (int i = 0; i < chars.length; i++) {
if (c.equals(chars[i])) {
num++;
}
}
// 按照指定格式输出
System.out.println(c + "(" + num + ")");
}
}
}
// 程序运行结果:
请输入字符串:
aababcabcdabcde
a(5)
b(4)
c(3)
d(2)
e(1)
- 方法三:
import java.util.*;
public class GetNumOfRepetitions3 {
public static void main(String[] args) {
System.out.println("请输入字符串:");
Scanner sc = new Scanner(System.in);
String text = sc.next();
HashMap<Character, Integer> hashMap = new HashMap<>();
for (int i = 0; i < text.length(); i++) {
// 遍历字符串,取出每个字符
char c = text.charAt(i);
// 该字符初始出现次数是0
int num = 0;
// 再次遍历字符串,碰到重复值就给num+1
for (int j = 0; j < text.length(); j++) {
if (c == text.charAt(j)) {
num++;
}
}
// 把字符串中的每个字符都存入hashMap中,但是hashMap的key具有唯一性
hashMap.put(c, num);
}
// hashMap---->Set对象
Set<Map.Entry<Character, Integer>> entrySet = hashMap.entrySet();
// 获取set迭代器
Iterator<Map.Entry<Character, Integer>> iterator = entrySet.iterator();
// 遍历
while (iterator.hasNext()) {
Map.Entry<Character, Integer> c = iterator.next();
// 按照指定格式输出
System.out.println(c.getKey() + "(" + c.getValue() + ")");
}
}
}
// 程序运行结果:
请输入字符串:
aababcabcdabcde
a(5)
b(4)
c(3)
d(2)
e(1)
- 方法四:
import java.util.*;
public class GetNumOfRepetitions4 {
public static void main(String[] args) {
System.out.println("请输入字符串:");
Scanner sc = new Scanner(System.in);
String text = sc.next();
char[] chars = text.toCharArray();
//static void sort(char[] a)
//按照数字顺序排列指定的数组。
Arrays.sort(chars);
//chars字符数组:aaaaabbbbcccdde
String s = new String(chars);
for (int i = 0; i < chars.length; ) {
// 我们已经把这个字符串按照顺序排列好了
// 用该字符最后一次出现的索引-它第一次出现的索引+1就是它出现的次数
int num = s.lastIndexOf(chars[i]) - s.indexOf(chars[i]) + 1;
System.out.println(chars[i]+ "("+num+")");
// i表示下个要输出的字符的索引
i+=num;
}
}
}
// 程序运行结果:
请输入字符串:
aababcabcdabcde
a(5)
b(4)
c(3)
d(2)
e(1)
- 方法五:
public class a {
public static void main(String[] args) {
System.out.println("请输入字符串:");
Scanner sc = new Scanner(System.in);
String str = sc.next();
//创建26个空间大小的数组,存放26个字母
int[] nums = new int[26];
for (char i : str.toCharArray()) {
//自动将char i转化成ascall码
if (i >= 97 && i <= 122) {
//利用数组的索引进行存储
nums[i - 97]++;
}
}
for (int i = 0; i < nums.length; i++) {
if (nums[i] != 0) {
//i加上97并且再转化为char类型就可以显示相应的字符
char j = (char) (i + 97);
System.out.println(j + "====" + nums[i]);
}
}
}
}
// 程序运行结果:
请输入字符串:
aababcabcdabcde
a(5)
b(4)
c(3)
d(2)
e(1)
Collections工具类的概述和常见方法讲解
- Collections成员方法
public static <T> void sort(List<T> list): 排序,默认按照自然顺序
public static <T> int binarySearch(List<?> list,T key): 二分查找
public static <T> T max(Collection<?> coll): 获取最大值
public static void reverse(List<?> list): 反转
public static void shuffle(List<?> list): 随机置换