<注> Hello~ 此篇博客需要一丢丢的Java基础,更适合找工作的中国宝宝观看噢。如有错误,欢迎在评论区指出~
目录
1、Set
a) 概念
Set是一种集合接口,它表示一组不包含重复元素的集合。Set接口继承自Collection接口,并且有多个实现类。之前Set都是用Object[]来存储元素。jdk1.5引入了泛型,可以指定集合中存储的元素类型,比如Set<String>表示存储字符串类型的集合。这样可以在编译时检查类型安全,避免在运行时出现类型转换错误。泛型的引入使得集合更加类型安全和方便使用。
<Tips> 泛型是一种在编程中使用的技术,它允许在定义类、接口和方法时使用一个或多个类型参数。通过使用泛型,可以在编译时检查代码的类型安全性,并且可以在不同的数据类型之间重用代码,提高代码的灵活性和可重用性。在Java中,泛型通常用于集合类和其他通用类中,以便在编译时指定数据类型。
在使用时,若没有指定泛型类型,默认使用Object类型
List list = new HashTestrrayList(); //默认可以放任意Object类型
b) 使用泛型的好处
1、减少了强制类型转换的次数,获取数据值更方便。
2、提供类型安全的机制,可以在编译时检查类型的一致性,从而减少程序运行时出现类型错误的可能性。
c) lambda表达式
Lambda表达式是一种匿名函数,可以作为参数传递给其他函数或方法。它允许在一行代码中定义简单的函数,并且通常用于函数式编程和简化代码。Lambda表达式通常由箭头符号“->”分隔参数列表和函数体。使用Lambda表达式来可以简化集合的操作,特别是在对集合进行遍历、筛选、映射等操作时。
例如,可以使用Lambda表达式来遍历Set中的元素:
Set<String> set = new HashSet<>();
set.add("apple");
set.add("banana");
set.add("orange");
set.forEach(item -> System.out.println(item));
另外,还可以使用Lambda表达式来筛选集合中的元素:
Set<String> set1 = new HashSet<>();
set.add("apple");
set.add("banana");
set.add("orange");
Set<String> set2 = set.stream()
.filter(item -> item.startsWith("a"))
.collect(Collectors.toSet());
set2.forEach(System.out::println);
d) HashSet
HashSet基于哈希表实现,它使用哈希函数来计算元素的索引,然后将元素存储在对应的索引位置。HashSet的元素是无序的,且不允许重复元素。
e) TreeSet
TreeSet基于红黑树实现,它会对插入的元素进行排序。TreeSet的元素是无序的,且不允许重复元素。
<Tips> 在使用HashSet时,插入、删除和查找元素的时间复杂度都是O(1);而在使用TreeSet时,这些操作的时间复杂度都是O(logn)。因此,如果需要对元素进行排序或有序遍历,可以使用TreeSet;如果只需要快速查找元素,可以使用HashSet。
TreeSet无法直接对自定义类型进行排序。
在我们常见的集合类型中,
有序的有ArrayList,LinkedList,LinkedHashSet,LinkedHashMap等,
无序的有HashSet,HashMap,HashTable,TreeSet,TreeMap等,
其中,TressSet和TressMap是可排序的。
2、Map
a) 概念
Map是一种键值对存储数据的集合类。它表示一个映射接口,将键映射到值。Map中的键是唯一的,每个键最多只能映射到一个值。Map和Collection没有继承关系,Map 以(key,value)键值对的形式存储数据。(key和value存储的都是对象的内存地址)
b) 方法
- put(k key, v value): 将指定的键值对添加到Map中,若Key已经存在,则更新对应的value。
- get(k key): 返回指定key对应的value,如果key不存在,则返回null。
- boolean containsKey(k key): 判断Map中是否包含指定的键。
- boolean containsValue(v value): 判断Map中是否包含指定的值。
- remove(Object key): 删除Map中指定的key。
- size(): 返回Map中键值对的数量。
- boolean isEmpty(): 判断Map是否为空。
- void clear(): 清空Map中的所有键值对。
- Set<k>keySet(): 返回Map中所有key的集合(Set)。
- Collection<v>values(): 返回Map中所有value的集合(Collection)。
- Set<Map.Entry<k,v>>entrySet(): 返回Map中对应的Map.Entry集合(Set)。
- default V getOrDefault(Object key, V defaultValue):返回指定key对应的value,如果key不存在,则返回defaultValue。
<Tips> Map.Entry<K,V>是Map的一个接口,接口中的内部接口默认是public static。
c) 遍历方法
第一类方法:先获取map的keySet,然后取出key对应的value。
特点:效率相对较低,因为还要根据key从哈希表中查找对应的value。
(1)通过foreach遍历map.keySet(),取出对应的value。
public static void printMap1(Map<Integer, String>map){
if(map.size()==0){ //may中没有键值对
System.out.println("the map is empty.");
return;
}
for(Integer key : map.keySet()){
System.out.println(key + " : " + map.getOrDefault(key,""));
}
}
(2)通过迭代器迭代map.keySet(),来取出对应的value
public static void printMap2(Map<Integer, String> map){
Set<Integer> keySet = map.keySet();
Iterator<Integer> it = keySet.iterator();
while (it,hasNext()) {
Integer cntKey = it.next();
System.out.println( cntKey + " : " + map.getOrDefault( cntKey, ""));
}
}
第二类方法:调用map.entrySet()方法,获取entrySet,然后直接从entrySet中获取key和value。
特点:效率较高,可以直接获取key和value;适用于大数据量map的遍历。
(1)调用map.entrySet(),然后使用foreach遍历entrySet
public static void printMap3(Map<Integer, String> map){
Set<Map.Entry<Integer, String>> entry = map.entrySet();
for(Map.Entry<Integer, String> it : entry){
System.out.println(it.getKey() + " : " + it.getValue());
}
}
(2)调用map.entrySet(),然后使用迭代器遍历entrySet
public static void printMap4(Map<Integer , String> map){
Set<Map.Entry<Integer , String>> entrySet = map.entrySet();
Iterator<Map.Entry<Integer , String>> it = entrySet.iterator();
while(it.hasNext()){
Map.Entry<Integer , String> node = it.next();
System.out.println(node.getKey() + " : " + node.getValue());
}
}
d) HashMap
(1)概念
HashMap实现了Map接口,是数组(查询效率高)和单链表(随机增删元素效率高)的结合体(底层是一个数组),用于存储键值对。HashMap中每个元素是一个单向链表(采用拉链法解决哈希冲突),单链表的每个节点是Node<K,V>类型。键是唯一的,但值可以重复。当向HashMap中添加一个键值对时,系统会根据键的哈希值来确定该键值对在哈希表中的位置。同一个单链表中所有节点的hash值不一定一样,但它们对应的数组下标一定一样。数组下标根据hash值,利用哈希函数计算得到数组下标。
(2)特点
1. 键值对无序存储:HashMap中的键值对是无序存储的,即插入的顺序不会影响存储顺序。
2. 键不可重复:HashMap中的键是唯一的(重写equals方法保证的),如果插入一个已经存在的键,则会覆盖原有的值。
3. 线程不安全:HashMap是非线程安全的,如果多个线程同时访问HashMap,可能会导致数据不一致性。
4. 允许null键值:HashMap允许键值为null,但只能有一个null键。
HashMap常用于缓存、存储数据等场景。在使用HashMap时,需要注意线程安全性和哈希冲突等问题,可以通过使用ConcurrentHashMap等线程安全的Map实现来解决这些问题。
(3)部分源码
static class Node<K,V> implements Map.Entry<K,V>{ //静态内部类Node
final int hash; //哈希值由key经过哈希函数计算得到
final K key; //key和value不能用泛型类型,在Map.Etry<K,V>接口定义
V value;
Node<K,V> next; //下一个节点地址
transient Node<K,V>[] table; //哈希表
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; //默认初始化容量16,必须是2的次幂
static final float DEFAULT_LOAD_FACTOR = 0.75f; //默认加载因子
static final int TREEIFY_THRESH = 8; //单链表元素超过8个,则转变为红黑树
static final int UNTREEIFY_THRESHOLD = 6; //红黑树节点数量小于6时,会重新变为单链表
Node(int hash, K Key, V value, Node<K,V> next){
this.hash = hash;
this.key = key;
this.value = value;
}
}
默认初始容量16,为了让元素散列均匀,提高HashMap的存取效率。
默认加载因子0.75,数组容量达到3/4时,开始扩容。
单链表转变为红黑树,是因为二叉树的检索会再次缩小扫描范围,提高效率。
(4)hasCode()和equals()方法
hascode()方法用于返回对象的哈希码值,哈希码值是根据对象的内存地址计算得出的一个整数,可以用于在哈希表等数据结构中进行快速查找。
equals()方法比较的是两个对象的引用是否相等,即比较两个对象是否指向同一个内存地址。
在将对象放入HashMap中作为key时,需要同时重写hashCode()方法和equals()方法。这是因为HashMap内部使用哈希表来存储键值对,而哈希表是根据对象的hashCode值来确定存储位置的。如果两个对象的hashCode()方法返回值相同,那么HashMap会调用equals()方法来进一步比较这两个对象是否相等。因此,为了确保HashMap能够正确地存储和检索对象,需要重写hashCode()方法和equals()方法来保证对象的唯一性和相等性。
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int hashCode() {
int result = 17; // 初始值
result = 31 * result + name.hashCode(); // 使用name的哈希码
result = 31 * result + age; // 使用age
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Person person = (Person) obj;
if (age != person.age) {
return false;
}
return name.equals(person.name);
}
}
在hasCode()方法中,使用了一种常见的计算哈希码的方法。首先选择一个初始值(通常选择一个质数,比如17),然后使用31这个质数乘以当前结果并加上属性的哈希码,这种方法可以帮助减少哈希冲突的可能性。
在equals()方法中,首先判断是同一个对象,如果是直接返回true;然后判断传入的对象是否为空或者是否是Person类的实例,不是则返回false;接着将传入的对象转换为Person类的实例;最后比较两个对象的name和age是否相等,如果相等则返回true,否则返回false。这样可以确在比较两个Person对象是否等时,只有name和age都相等时才返回。
(5)put()方法原理
put()方法用于将指定的键值对添加到HashMap中。
1.先将key,value封装到Node对象中。
2.底层会调用key的hashCode()方法得出hash值。
3.通过哈希函数,将hash值转换为数组的下标。
如果下标位置上没有任何元素,就把Node添加到这个位置上。
如果下标位置上有元素且是单链表,此时会将当前Node中的key与链表上每一个节点中的key进行equals比较。
如果所有的equals方法返回都是false,那么这个新节点Node将被添加到链表的末尾。
如果其中有一个equals返回了true,那么链表中对应的这个节点的value将会被新节点的value覆盖。如果键是null,则会将该键值对添加到HashMap中。如果键值为null,则新值将替换旧值,并且返回null。如果键不存在,则返回null。(key和value只能有一个为null。)
HashMap<String, Integer> map = new HashMap<>(); map.put("A", 1); map.put("B", 2); map.put("C", 3); System.out.println(map); // 输出: {A=1, B=2, C=3} map.put("B", 5); System.out.println(map); // 输出: {A=1, B=5, C=3} map.put("D", 4); System.out.println(map); // 输出: {A=1, B=5, C=3, D=4}
(6)get()方法原理
get()方法是用来获取HashMap中指定键所对的值。它接收一个键为参数,然后返回与该键关联的值。
1.先调用key的hasCode()方法得出hash值。
2.通过哈希函数,将hash值转换为数组的下标。
3.通过数组下标快速定位到数组中的某个位置。
如果这个位置上什么都没有,返回null。
如果这个位置上有单链表,此时会将当前Node中的key与链表上每一个节点中的key进行equals比较。如果所有的equals方法返回都是false,那么返回null。如果其中有一个返回了true,此时返回这个节点的value。
HashMap, Integer> hashMap = new HashMap<>(); hashMap.put("apple", 10); hashMap("banana", 20); System.out.println(hashMap.get("apple")); // 输出:10 System.out.println(hashMap.get("orange")); // 输出:null
e) TreeMap
(1)概念
TreeMap是基于红黑树(平衡二叉树)实现的有序映射集合。它可以自动对String类型或8大基本类型的包装类型,根据键的自然顺序或者自定义比较器对键进行排序。TreeMap中键值对是按照键的顺序进行排序的,因此它是有序的。
若将自定义类型添加到TreeSet中,key会报错 java.lang.ClassException
因为自定义类型没有实现java.lang.Comparable接口(此时使用的是TreeSet的无参构造器,无法直接对自定义类型进行排序)。
(2)特点
1. 键值是按照键的自然顺序或者自定义的比较器进行排序的。
2. TreeMap是基于红黑树实现的,因此插入、删除和查操作的时间复杂度是O(logn)。
3. TreeMap不允许键为null,但允许值为null。
4. TreeMap是非同步的,不是线程安全的,需要在多线程环境下使用,需要手动进行步操作。
(3)排序方法
第一类方法:(当比较规则不会发生改变的时候,或者说比较规则只有一个的时候)使用在集合中的自定义类型实现 Comparable接口,并重写compareTo方法。
public class Person implements Comparable<Person>{
int age;
public Person(int age){
this.age = age;
}
@Override
public int compareTo(Person o){ //compareTo方法比较两个Person对象的年龄大小,
return this.age - o.agr;
//如果当前对象的年龄大于传入对象的年龄,则返回一个正数。
//如果当前对象的年龄小于传入对象的年龄,则返回一个负数。
//如果两个对象的年龄相等,则返回0。
}
}
public static void main(String[] args){
Person person1 = new Person(25);
Person person2 = new Person(30);
if(person1.compareTo(person2) < 0){
System.out.println("person1比person2小。");
}
else if(person1.compareTo(person2) > 0){
System.out.println("person1比person2大。");
}
else {
System.out.println("person1和person2一样大。");
}
}
第二类方法:(当比较规则有多个,且需要在多个比较规则之间频繁切换的时候)选择TreeSet / TreeMap自带的比较器参数的构造器,重写compare方法,自定义排序规则。
在传递比较器参数给TreeSet / TreeMap构造器时,有3种方法:
1.定义一个Comparator接口的实现类。
2.使用匿名内部类。
3.lambda表达式(“->”的lambda表达式,或者Comparator.comparing方法)。
import java.util.Comparator;
//创建一个Comparator比较器类:
public class CustomComparator implements Comparator<Integer> {
@Override
public int compare(Integer o1, Integer o2) {
// 自定义排序规则,比较大小
return o1 - o2;
}
}
//方法一:实例化TreeSet,传入该自定义比较器类
Set<Integer> treeSet = new TreeSet<>(new CustomComparator());
//方法二:使用匿名内部类
Set<Integer> treeSet = new TreeSet<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
// 自定义排序规则,比较大小
return o1 - o2;
}
});
//方法三:使用lambda表达式,传递一个比较器对象
Set<Integer> treeSet = new TreeSet<>((o1, o2) -> o1.age - o2.age);
感谢您的观看,希望你今天特别特别开心,加油~