Java集合(高级)

一、Set接口

1.1 Set接口的框架

Set接口:存储无序(添加和取出的顺序不一致)的、没有索引、不可重复的数据(所以最多包含一个null)

  • LinkedHashSet:作为HashSet的子类,遍历其内部的数据时,可以按照添加的顺序遍历
  • TreeSet:底层使用红黑树存储,可以按照添加对象的指定属性进行排序
  • HashSet:
 1.作为Set接口的主要实现类
 2.线程不安全的
 3.可以存储null值,但是只能有一个null
 4.HashSet实际上是HashMap
 	new HashSet()-->
	 	public HashSet() {
	        map = new HashMap<>();
	    }
 5.HashSet不保证元素是有序的,取决于hash后,再确定索引的结果(即,不保证元素存放顺序和取出顺序一致)
 6.不能有重复元素/对象

无序性:以HashSet为例说明,无序性不等于随机性,存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值添加
不可重复性:保证添加的元素按照equals()判断时,不能返回true,即相同的元素只能添加一个

1.2 HashSet源码解读

1.执行HashSet构造器
HashSet hashSet = new HashSet();

public HashSet() {
    map = new HashMap<>();
}

2.执行add()
hashSet.add("java");

public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}
//(static)PRESENT = new Object();:与HashMap中的value相对应的虚值,占位作用

3.执行put(),该方法会执行hash(key)得到key对应的hash值,算法是h = key.hashCode()) ^ (h >>> 16),所以hash值不等价于hashCode
public V put(K key, V value) {//key = "java",value = PRESENT共享
    return putVal(hash(key), key, value, false, true);
}

4.执行putVal
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;//定义了辅助变量
    //table就是HashMap的一个数组,类型是Node[]
    //if语句表示如果当前table是null或者大小=0,就第一次扩容到16个空间
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    //(1)根据key得到的hash值,去计算该key应该存放到table表的哪个索引位置,并把这个位置的对象赋给辅助变量p
    //(2)判断p是否为空
    //(2.1)如果p为null,表示还未存放元素,就创建一个Node(key="java",value=PRESENT)
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

在这里插入图片描述

我们向HashSet中添加元素a,首先调用元素a所在类的HashCode()方法,计算元素a的哈希值,此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为索引位置),判断数组此位置是否已经有元素:

  • 如果此位置没有其他元素,则元素a直接添加成功—>情况1
  • 如果此位置存在其他元素b(或者以链表形式存在多个元素),则比较元素a与b的哈希值:如果哈希值不同,则元素a添加成功—>情况2;如果哈希值相同,进而需要调用元素a所在类的equals()方法,equals()方法返回true则表示元素a添加失败,反之添加成功—>情况3

注:不同元素的哈希值不同,但是不同的哈希值通过hash()方法得到的索引值可能相同,所以才有了情况2余情况3

对于添加成功的情况2和情况3而言:元素a与已经存在指定索引位置上的数据以链表的方式存储。

  • jdk7中,元素a指向原来的元素
  • jdk8中,原来的元素指向元素a

总结:七上八下

在Java8中,如果一条链表的元素个数为8,table为64,此时链表就会进行树化(红黑树)

1.3 案例

案例一:

使用HashSet存储字符串并遍历

package collection_.Set_;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class SetMethod {
    public static void main(String[] args) {
        //创建集合对象
        Set set = new HashSet();
        //添加元素对象
        set.add("hello");
        set.add("world");
        set.add("java");
        set.add("java");//重复
        set.add(null);
        set.add(null);

        //遍历集合对象

        //方法一:转数组
//        Object[] o = set.toArray();
//        for (int i = 0; i < o.length; i++) {
//            System.out.println(o[i]);
//        }

        //方法二:迭代器
        Iterator i = set.iterator();
        while (i.hasNext()) {
            Object o = i.next();
            System.out.println(o);
        }

        //方法三:增强for
//        for (Object o : set) {
//            System.out.println(o);
//        }
    }
}

在这里插入图片描述

  1. Set接口的实现类的对象(Set接口对象),不能存放重复元素,可以添加一个null
  2. Set接口对象存放数据是无序的(即添加的顺序和取出的顺序不一致)
  3. 取出的顺序虽然不是添加的顺序,但是它是固定的

案例二:

使用HashSet存储自定义对象并遍历

import java.util.HashSet;

class Student {
    String name;
    int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

public class Test {
    public static void main(String[] args) {
        //创建集合对象
        HashSet<Student> hashSet = new HashSet<>();
        //创建元素对象
        Student s1 = new Student("张三", 18);
        Student s2 = new Student("李四", 19);
        Student s3 = new Student("李四", 19);
        //插入元素对象
        hashSet.add(s1);
        hashSet.add(s2);
        hashSet.add(s3);
        //遍历集合元素
        for (Student s : hashSet) {
            System.out.println(s);
        }
    }
}

在这里插入图片描述

1.4 由案例二引出的面试题

1.5 LinkedHashSet的使用

LinkedHashSet作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据
优点:对于频繁的遍历操作,LinkedHashSet效率要高于HashSet
在这里插入图片描述

1.6 TreeSet自然排序(了解)

  1. 向TreeSet中添加的数据,要求是相同类的对象
  2. 两种排序方式:自然排序(实现Comparable接口)和定制排序(实现Comparator接口)
  3. 自然排序中,比较两个对象是否相同的标准为:compare To()返回0,不再是equals()
  4. 定制排序中,比较两个对象是否相同的标准为:compare()返回0,不再是equals()

1.7 HashSet和TreeSet的区别


二、Map接口

2.1 Map实现类结构

在这里插入图片描述
Map: 双列数据,存储key-value对的数据,类似于高中的函数:y=f(x)

  • HashMap: 作为Map的主要实现类,线程不安全的,效率高;可存储null的key和value
    LinkedHashMap: 保证遍历Map元素时,可以按照添加的顺序实现遍历;原因:在原有HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素;对于频繁的遍历操作,此类执行效率高于HashMap

  • TreeMap: 保证按照添加的key-value对进行排序,实现排序遍历;此时考虑key的自然排序或者定制排序;底层使用红黑树

  • Hashtable: 作为古老的实现类,线程安全的,效率低;不能存储null的key和value
    Properties: 常用来处理配置文件;key和value都是String类型

2.2 Map结构的理解

在这里插入图片描述

  • Map中的key:无序的、不可重复的,使用Set存储所有的key
  • Map中的value:无序的、可重复的,使用Collection存储所有的value
  • 一个键值对构成了一个Entry对象
  • Map中的entry:无序的、不可重复的,使用Set存储所有的entry

2.3 Map的常用功能

方法名功能
映射功能public V put(K key,V value)将key映射到value,如果key存在,则覆盖value,并将原来的value返回
获取功能public V get(K key)根据指定的key值取得相应的value值,若没有此key值,返回null
int size()返回对应关系的个数
判断功能boolean containsKey(Object key)判断指定的key是否存在
boolean containsValue(Object Value)判断指定的value是否存在
boolean isEmpty()判断是否有对应关系
删除功能void clear()清空所有的对应关系
V remove(Object key)根据指定的key删除对应关系,并返回key所对应的值,如果没有删除成功返回null
遍历功能public set<Map.Entry<K,V>> entrySet()将Map集合变为Set集合
public Set<K> KeySet()返回所有Key值集合,Key不能重复
public Collection<V> values()返回所有values值,value可以重复
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class Test {
    public static void main(String[] args) {
        //创建集合对象
        Map<String, String> map = new HashMap<String, String>();
        //添加映射关系
        map.put("IT001", "张三");
        map.put("IT002", "李四");
        map.put("IT003", "王五");
        //返回所有Key值集合
        Set<String> keys = map.keySet();
        for (String key : keys) {
            System.out.println(key);
        }
        System.out.println("------------");
        //返回所有values值
        Collection<String> values = map.values();
        for (String value : values) {
            System.out.println(value);
        }
    }
}

在这里插入图片描述

2.4 Map的第一种遍历方式(keySet()方法)

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class Test {
    public static void main(String[] args) {
        //创建集合对象
        Map<String, String> map = new HashMap<String, String>();
        //添加映射关系
        map.put("IT001", "张三");
        map.put("IT002", "李四");
        map.put("IT003", "王五");
        //遍历Map对象
            //1.找到所有的key
        Set<String> keys = map.keySet();
            //2.遍历所有的key
        for (String key : keys) {
            //3.让每个key去找其对应的value
            String value = map.get(key);
            System.out.println("[key:" + key + "  value:" + value+"]");
        }
    }
}

在这里插入图片描述

2.5 Map的第二种遍历方式(entrySet()方法)

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class Test {
    public static void main(String[] args) {
        //创建集合对象
        Map<String,String> map = new HashMap<String, String>();
        //添加映射关系
        map.put("IT001", "张三");
        map.put("IT002", "李四");
        map.put("IT003", "王五");
        //遍历Map对象
            //1.获取所有的Entry对象
        Set<Map.Entry<String,String>> entries = map.entrySet();
            //2.遍历包含所有Entry对象集合
        for (Map.Entry<String,String> entry:entries) {
            //3.获取每个单独Entry对象,通过Entry对象获取对应的Key和Value
            String key = entry.getKey();
            String value = entry.getValue();
            System.out.println("[key:" + key + "  value:" + value+"]");
        }
    }
}

2.6 使用HashMap存储自定义对象并去重

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

class Student{
    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Student)) return false;
        Student student = (Student) o;
        return age == student.age &&
                Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

public class Test {
    public static void main(String[] args) {
        //创建HashMap对象
        HashMap<Student,String> hashMap = new HashMap<Student,String>();
        //创建Key对象
        Student s1 = new Student("张三",18);
        Student s2 = new Student("李四",20);
        Student s3 = new Student("李四",20);
        //添加映射关系
        hashMap.put(s1,"班长");
        hashMap.put(s2,"学委");
        hashMap.put(s3,"学委");
        //遍历HashMap对象
            //1.获取所有的Entry对象
        Set<Map.Entry<Student,String>> entries = hashMap.entrySet();
            //2.遍历包含所有Entry对象集合
        for (Map.Entry<Student,String> entry:entries) {
            //3.获取每个单独Entry对象,通过Entry对象获取对应的Key和Value
            Student key = entry.getKey();
            String value = entry.getValue();
            System.out.println("[key:" + key + "  value:" + value+"]");
        }
    }
}

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值