map和set
哈希表(hashMap)和 集合 (Set)是数据结构中比较常用的一部分,他们的特性通常可以解决很多问题,这两个数据结构是同根生,存储数据的方式也很特别。hashmap
相当于一个地图,存储数据是按照键值对的方式来存储的,就等于一种类对象的数据模式。Set
是一种集合,存储方式与数组差不多。不过虽然这两种数据结构与其他数据有些相同之处,不过他们最大的区别就是,set
中不会存储两个相同的值,存储在set
中的值都是唯一的。同样的,hashmap
中的键也是唯一的。利用这两个数据的特性解决一些问题是不是会方便许多。
Set的两种常用形态
Set
是一个Java集合框架的一个接口,存储的元素都是唯一的。因为是接口,所有只能使用接口实现类来构造:
常见的有三种,常用的只有两种:HashSet
,TreeSet
,那么这两个分别有什么区别呢?
特性比较 | HashSet | TreeSet |
---|---|---|
内部实现 | 使用红黑树实现 | 使用哈希表实现 |
有序性 | 有序 | 无序 |
查找删除元素时性能 | 时间复杂度为常数 | 时间复杂度为O(log2) |
对元素相等时的判断 | 使用equals | 使用比较器或者comparaTo方法 |
数据大时 | 优先选择 | 不推荐 |
先来了解一下如何构造:
set和map后的<>中可以添加基本数据类型和引用类型
Set<Integer> set = new HashSet<>();
HashSet<Integer> hashSet = new HashSet<>();
TreeSet<Integer> treeSet = new TreeSet<>();
学习完构造方法,那我们来了解一些方法,如何快速上手:
可以拿这个题练习一下使用。
这里我使用了Set保存’宝石‘,然后去石头中进行对比,得到宝石数量,返回即可。利用了set中的元素唯一性和判断元素存在集合的方法。感兴趣的小伙伴可以多多练习。
class Solution {
public int numJewelsInStones(String jewels, String stones) {
Set<Character> set = new HashSet<>();
for (char ch:jewels.toCharArray()) {
set.add(ch);
}
int ret = 0;
for (char ch:stones.toCharArray()) {
if(set.contains(ch)){
ret++;
}
}
return ret;
}
}
Map的两种常用形态
与前者一样,map
也是Java的数据框架接口类,因为它独特的存储方式,备受喜爱,在排除元素或者统计元素个数时使用十分方便,因为HaspMap
查找一个数据的时间复杂度是一个常数项,也就是O(1)
,几乎达到了最快速度。查找元素如此高效与它底层结构脱不了干系!
特性 | HashMap | TreeMap |
---|---|---|
底层数据结构 | 链表加数组或红黑树 | 红黑树 |
排序 | 无序 | 有序,按升序排序 |
查找元素删除元素效率 | 高效,时间复杂度为O(1) | 时间复杂度为O(log2) |
使用空间时 | 内存相对于TreeMap少 | 使用内存多一些,需要维护树 |
如何构造一个哈希表:
//T是泛型
HashMap<T,T> hashMap = new HashMap<>();
TreeMap<T,T> treeMap = new TreeMap<>();
map基本方法:
使用map存储普通数据类型:
public static void main(String[] args) {
//存储普通数据类型
HashMap<String,Integer> hashMap = new HashMap<>();
TreeMap<String,Integer> treeMap = new TreeMap<>();
//添加普通数据类型
hashMap.put("aaa",1);
hashMap.put("bbb",2);
treeMap.put("aaa",1);
treeMap.put("bbb",2);
System.out.println("hashmap:"+hashMap);
System.out.println("treeMap:"+treeMap);
}
可以看到map的存储方式是键值对,map存储数据有一个非常需要注意的地方!map的键是唯一的,意味着如果在同一个map中存储两个键相同的数据,这表明后一个值会将前一个值进行替换!
如下:
public static void main(String[] args) {
//存储普通数据类型
HashMap<String,Integer> hashMap = new HashMap<>();
hashMap.put("aaa",1);
hashMap.put("aaa",2);
System.out.println("hashmap:"+hashMap);
}
在map
中不仅可以存储普通数据类型,还可以使用类对象作为键:
public static void main(String[] args) {
HashMap<Student,Integer> hashMap = new HashMap<>();
TreeMap<Student,Integer> treeMap = new TreeMap<>();
//HashMap的打印
hashMap.put(new Student(),10);
hashMap.put(new Student(),20);
System.out.println(hashMap);
//TreeMap的打印
treeMap.put(new Student(),10);
treeMap.put(new Student(),20);
System.out.println(treeMap);
}
这个案例可以清楚的发现这两者之间的不同之处,当hashmap和treemap
存储的数据相同时,hashmap
可以正常存储,而treemap
在存储时会发生错误,观察错误是说明应该添加一个comparable(比较器)
这个说明了在hashmap
中数据是无序存在的,而在treemap
中是有序存在的,当存储一个数据时,treemap
必须要知道数据排列的方式。
存储数据时treemap
和hashmap
中键和值的区别(可以为null吗?)!
public static void main(String[] args) {
HashMap<Integer,Integer> hashMap = new HashMap<>();
TreeMap<Integer,Integer> treeMap = new TreeMap<>();
//HashMap的打印
hashMap.put(null,10);//键为null,值不为空
System.out.println(hashMap);
//TreeMap的打印
treeMap.put(null,10);
System.out.println(treeMap);
}
可以看到treemap
抛出空指针异常,说明不能以null
作为treemap
的键。
public static void main(String[] args) {
HashMap<Integer,Integer> hashMap = new HashMap<>();
TreeMap<Integer,Integer> treeMap = new TreeMap<>();
//HashMap的打印
hashMap.put(2,null);
System.out.println(hashMap);
//TreeMap的打印
treeMap.put(2,null);
System.out.println(treeMap);
}
当使用null作为值时,可以看到打印结果,并没有异常抛出,说明null值是可以作为值存在于map
中。
练习:字符串中的第一个唯一字符
public int firstUniqChar(String s) {
int ret = -1;
//建立一个哈希表存储字符和记录字符个数
HashMap<Character,Integer> hashMap = new HashMap<>();
for (int i = 0; i <s.length() ; i++) {
hashMap.put(s.charAt(i),hashMap.getOrDefault(s.charAt(i),0)+1);
}
for (int i = 0; i <s.length() ; i++) {
//从前往后遍历,找到第一个符合条件的字符,退出循环
if(hashMap.get(s.charAt(i))==1){
ret = i;
break;
}
}
return ret;
}