[豪の学习笔记] JavaReStudy#12

跟学视频:韩顺平Java课程

集合

1 - 集合的理解与好处

数组

长度开始时必须指定,而且一旦指定就不能更改

保存的必须为同一类型的元素

使用数组进行增加/删除元素比较麻烦

集合

可以动态保存任意多个对象

提供了一系列方便的操作对象的方法

使用集合添加/删除新元素更加简洁

2 - 集合的框架体系

Java的集合类很多,主要分为Collection和Map两大类

​ 集合主要是单列集合和双列集合

​ Collection接口有两个重要的子接口List、Set,它们的实现子类都是单列集合

​ Map接口的实现子类是双列集合,存放的是key-value

385aee3d-ccc1-4f29-aaa3-ced3571b3987

5a23a8a7-a7ce-4c69-a52b-a3587cfcb482

3 - Collection方法

​ public interface Collection extends Iterable

​ Collection实现子类可以存放多个元素,每个元素可以是Object

​ 有些Collection的实现类,可以存放重复的元素,有些不可以

​ 有些Collection的实现类是有序的(List),有些不是有序(Set)

​ Collect接口没有直接的实现子类,是通过它的子接口Set和List来实现的

常用方法

add()  //添加单个元素
remove()  //删除指定元素
contains()  //查找元素是否存在
size()  //获取元素个数
isEmpty()  //判断是否为空
clear()  //清空
addAll()  //添加多个元素
containsAll()  //查看多个元素是否都存在
removeAll()  //删除多个元素

遍历方式一 使用Iterator

​ Iterator对象称为迭代器,主要用于遍历Collection集合中的元素

​ 所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象,即可以返回一个选代器

​ Iterator仅用于遍历集合,其本身并不存放对象

Iterator的执行原理

Iterator iterator = coll.iterator();  //得到一个集合的迭代器
//hasNext(): 判断是否还有下一个元素
while(iterator.hasNext()){
    System.out.println(iterator.next());  //next()作用:1.下移2.将下移以后集合位置上的元素返回
}

​ 在调用iterator.next()方法之前必须要调用iterator.hasNext()进行检测

​ 若不调用,且下一条记录无效,直接调用iterator.next()会抛出NoSuchElementException异常

遍历方式二 增强for循环

​ 增强for循环是简化版的iterator,可以代替iterator迭代器,本质一样,只能用于遍历集合或数组

for(元素类型 元素名: 集合名或数组名){
	访问元素
}

4 - List接口和常用方法

​ List集合类中元素有序(即添加顺序和取出顺序一致)、且可重复

​ List集合中的每个元素都有其对应的顺序索引,即支持索引

​ List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素

常用方法

void add(int index, Object ele)  //在index位置插入ele元素
boolean addAll(int index, Collection eles)  //从index位置开始将eles中的所有元素添加进来
Object get(int index)  //获取指定index位置的元素
int indexOf(Object obj)  //返回obj在集合中首次出现的位置
int lastlndexOf(Object obj)  //返回obj在当前集合中末次出现的位置
Object remove(int index)  //移除指定index位置的元素,并返回此元素
Object set(int index, Object ele)  //设置指定index位置的元素为ele,相当于是替换
List subList(int fromlndex, int tolndex)  //返回从fromlndex到tolndex位置的子集合

5 - ArrayList

注意事项

​ permits all elements,including null,ArrayList可以加入null,并且可以加入多个null

​ ArrayList是由数组来实现数据存储的

​ ArrayList基本等同于Vector

​ ArrayList线程不安全,但执行效率高

底层源码

​ ArrayList中维护了一个Object类型的数组elementData

transient Object[] elementData;  //transient 瞬间、短暂的,表示该属性不会被序列化

​ 当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第一次添加,则扩容elementData为10,如需要再次扩容,则扩容elementData为1.5倍

​ 如果使用的是指定大小的构造器,则初始elementData容量为指定大小,如果需要扩容,则直接扩容elementData为1.5倍

6 - Vector

public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, Serializable

​ Vector底层也是一个对象数组,protected Object[] elementData;

​ Vector是线程同步的,即线程安全,Vector类的操作方法带有synchronized

​ 在开发中,需要线程同步安全时考虑使用Vector

​ 如果是无参构造方法,则默认10空间,如需再次扩容则按两倍扩容

​ 如果是指定大小,则每次直接按两倍扩容

7 - LinkedList

​ LinkedList底层实现了双向链表和双端队列特点

​ 可以添加任意元素,元素可重复,包括null

​ 线程不安全,未实现同步

底层操作机制

​ LinkedList底层维护了一个双向链表

​ LinkedList中维护了两个属性first和last,分别指向首节点和尾节点

​ 每个节点(Node对象),里面又维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向后一个节点,最终实现双向链表

​ LinkedList的元素的添加和删除不是通过数组完成,故相对来说效率较高

image-20250426143216327

@SuppressWarnings({"all"})
public class LinkedListCRUD {
	public static void main(String[] args) {
		LinkedList linkedList = new LinkedList();
		linkedList.add(1);
		linkedList.add(2);
		linkedList.add(3);
		System.out.println("linkedList=" + linkedList);
		//演示一个删除结点的
		linkedList.remove(); // 这里默认删除的是第一个结点
		//linkedList.remove(2);
		System.out.println("linkedList=" + linkedList);
		
         //修改某个结点对象
		linkedList.set(1, 999);
		System.out.println("linkedList=" + linkedList);

         //得到某个结点对象
		//get(1) 是得到双向链表的第二个对象
		Object o = linkedList.get(1);
		System.out.println(o);//999
		
         //因为 LinkedList 是 实现了 List 接口, 遍历方式
		System.out.println("===LinkeList 遍历迭代器====");
		Iterator iterator = linkedList.iterator();
		while (iterator.hasNext()) {
			Object next = iterator.next();
			System.out.println("next=" + next);
		}
        
		System.out.println("===LinkeList 遍历增强 for====");
		for (Object o1 : linkedList) {
			System.out.println("o1=" + o1);
		}
        
		System.out.println("===LinkeList 遍历普通 for====");
		for (int i = 0; i < linkedList.size(); i++) {
			System.out.println(linkedList.get(i));
		}
    }
}
/* 
1. LinkedList linkedList = new LinkedList();
	public LinkedList() {}
2. 这时 linkeList 的属性 first = null last = null
3. 执行 添加
	public boolean add(E e) {
		linkLast(e);
		return true;
	}
4.将新的结点,加入到双向链表的最后
	void linkLast(E e) {
		final Node<E> l = last;
		final Node<E> newNode = new Node<>(l, e, null);
		last = newNode;
		if (l == null)
			first = newNode;
		else
			l.next = newNode;
		size++;
		modCount++;
	}
*/
/*
linkedList.remove(); // 这里默认删除的是第一个结点
1. 执行 removeFirst
	public E remove() {
		return removeFirst();
	}
2. 执行
	public E removeFirst() {
		final Node<E> f = first;
		if (f == null)
			throw new NoSuchElementException();
		return unlinkFirst(f);
	}
3. 执行 unlinkFirst, 将 f 指向的双向链表的第一个结点拿掉
	private E unlinkFirst(Node<E> f) {
		// assert f == first && f != null;
		final E element = f.item;
		final Node<E> next = f.next;
		f.item = null;
		f.next = null; // help GC
		first = next;
		if (next == null)
			last = null;
		else
			next.prev = null;
		size--;
		modCount++;
		return element;
	}
*/

ArrayList和LinkedList的比较

底层结构 增删效率 改查效率
ArrayList 可变数组 较低,通过数组扩容 较高
LinkedList 双向链表 较高,通过链表追加 较低

​ 如果改查操作多,则选择ArrayList

​ 如果增删操作多,则选择LinkedList

8 - Set接口和常用方法

Set接口基本介绍

​ 无序,添加和取出的顺序不一致

​ 没有索引,故Set接口对象不能通过索引来获取

​ 不允许重复元素,故最多包含一个null

Set接口的常用方法

​ 和List接口一样,Set接口也是Collection的子接口,故常用方法和Collection接口一样

Set接口的遍历方式

​ 同Collection的遍历方式一样,可以使用迭代器或增强for,不能使用索引的方式来获取

9 - HashSet

基本介绍

​ HashSet实现了Set接口

​ HashSet实际上是HashMap

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

​ 可以存放null值,但是只能有一个null

​ HashSet不保证元素是有序的,取决于hash后,再确定索引的结果(即不保证存放元素的顺序与取出顺序一致)

​ 不能有重复的元素/对象

@SuppressWarnings({"all"})
public class HashSet01 {
	public static void main(String[] args) {
		HashSet set = new HashSet();
		//说明
		//1. 在执行 add 方法后,会返回一个 boolean 值
		//2. 如果添加成功,返回 true, 否则返回 false
		//3. 可以通过 remove 指定删除哪个对象
		System.out.println(set.add("john"));//T
		System.out.println(set.add("lucy"));//T
		System.out.println(set.add("john"));//F
		System.out.println(set.add("jack"));//T
		System.out.println(set.add("Rose"));//T
		
         set.remove("john");
		System.out.println("set=" + set);//3个

		set = new HashSet();
		System.out.println("set=" + set);//0
		
         //4. Hashset 不能添加相同的元素/数据?
		set.add("lucy");//添加成功
		set.add("lucy");//加入不了
		set.add(new Dog("tom"));//OK
		set.add(new Dog("tom"));//Ok
		System.out.println("set=" + set);

         //再加深一下. 非常经典的面试题
		//看源码,分析底层机制,看add方法到底如何实现的,机制很复杂
         set.add(new String("magic"));//ok
		set.add(new String("magic"));//加入不了
         System.out.println("set=" + set);
	}
}

class Dog {
	private String name;
	public Dog(String name) {
		this.name = name;
	}
    
	@Override
	public String toString() {
		return "Dog{" +
			"name='" + name + '\'' + '}';
	}
}

底层机制说明

​ HashSet底层是HashMap,HashMap底层是数组+链表+红黑树

​ 添加一个元素时,先得到hash值,此值会转成索引值

​ 找到存储数据表table,看这个索引位置是否已经存放了元素,如果没有则直接加入;如果有则调用equals比较,如果相同就放弃添加,如果不同就添加到最后

​ 在Java8中,如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)

image-20250426170238162

@SuppressWarnings({"all"})
public class HashSetSource {
    public static void main(String[] args) {
        HashSet hashSet = new HashSet();
        hashSet.add("java");//到此位置,第1次 add 分析完毕
        hashSet.add("php");//到此位置,第2次 add 分析完毕
        hashSet.add("java");
        System.out.println("set=" + hashSet);

        //韩老师的HashSet源码解读
        //1.执行HashSet()
        public HashSet() {
            map = new HashMap<>();
        }

        //2.执行add()
        public boolean add(E e) {//e = "java"
            return map.put(e, PRESENT)==null;//(static) PRESENT = new Object();
        }

        //3.执行put(), 该方法会执行hash(key)得到key对应的hash值 算法 h = key.hashCode()) ^ (h >>> 16)
        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 是否为 null
            //(2.1) 如果 p 为 null, 表示还没有存放元素, 就创建一个 Node (key="java",value=PRESENT)
            //(2.2) 就放在该位置 tab[i] = newNode(hash, key, value, null)
            if ((p = tab[i = (n - 1) & hash]) == null)
                tab[i] = newNode(hash, key, value, null);
            else {
                Node<K,V> e; K k;
                //如果当前索引位置对应的链表的第一个元素和准备添加的 key 的 hash 值一样并且满足下面两个条件之一:(1)准备加入的key和p指向的Node结点的key是同一个对象(2)p指向的Node 结点的key的equals()和准备加入的key比较后相同,就不能加入
                if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
                    e = p;
                //再判断p是不是一颗红黑树, 如果是就调用putTreeVal,来进行添加
                else if (p instanceof TreeNode)
                    e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
                else {
                    //如果 table 对应索引位置,已经是一个链表, 就使用 for 循环依次和该链表的每一个元素比较,若都不相同则加入到该链表的最后
                    //注意在把元素添加到链表后,立即判断该链表是否已经达到8个结点, 若达到则调用 treeifyBin()对当前这个链表进行树化(转成红黑树)
                    //注意,在转成红黑树时,要进行判断:
                    // if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64))
                    // resize();
                    //如果上面条件成立,先 table 扩容;有上面条件不成立时,才进行转成红黑树
                    //在该链表的每一个元素比较过程中,如果有相同情况,就直接break
                    for (int binCount = 0; ; ++binCount) {
                        if ((e = p.next) == null) {
                            p.next = newNode(hash, key, value, null);
                            if (binCount >= TREEIFY_THRESHOLD(8) - 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;
            //size就是我们每加入一个结点Node(k,v,h,next), size++
            if (++size > threshold)
                resize();//扩容
            afterNodeInsertion(evict);
            return null;
        }
    }
}

HashSet练习 - 重写hashcode方法

​ 定义一个Employee类,该类包含:private成员属性 name,age,当name和age的值相同时,认为是相同员工,不能添加到HashSet集合中

public class HashSetExercise{
    public static void main(String[] args){
    	HashSet hashSet = new HashSet();
        hashSet.add(new Employee("mon3ter", 18));//ok
        hashSet.add(new Employee("doctor", 66));//ok
        hashSet.add(new Employee("mon3ter", 18));//加入不成功
    }
}

class Employee{
    private String name;
    private int age;
    
    public Employee(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(){
        this.age = age;
    }
    
    @Override
    public String toString(){
        return "Employee{" +
            	"name='" + name + '\'' +
            	", age=" + age +
            	'}';
    }
    
    @Override
    public boolean equals(Object o){
        if(this == o){
            return true;
        }
        if(o == null || getClass() != o.getClass()){
            return false;
        }
        Employee employee = (Employee) o;
        return age == employee.age && Object.equals(name, employee.name);
    }
    
    @Override
    public int hashCode(){
        return Object.hash(name, age);
    }
}

10 - LinkedHashSet

基本介绍

​ LinkedHashSet是HashSet的子类

​ LinkedHashSet底层是一个LinkedHashMap,底层维护了一个数组+双向链表,区别于HashSet底层的数组+单向链表

​ LinkedHashSet根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序,这使得元素看起来是以插入顺序保存的

​ LinkedHashSet不允许添加重复元素

底层机制

image-20250428212733309

​ 在LinkedHashSet中维护了一个hash表和双向链表(LinkedHashSet有head和tail),底层维护的是一个LinkedHashMap(是HashMap的子类)

​ 每一个节点有before和after属性,这样可以形成双向链表

​ 在添加一个元素时,先求hash值再求索引,确定该元素在table的位置,然后将添加的元素加入到双向链表(如果已经存在,不添加[原则与HashSet一致])

​ 第一次添加时,直接将数组table扩容到16,存放的结点类型是LinkedHashMap$Entry

​ 数组是HashMap$Node[]存放的元素是LinkedHashMap$Entry类型,继承关系是在内部类完成

static class Entry<K,V> extends HashMap.Node<K,V>{
	Entry<K,V> before, after;
	Entry(int hash, K key, V value, Node<K,V> next) {
		super(hash, key, value, next);
    }
}

11 - Map接口和常用方法

Map接口实现类的特点(JDK8)

​ Map与Collection并列存在,用于保存具有映射关系的数据:Key-Value

​ Map中的key和value可以是任何引用类型的数据,会封装到HashMap%Node对象中

​ Map中的key不允许重复,当有重复时会覆盖原先对应value的数据

​ Map中的value可以重复

​ Map的key可以为null,value也可以为null,但key为null的情况只能有一个

​ 常用String类作为Map的key

​ key和value之间存在单向一对一关系,即通过指定的key总能找到对应的value,一对k-v是放在一个HashMap$Node中的

// k-v最后是HashMap$Node node = newNode(hash, key, value, null)
// k-v为了方便程序员的遍历,还会创建EntrySet集合,该集合存放的元素的类型是Entry,而一个Entry对象就有key和value
// EntrySet<Entry<k,v>>即transient Set<Map.Entry<k,v>> entrySet;
// entrySet中,定义的类型是Map.Entry,但是实际上存放的还是HashMap$Node,这是因为HashMap$Node implements Map.Entry
// 当把HashMap$Node对象存放到entrySet就方便我们的遍历,因为Map.Entry提供了重要的方法:K getKey(); V getValue();

Map接口常用方法

put(key, value);  //添加
remove(key);  //根据键删除映射关系
get(key);  //根据键获得值
size();  //获取当前元素个数
isEmpty();  //判断是否为空
clear();  //清楚所有K-V
containsKey(key);  //查找该键是否存在

Map接口遍历方式

1) containsKey 查找键是否存在
2) keySet 获取所有的键
3) entrySet 获取所有关系k-v
4) values 获取所有的值
@SuppressWarnings({"all"})
public class MapFor{
    public static void main(String[] args){
        Map map = new HashMap();
        map.put("key1", "value1");
        map.put("key2", "value2");
        map.put("key3", "value3");
        map.put("key4", "value4");
        map.put("key5", "value5");
        map.put("key6", "value6");

        //第一种:先取出所有的key,再通过key取出对应的value
        Set keySet = map.keySet();
        System.out.println("-----增强for-----");
        for(Object key : keySet){
            System.out.println(key + "-" + map.get(key));
        }
        System.out.println("-----迭代器-----");
        Iterator iterator = keySet.iterator();
        while(iterator.hasNext()){
            Object key = iterator.next();
            System.out.println(key + "-" + map.get(key));
        }

        //第二种:把所有的value取出来
        Collection values = map.values();
        //这里可以使用所有的Collections使用的遍历方法
        // (1)增强for  (2)迭代器

        //第三种:通过EntrySet来获取K-V
        Set entrySet = map.entrySet();
        System.out.println("-----使用EntrySet的增强for-----");
        for(Object entry : entrySet){
            //将entry转成Map.Entry
            Map.Entry m = (Map.Entry)entry;
            System.out.println(m.getKey() + "-" + m.getValue());
        }

        System.out.println("-----使用EntrySet的迭代器-----");
        Iterator iterator1 = entrySet.iterator();
        while(iterator1.hasNext()){
            Object entry = iterator1.next();
            Map.Entry m = (Map.Entry)entry;
            System.out.println(m.getKey() + "-" + m.getValue());
        }
    }
}

12 - HashMap

HashMap小结

​ Map接口的常用实现类:HashMap、Hashtable、Properties

​ HashMap是Map接口使用频率最高的实现类,它以key-value键值对的方式来存储数据(HashMap$Node类型)

​ key不能重复,但是值可以重复,允许使用null键和null值

​ 如果添加相同的key,则会覆盖原来的key-value,key不变,value被覆盖

​ 与HashSet一样,不保证映射的顺序,因为底层是以hash表的方式来存储的(JDK8的HashMap底层 数组+链表+红黑树)

​ HashMap没有实现同步,方法没有做同步互斥的操作,即没有synchronized,因此是线程不安全的,

HashMap底层机制及源码剖析

​ (key,value)是一个Node,实现了Map.Entry<K,V>

​ JDK7.0的HashMap底层实现是[数组+链表],而在JDK8.0则是[数组+链表+红黑树]

---扩容机制---

​ HashMap底层维护了Node类型的数组table,默认为null

​ 当创建对象时,将加载因子(loadfactor)初始化为0.75

​ 当添加key-value时,通过key的哈希值得到在table的索引,然后判断该索引处是否有元素。如果没有元素则直接添加;如果该索引处有元素,则继续判断该元素的key和准备加入的key是否相等。如果相等,则直接替换val;如果不相等,则需要判断是树结构还是链表结构,做出相应处理。如果添加时发现容量不够,则需要进行扩容

​ 第一次添加,则需要扩容table容量为16,临界值(threshold)为12(16*0.75),之后再扩容则需要扩容table容量为原来的2倍(32),临界值为原来的2倍(24),以此类推

​ 在Java8中,如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)

@SuppressWarnings({"all"})
public class HashMapSource {
    public static void main(String[] args){
        HashMap map = new HashMap();
        map.put("key1", "value1"); //ok
        map.put("key2", "value2"); //ok
        map.put("key1", "value3"); //替换value

        System.out.println("map=" + map);
        /*
        1.执行构造器 new HashMap(),
          初始化加载因子 loadfactor = 0.75
          HashMap$Node[] table = null
        2.执行put 调用hash方法, 计算key的hash值(h = key.hashCode()) ^ (h >>> 16)
          public V put(K key, V value){
            return putVal(hash(key), key, value, false, true);
          }
        3.执行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数组为null, 或者length = 0 , 就扩容到16
            if ((tab = table) == null || (n = tab.length) == 0)
                n = (tab = resize()).length;
            //取出hash值对应的table的索引位置的Node, 如果为null, 就直接把加入的k-v创建成一个Node, 加入该位置即可
            if ((p = tab[i = (n - 1) & hash]) == null)
                tab[i] = newNode(hash, key, value, null);
            else {
                Node<K,V> e; K k;//辅助变量
                //如果table的索引位置的key的hash和新的key的hash值相同,且满足(table现有的结点的key和准备添加的key是同一个对象 || equals 返回真),就认为不能加入新的 k-v
                if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
                    e = p;
                else if (p instanceof TreeNode)//如果当前的table的已有的Node是红黑树,就按照红黑树的方式处理
                    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);
                            //加入后,判断当前链表的个数是否已经到8个,到8个后就调用treeifyBin方法进行红黑树的转换
                            if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                                treeifyBin(tab, hash);
                                break;
                            }
                        //如果在循环比较过程中发现有相同,就break,就只是替换value
                        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; //替换key对应value
                    afterNodeAccess(e);
                    return oldValue;
                }
            }
            ++modCount;//每增加一个 Node ,就 size++
            if (++size > threshold[12-24-48]) //如size > 临界值,就扩容
                resize();
            afterNodeInsertion(evict);
            return null;
        }
		4.关于树化(转成红黑树)
        //如果table为null或者大小还没有到64,就暂时不树化,而是进行扩容,否则才会真正的树化 -> 剪枝
        final void treeifyBin(Node<K,V>[] tab, int hash) {
            int n, index; Node<K,V> e;
            if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
                resize();
        }
        */
    }
}

13 - HashTable

基本介绍

​ 存放的元素是键值对,即K-V

​ HashTable的键和值都不能为null

​ HashTable的使用方法基本上和HashMap一样

​ HashTable是线程安全的(synchronized),HashMap是线程不安全的

底层简单剖析

​ 底层有数组HashTable$Entry[],初始化大小为11

​ 扩容临界值threshold 8 = 11 * 0.75

---扩容机制---

​ 执行方法 addEntry(hash,,key,value,index); 添加K-V封装到Entry
​ 当 if(count >= threshold) 满足时,就进行扩容
​ 按照 int newCapacity = (oldCapacity << 1) + 1; 的大小扩容

14 - Properties

基本介绍

​ Properties类继承自HashTable类,并且实现了Map接口,也是使用一种键值对的形式来保存数据

​ 它的使用特点与HashTable类似

​ Properties还可用于从 xxx.properties 文件中加载数据到Properties类对象,并进行读取和修改(xxx.properties文件通常作为配置文件)

15 - 集合选型规则

​ 在开发中选择什么集合实现类,主要取决于业务操作特点,然后根据集合实现类特性进行选择

1)先判断存储的类型(一组对象或一组键值对)
2)一组对象:Collection接口
允许重复:List
	增删多:LinkedList 底层维护了一个双向链表
	改查多:ArrayList 底层维护Object类型的可变数组
不允许重复:Set
	无序:HashSet 底层是HashMap,维护了一个哈希表,即数组+链表+红黑树
	排序:TreeSet
	插入和取出顺序一致:LinkedHashSet 维护数组+双向链表
3)一组键值对:Map
	键无序:HashMap 底层是哈希表
	键排序:TreeMap
	键插入和取出顺序一致:LinkedHashMap
	读取文件:Properties

16 - TreeSet

​ 当我们使用无参构造器创建TreeSet时,仍然是无序的

​ 使用 TreeSet 提供的一个构造器,可以传入一个比较器(匿名内部类)并指定排序规则

// TreeSet treeSet = new TreeSet();
TreeSet treeSet = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
	//下面调用 String 的 compareTo 方法进行字符串大小比较
	return ((String) o1).length() - ((String) o2).length();
}
});

---源码解读---

// 1.构造器把传入的比较器对象,赋给了TreeSet的底层的TreeMap的属性 this.comparator
public TreeMap(Comparator<? super K> comparator) {
	this.comparator = comparator;
}
// 2.在调用 treeSet.add("tom"), 在底层会执行到
if (cpr != null) {//cpr 就是我们的匿名内部类(对象)
	do {
		parent = t;
        //动态绑定到我们的匿名内部类(对象)compare
        cmp = cpr.compare(key, t.key);
        if (cmp < 0)
		   t = t.left;
	    else if (cmp > 0)
            t = t.right;
        else //如果相等,即返回 0,这个 Key 就没有加入
            return t.setValue(value);
	} while (t != null);
}

17 - TreeMap

​ 使用默认的构造器创建TreeMap,是无序的(也没有排序)

​ 使用 TreeMap 提供的一个构造器,可以传入一个比较器(匿名内部类)并指定排序规则

// TreeMap treeMap = new TreeMap();
TreeMap treeMap = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
	//按照传入的 k(String) 的大小进行排序
	//按照 K(String) 的长度大小排序
	//return ((String) o2).compareTo((String) o1);
	return ((String) o2).length() - ((String) o1).length();
}
});

---源码解读---

// 1.构造器 把传入的实现了Comparator接口的匿名内部类(对象),传给 TreeMap的comparator
public TreeMap(Comparator<? super K> comparator) {
	this.comparator = comparator;
}
// 2.调用put方法
// 2.1.第一次添加, 把k-v封装到Entry对象, 放入root
Entry<K,V> t = root;
if (t == null) {
	compare(key, key); // type (and possibly null) check
	root = new Entry<>(key, value, null);
	size = 1;
	modCount++;
	return null;
}
// 2.2.以后添加
Comparator<? super K> cpr = comparator;
if (cpr != null) {
	do { //遍历所有的 key , 给当前 key 找到适当位置
		parent = t;
		cmp = cpr.compare(key, t.key);//动态绑定到我们的匿名内部类的compare
		if (cmp < 0)
			t = t.left;
		else if (cmp > 0)
			t = t.right;
		else //如果遍历过程中,发现准备添加 Key 和当前已有的 Key 相等,就不添加
            return t.setValue(value);
	} while (t != null);
}

18 - Collections方法

Collections工具类介绍

​ Collections是一个操作Set、List和Map等集合的工具类

​ Collections中提供了一系列静态的方法对集合元素进行排序、查询、修改等操作

排序操作(均为static方法)

reverse(List); //反转List中元素的顺序
shuffle(List); //对List集合元素进行随机排序
sort(List); //根据元素的自然顺序对指定List集合元素按升序排序
sort(List, Comparator); //根据指定的Comparator产生的顺序对List集合元素进行排序
swap(List, int, int); //将指定List集合中的i处元素和j处元素进行交换

查找、替换

Object max(Collection); //根据元素的自然顺序,返回给定集合中的最大元素
Object max(Collection, Comparator); //根据Comparator指定的顺序,返回给定集合中的最大元素
Object min(Collection); //根据元素的自然顺序,返回给定集合中的最小元素
Object min(Collection, Comparator); //根据Comparator指定的顺序,返回给定集合中的最小元素
int frequency(Collection, Object); //返回指定集合中指定元素的出现次数
void copy(List dest, List src); //将src中的内容复制到dest中
boolean replaceAll(List list, Object oldVal, Object newVal); //使用新值替换List对象的所有旧值
posted @ 2025-05-25 20:04  SchwarzShu  阅读(15)  评论(0)    收藏  举报