Java集合-Collection Map

news2024/11/25 19:11:19

文章目录

  • 概念
  • Iterable
  • Collection接口
    • List接口
      • ArrayList
      • Vector
      • LinkedList
      • ArrayList 和 LinkedList 比较
    • Set
      • HashSet
  • LinkedHashSet
  • Map
    • HashMap

概念

1.集合主要是两组:单列集合(Collection) , 双列集合(Map)
2.Collection 接口有两个重要的子接口 List ,Set . 他们的实现子类都是单列集合
在这里插入图片描述
3.Map 接口的实现子类 是双列集合,存放的 K-V

Iterable

单列集合的顶级接口,含有Iterator()方法,主要用于遍历Collection集合中的元素
Collection的所有实现类都有Iterator()方法,返回值为调用该方法的实现类对象
1.常用方法:

Iterator iterator = collection.iterator();//获取集合的迭代器
iterator.hasNext();//是否存在下一个元素,仅判断
iterator.next();//返回集合中的当前元素,初始指向集合中第一个元素,调用一次,指针往下挪一位
iterator.remove();//移除集合中的当前元素,初始指向集合中第一个元素,调用一次,指针往下挪一位

2.常用一下方法遍历集合
idea快捷键: itit | itco

Iterator<Integer> iterator = collection.iterator();
while (iterator.hasNext()){
   System.out.println(iterator.next());
}

遍历完后,不可在调用iterator.next(),会抛出NoSuchElementException异常
如果还想使用,在使用collection.iterator()获取一个迭代器即可

3.增强for循环就是简单版本的iterator(),底层是迭代器,集合,数组都可以使用

for (Object object: collection) {
   System.out.println(object);
}

Collection接口

Collection 接口常用方法:

collection.add(1);//添加一个元素
collection.addAll();//添加一个集合中的所有元素
collection.remove();//删除指定元素
collection.removeAll();//删除传入集合中的所有元素
collection.clear();//清空集合
collection.contains();//判断集合是否含有传入元素
collection.containsAll();//判断集合是否包含传入集合的所以有元素
collection.size();//集合大小
collection.isEmpty();//判断集合是否为空

List接口

List中的元素是有序的,存入和取出顺序一致,且元素可重复
List中所有元素都有索引(从0开始),可以根据索引取元素
1.List中的常用方法

list.get(index);//获取指定索引的元素
list.indexOf(item);//获取指定元素首次出现的索引
list.lastIndexOf(item);//获取指定元素最后出现的索引
list.add(index,item);//在index 和 index-1 之间添加一个元素
list.remove(index);//根据指定索引删除元素
list.set(index,item);//替换指定索引元素
list.subList(l,r);//返回 l<= i < r之间的元素

2.遍历List的方法
1.简单for循环
2.增强for循环
3.iterator

ArrayList

可以存放一或多个null值,list.add(null);
有数组实现数据存储
线程不安全

		ArrayList list = new ArrayList();
		//使用 for 给 list 集合添加 1-10 数据
        for (int i = 1; i <= 10; i++) {
            list.add(i);
        }
		//使用 for 给 list 集合添加 11-15 数据
        for (int i = 11; i <= 15; i++) {
            list.add(i);
        }
        list.add(100);
        list.add(200);
        list.add(null);

执行流程 && 底层源码分析:
扩容机制:

transient Object[] elementData;//内部维护的数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//空数组
private static final int DEFAULT_CAPACITY = 10;
1.
//无参构造创建
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
2.
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  //判断当前数组大小是否够用,不够进行扩容处理
        elementData[size++] = e;
        return true;
    }

3.
    private void ensureCapacityInternal(int minCapacity) {//此时minCapacity为1 ,是size+1传递过来的size初始为0
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)//判断内部数组长度大于还是小于10);
    }
4.1
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//判断维护数组是否为空数组
        	//DEFAULT_CAPACITY为10
            return Math.max(DEFAULT_CAPACITY, minCapacity);//返回10和原数组大小较大的一个
        }
        //首次长度初始化为10
        return minCapacity;
    }
4.2
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;//记录集合被修改的次数
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)//如果外部数组长度大于内部数组长度,则扩容
            grow(minCapacity);//扩容
}
5.
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;//oldCapacity 为 1
        int newCapacity = oldCapacity + (oldCapacity >> 1);//扩容为1.5倍
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;//无参构造创建,首次扩容为10
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);	
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);//将旧数组拷贝到新,如果新数组的长度超过原数组的长度,其余用默认值填充
    }
6.一步一步返回,如此循环,满了就1.5本扩容,直到MAX_ARRAY_SIZE ,如果使用有参构造传入value,那么,就按照传入的值1.5倍扩容

//如果是有参构造,就是将长度初始化了,内部数组为自定义长度的数组
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];//内部数组成了自定义长度的数组
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
        }
    }

Vector

1.Vector是线程同步的即线程安全的,所有的方法都带有synchronized
2.无参构造创建: 默认内部数组为10 ,之后2倍扩容,有参构造指定长度,按指定2倍长度扩容

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

扩容源码分析:无参
1.    
public Vector() {
   this(10);//默认内部数组初始大小
}
2.
    public Vector(int initialCapacity) {
        this(initialCapacity, 0);
    }
3.
    public Vector(int initialCapacity, int capacityIncrement) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
        this.elementData = new Object[initialCapacity];//创建初始值为10的内部数组
        this.capacityIncrement = capacityIncrement;
    }
4.第一次add
protected int elementCount;//数组中有效元素个数

    public synchronized boolean add(E e) {
        modCount++;//记录修改次数
        ensureCapacityHelper(elementCount + 1);//
        elementData[elementCount++] = e;
        return true;
    }
4.1	
    private void ensureCapacityHelper(int minCapacity) {
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)//数组有效元素长度 - 数组总长度
            grow(minCapacity);
    }
4.2由于数组初始长度为10,add 10次后进入grow方法
protected int capacityIncrement;//容器自增量,可以在创建集合是设置,为构造器第二个参数
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?capacityIncrement : oldCapacity);//默认自增量为0,所以默认扩容两倍
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

其他:
有参:
指定初始长度
    public Vector(int initialCapacity) {
        this(initialCapacity, 0);
    }
指定初始长度和每次扩容量
    public Vector(int initialCapacity, int capacityIncrement) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
        this.capacityIncrement = capacityIncrement;
    }

LinkedList

1.LinkedList底层维护一个双向链表,更具索引获取值时,索引从0开始
2.内部维护了一个静态内部类

    private static class Node<E> {
        E item;//当前节点的值
        Node<E> next;//指向下一个节点
        Node<E> prev;//指向前一个节点

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;//
            this.next = next;
            this.prev = prev;
        }
    }

简单操作:

        LinkedList<Integer> linkedList = new LinkedList<>();
        linkedList.add(1);

LinkedList初始化及CURD源码分析:

几个重要参数:
transient int size = 0;//链表长度
transient Node<E> first;//第一个结点位置
transient Node<E> last;//最后一个结点位置
protected transient int modCount = 0;//此列表在结构上被修改的次数,防止线程安全问题,如果该字段的值意外变化,迭代器(或列表迭代器)将抛出ConcurrentModificationException异常来响应next、remove、previous、set或add操作。这提供了快速失败的行为,

	//无参构造初始化
    public LinkedList() {
    }
增加:
1.  
	public boolean add(E e) {
        linkLast(e);
        return true;
    }
2.
    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++;
    }
调用静态节点类的构造方法
        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;//给当前节点赋值
            this.next = next;//增加节点的next指向下一个节点
            this.prev = prev;//prev指向上一个结点
        }  
删除:
        1.linkedList.removeFirst();//删除头节点
        调用核心方法:private E unlinkFirst(Node<E> f)
        2.linkedList.removeLast();//删除尾节点
        调用核心方法:private E unlinkLast(Node<E> l) 

上述23.linkedList.remove();//底层调用的是removeFirst
        4.linkedList.remove(new Integer(4));//删除指定对象节点
        5.linkedList.remove(3);//更具索引删
上述3个方法的核心都是在E unlink(Node<E> x)上进行修改或加以限制,
E unlink(Node<E> x) {
        // assert x != null;
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;
		//重新指定删除结点的下一个节点的prev指向
        if (prev == null) {
            first = next;
        } else {
            prev.next = next;
            x.prev = null;
        }
		//重新指定删除结点的上一个节点的next指向
        if (next == null) {
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }

        x.item = null;
        size--;
        modCount++;
        return element;
    }
查找:
		1.linkedList.get(1);//返回指定索引节点保存的数据值
        2.linkedList.getLast();
        3.linkedList.getFirst();
更改:
linkedList.set(index,value);更具指定索引,修改对应的节点数据

ArrayList 和 LinkedList 比较

查找多,用ArrayList
修改多,用LinkedList

Set

无序(添加和取出元素顺序,但取出顺序固定不变),没索引.
不允许有重复元素

遍历方法:
迭代器
增强for循环
不能用索引方式

HashSet

1.HashSet底层实际上时HashMap
2.第一次添加是内部的table数组扩容到16,再次扩容临界值是16 * 加载因子(默认0.75) 为12,下次扩容临界值是 2 乘以16 乘以0.75 为24
3.在Java8中如果一条链表的元素个数 达到 8 也就是>=7 且table数组大小大于64,就会进行树化(红黑树).否则仍采用数组扩容机制

1.HashSet底层是HashMap
2.添加元素时,先计算得到hash值会转成索引值,找到存储数据表table,若果索引位置没有元素则直接加入,若已经存放有元素则调用equals比较,为true,就放弃添加,不相同,再判断 p 是不是一颗红黑树, 如果是一颗红黑树,就调用 putTreeVal , 来进行添加.不是红黑树,依次和该链表的每一个元素比较后,都不相同, 则加入到该链表的最后.

        HashSet hashSet = new HashSet();
        hashSet.add("java");
        hashSet.add("java");
        hashSet.add("php");
源码分析:
	无参构造初始化
    public HashSet() {
        map = new HashMap<>();
    }
1.添加 "java"
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
2.
    public V put(K key, V value) {
        return putVal(hash(key)//计算哈希值, key, value, false, true);
    }
2.1
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
3.进入putVall方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        //if 语句表示如果当前 table 是 null, 或者 大小=0
        //table 就是 HashMap 的一个数组,类型是 Node[]
		//if 语句表示如果当前 table 是 null, 或者 大小=0
		//就是第一次扩容,到 16 个空间.
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;//刚开始tab为空,进入resize()方法进行第一次扩容
//(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 {
        //如果当前索引位置对应的链表的第一个元素和准备添加的 key 的 hash 值一样
		//并且满足 下面两个条件之一:
		//(1) 准备加入的 key 和 p 指向的 Node 结点的 key 是同一个对象
		//(2) p 指向的 Node 结点的 key 的 equals() 和准备加入的 key 比较后相同
		//就不能加入
            Node<K,V> e; K k;
            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 {
//(1) 依次和该链表的每一个元素比较后,都不相同, 则加入到该链表的最后
// 注意在把元素添加到链表后,立即判断 该链表是否已经达到 8 个结点
// , 就调用 treeifyBin() 对当前这个链表进行树化(转成红黑树)
// 注意,在转成红黑树时,要进行判断, 判断条件
// if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64))
// resize();
// 如果上面条件成立,先 table 扩容. // 只有上面条件不成立时,才进行转成红黑树
//(2) 依次和该链表的每一个元素比较过程中,如果有相同情况,就直接 break
                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;//返回null代表添加成功
    }

3.1 第一次扩容
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; 
 static final float DEFAULT_LOAD_FACTOR = 0.75f;
final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {//false
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold//false
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults//初始化
            newCap = DEFAULT_INITIAL_CAPACITY;//第一次扩容大小: 16,默认初始大小
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//当数据大小为0.75 * 16时再次扩容
        }
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;//赋值,正真扩容!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;//返回
    }

LinkedHashSet

1.LinkedHashSet使用hashcode值来决定元素的存储位置,使用双向链表维护元素的次序,元素以插入顺序保存.(加入和取出元素顺序一致)
2.LinkedHashSet底层是一个LinkedHashMap(是HashMap的子类),底层维护一个数组加双向链表
3.LinkedHashSet不能插入重复元素
4.第一次添加时将数组 table(table的类型是HashMap N o d e [ ] ) 扩容到 16 , 存放的结点类型是 L i n k e d H a s h m a p Node[]) 扩容到 16 ,存放的结点类型是LinkedHashmap Node[])扩容到16,存放的结点类型是LinkedHashmapEntry
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);
        }
    }

常用方法:

Map

Set 中 k-v k是添加值,v是present常量,只用了k
Map 中 k - v 都由用户添加
1.Map与Collection并列存在。用于保存具有映射关系的数据:Key-Value(双列元素)
2.Hap中的 key 和 value 可以是任何引用类型的数据,会封装到HashMap$Node对象中
3. Map中的key不允许重复,原因和HashSet一样,前面分析过源码。
4. Map中的value可以重复,如果key相同,则覆盖之前的value
5. Map 的 key 可以为 null, value 也可以为 null ,注意 key 为 null,
6. 常用 String 类作为 Map 的 key
7. key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到对应的 value

常用方法:

Map map = new HashMap();
map.put(key, value);//替换-> 一会分析源码
map.remove(key)根据键删除映射关系
map.get(key)根据键获取值
map.size();获取元素个数
map.containsKey(key)是否包含指定key
map.isEmpty();判断个数是否为空 
map.clear();清空集合

遍历:

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

//第二组: 把所有的 values 取出
Collection values = map.values();
//这里可以使用所有的 Collections 使用的遍历方法
//(1) 增强 for
System.out.println("---取出所有的 value 增强 for----");
for (Object value : values) {
System.out.println(value);
}
//(2) 迭代器
System.out.println("---取出所有的 value 迭代器----");
Iterator iterator2 = values.iterator();
while (iterator2.hasNext()) {
Object value = iterator2.next()
System.out.println(value);
}

//第三组: 通过 EntrySet 来获取 k-v
Set entrySet = map.entrySet();// EntrySet<Map.Entry<K,V>>
//(1) 增强 for
System.out.println("----使用 EntrySet 的 for 增强(第 3 种)----");
for (Object entry : entrySet) {
//将 entry 转成 Map.Entry
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + "-" + m.getValue());
}
//(2) 迭代器
System.out.println("----使用 EntrySet 的 迭代器(第 4 种)----");
Iterator iterator3 = entrySet.iterator();
while (iterator3.hasNext()) {
Object entry = iterator3.next();
//System.out.println(next.getClass());//HashMap$Node -实现-> Map.Entry (getKey,getValue)
//向下转型 Map.Entry
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + "-" + m.getValue());
}
}

HashMap

jdk8: 底层是数组+链表+红黑树
数组长度大于64 且 链表长度大于8 该节点的链表树化

  1. HashMap底层维护了Node类型的数组table,默认为null
    2)当创建对象时,将加载因子(loadfactor)初始化为0.75.
    3)当添加key-val时,通过key的哈希值得到在table的索引。然后判断该索引处是否有元素,
    如果没有元素直接添加。如果该索引处有元素,继续判断该元素的key是否和准备加入的key相等,如果相等,则直接替换val;如果不相等需要判断是树结构还是链表结构,做出相应处理。如果添加时发现容量不够,则需要扩容。
    4)第1次添加,则需要扩容table容量为16,临界值(threshold)为12.,以后再扩容,则需要扩容table容量为原来的2倍,临界值为原来的2倍,即24,依次类推.
    6)在Java8中,如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认是8),粗table的大小>= MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)

1.k-v最后是 HashMap.Node node = newNode(hash,key,value,null)
2.k-v为了方便程序员的遍历,创建了EntrySet集合,该集合存放的元素的类型Entry,而一个Entry对象就有k ,v EntrySet<Entry<K,V>>即: transient Set<Map. Entry<K, V>> entrySet;
3. entrySet中,定义的类型是Map.Entry ,但是实际上存放的还是 HashMap.Node,这是因为 HashMap.Node implements Map.Entry
4.当把 HashMap.Node 对象存放到entrySet 就方便我们的遍历,因为 Map.Entry 提供了重要方法K getKey V getValue();
5.实际上就是把所有HashMap$Node转成Entry,然后把所有Entry放到EntrySet中(存放的是地址,不是实际数据),方便程序员遍历,EntrySet中的数据实际指向的是类型为HashMap Node[] 的table表中的数据(存放的是地址,不是实际数据)

内部节点
//实现implements Map.Entry<K,V>接口
static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;

        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

        public final K getKey()        { return key; }//getKey
        public final V getValue()      { return value; }//getValue
        public final String toString() { return key + "=" + value; }

        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                if (Objects.equals(key, e.getKey()) &&
                    Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }
    }
EntrySet 中包含所有Entry,但是不能直接获取Entry对象,可以使用增强for循环遍历
final class EntrySet extends AbstractSet<Map.Entry<K,V>> {......}

所以还提供了直接获取所有key的内部类
final class KeySet extends AbstractSet<K> {.......}
以及直接获取所有Value的内部类
final class Values extends AbstractCollection<V> {......}

解析 hashMap.put(key,value)
扩容等机制和HashSet基本一样

1.
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
2.
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        
        if ((tab = table) == null || (n = tab.length) == 0)
        //第一次扩容
            n = (tab = resize()).length;
            //hash值对应的数组索引为空,直接放入
        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;
    }

细致分析
// 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) {//K = "java" value = 10
            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;
                        }
                        if (e.hash == hash && //如果在循环比较过程中,发现有相同,就 break,就只是替换 value
                                ((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;
        }
        5. 关于树化(转成红黑树)
//如果 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();
        }
*/
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/647525.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SWIG介绍和使用

官网&#xff1a;https://www.swig.org/ github&#xff1a;https://github.com/swig SWIG 是一种软件开发工具&#xff0c;可将用 C 和 C 编写的程序与各种高级编程语言连接起来。 SWIG 与不同类型的目标语言一起使用&#xff0c;包括常见的脚本语言&#xff0c;如 Javascri…

redis集群-Redis(六)

上篇文章介绍了哨兵架构以及集群搭建。 哨兵架构&redisCluster-Redis&#xff08;五&#xff09;https://blog.csdn.net/ke1ying/article/details/131178880 redisCluster 接着上篇文章继续讲解&#xff0c;前面一个linux服务器已经有了8001和8004节点&#xff0c;现在继…

八大典型的黑盒测试方法已来袭,快快接住!

前言 有了软件缺陷的暴露&#xff0c;我们就需要通过各种软件测试的方法来查找出软件的漏洞&#xff0c;编写出测试用例&#xff0c;及时修改bug。 在下面的这篇文章中&#xff0c;我们将谈论八大典型的黑盒测试方法&#xff0c;一起来学习⑧&#x1f4a1; 一、等价类划分法…

【Python实战】Python采集王者皮肤图片

前言 我们上一篇介绍了&#xff0c;如何采集王者最低战力&#xff0c;本文就来给大家介绍如何采集王者皮肤&#xff0c;买不起皮肤&#xff0c;当个桌面壁纸挺好的。下面&#xff0c;我和大家介绍如何获取数据。 环境使用 python 3.9pycharm 模块使用 requests 模块介绍 re…

c++返回值使用引用的注意事项

c返回值使用引用的注意事项 问题原因返回函数的引用形参另外一个问题解决方法 问题 引用变量b接受到的值为无意义的数字。 原因 int& b returnYingyong();相当于&#xff1a; int i2 3; int& b i2; 销毁 i2;将局部变量作为返回值&#xff0c;导致引用b数据无意义…

【LInux】进程间通信(共享内存)

文章目录 system V共享内存shmget&#xff08;创建共享内存&#xff09;ftok&#xff08;key&#xff09;ipcs指令shmctl&#xff08;销毁共享内存&#xff09;shmat代码 共享内存 system V共享内存 共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间&am…

新能源突围,需要发力被忽视的中端品牌战场

作者 |张祥威 编辑 |德新 今年2月&#xff0c;受特斯拉等竞品降价影响&#xff0c;深蓝SL03销量下滑至4000台。深蓝的市场部上报长安中国&#xff0c;十天后开始降价&#xff0c;3月SL03销量重回正轨。 在巨头和新势力之间灵活腾挪&#xff0c;深蓝向外界展示了爆款制造的素质…

网络安全知识点梳理(期末不挂科)

网络安全复习知识点太多梳理不清晰&#xff1f;&#x1f44f;&#x1f3fb;&#x1f44f;&#x1f3fb;&#x1f44f;&#x1f3fb; 本文一篇帮你梳理清晰&#xff0c;内容覆盖整个大学网络安全知识点&#x1f44c;&#x1f3fb; 干货满满不看后悔&#x1f44d;&#x1f44…

leetcode 309.最佳买卖股票时机含冷冻期

题目描述 给定一个整数数组prices&#xff0c;其中第 prices[i] 表示第 i 天的股票价格 。​ 设计一个算法计算出最大利润。在满足以下约束条件下&#xff0c;你可以尽可能地完成更多的交易&#xff08;多次买卖一支股票&#xff09;: 卖出股票后&#xff0c;你无法在第二天买入…

基于深度学习的高精度足球检测识别系统(PyTorch+Pyside6+YOLOv5模型)

摘要&#xff1a;基于深度学习的高精度足球检测识别系统可用于日常生活中或野外来检测与定位足球目标&#xff0c;利用深度学习算法可实现图片、视频、摄像头等方式的足球目标检测识别&#xff0c;另外支持结果可视化与图片或视频检测结果的导出。本系统采用YOLOv5目标检测模型…

【Java基础学习打卡09】JRE与JDK

目录 引言一、JRE二、JDK三、JDK、JRE和JVM关系总结 引言 本文将介绍JRE、JDK是什么&#xff0c;以及JDK、JRE和JVM关系三者之间的关系。 一、JRE JRE全称为Java Runtime Environment&#xff0c;是Java应用程序的运行时环境。JRE包括Java虚拟机&#xff08;JVM&#xff09;、…

【数据结构】二叉树经典题目

1. 二叉树创建字符串 相信大部分人看了题目描述之后&#xff0c;都会和我一样一脸的懵逼。直到我看到了一个描述才恍然大悟 分为3种情况&#xff1a; 左右都为空 --省略右为空&#xff0c;左不为空 – 省略左为空&#xff0c;右不为空–不省略 这里复习一下二叉树的前序遍历…

Linux基本指令(四)

文章目录 一、新建(adduser)与删除(userdel)普通用户二、date指令三、find指令四、grep指令 一、新建(adduser)与删除(userdel)普通用户 前面我Linux登录时都是以root的身份登录的&#xff0c;从现在开始以普同用户登录&#xff0c;那么普通用户哪里来&#xff0c;是由root用户…

泰坦陨落2找不到msvcr120.dll的解决方法

msvcr120.dll是的动态链接库文件之一。它在Windows操作系统中发挥着重要的作用&#xff0c;它提供了应用程序所需的各种功能和方法。 该文件返回编译后的代码所需的支持库。msvcr120.dll包含用于C / C编译器生成的应用程序所需的重要功能&#xff0c;包括数学函数&#xff0c;…

第六节 容器(列表)

文章目录 列表掌握知识点1.1 概述1.1.1 语法格式 1.2 列表的循环遍历1.2.1 for 循环遍历列表1.2.2 while 循环遍历列表 1.3 列表的常见操作1.3.1 列表增加元素1.3.2 列表删除元素1.3.3 列表查询元素 1.4 列表的排序1.5 列表的嵌套1.6 练习 随机分班1.7 练习 判断字符结尾1.8 扩…

华为OD机试真题 JavaScript 实现【最长子字符串的长度】【2022Q4 100分】,附详细解题思路

一、题目描述 给你一个字符串s&#xff0c;字符串s首尾相连组成一个环形&#xff0c;请你在环形中找出‘o’字符出现了偶数次最长子字符串的长度。 二、输入描述 输入一串小写字母组成的字符串。 三、输出描述 输出一个整数。 四、解题思路 题目要求在给定的环形字符串中…

HTML中嵌入JavaScript代码的三种方式

第一种方式: <!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title>HTML中嵌入JavaScript的第一种方式</title> </head> <body> <!-- 1.要实现的功能…

Unix/Linux操作系统的最强入门科普(经典)

今天这篇文章&#xff0c;我们来聊聊操作系统&#xff08;Operating System&#xff09;。 说到操作系统&#xff0c;大家都不会陌生。我们天天都在接触操作系统——用台式机或笔记本电脑&#xff0c;使用的是windows和macOS系统&#xff1b;用手机、平板电脑&#xff0c;则是…

深度学习入门——神经网络

神经网络 神经网络是一种受到人脑神经系统启发的机器学习模型。它由一系列相互连接的人工神经元组成&#xff0c;这些神经元以层次结构排列。每个神经元接收来自上一层神经元的输入&#xff0c;并根据权重和激活函数对输入进行加权处理&#xff0c;然后将输出传递给下一层神经…

【框架源码】Spring源码解析之Bean创建源码流程

问题&#xff1a;Spring中是如何初始化单例bean的&#xff1f; 我们都知道Spring解析xml文件描述成BeanDefinition&#xff0c;解析BeanDefinition最后创建Bean将Bean放入单例池中&#xff0c;那么Spring在创建Bean的这个过程都做了什么。 Spring核心方法refresh()中最最重要…