Java基础(三)

<注> Hello~ 此篇博客需要一丢丢的Java基础,更适合找工作的中国宝宝观看噢。如有错误,欢迎在评论区指出~

目录

1、Set

a) 概念

b) 使用泛型的好处

c) lambda表达式

d) HashSet

e) TreeSet

2、Map

a) 概念

b) 方法

c) 遍历方法

d) HashMap

(1)概念

(2)特点

(3)部分源码

(4)hasCode()和equals()方法

(5)put()方法原理

(6)get()方法原理

e) TreeMap

(1)概念

(2)特点

(3)排序方法

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) 方法
  1. put(k key, v value): 将指定的键值对添加到Map中,若Key已经存在,则更新对应的value。
  2. get(k key): 返回指定key对应的value,如果key不存在,则返回null。
  3. boolean containsKey(k key): 判断Map中是否包含指定的键。
  4. boolean containsValue(v value): 判断Map中是否包含指定的值。
  5. remove(Object key): 删除Map中指定的key。
  6. size(): 返回Map中键值对的数量。
  7. boolean isEmpty(): 判断Map是否为空。
  8. void clear(): 清空Map中的所有键值对。
  9. Set<k>keySet(): 返回Map中所有key的集合(Set)。
  10. Collection<v>values(): 返回Map中所有value的集合(Collection)。
  11. Set<Map.Entry<k,v>>entrySet(): 返回Map中对应的Map.Entry集合(Set)。
  12. 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);

感谢您的观看,希望你今天特别特别开心,加油~

  • 20
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值