Java集合总结

2023-11-19

Java常用集合总结

集合的整体框架

在这里插入图片描述

Collection的上层是Iterable接口,意味着Collection所有的子类都可以使用迭代器去访问元素,Collection还分为Set和List接口,Set接口下的实现子类都是不允许存在重复元素的,而List则是可以允许存在重复元素。Map这边属于双列集合(key - value),每个key对应唯一的一个value值。

1. List接口

List接口实现的子类允许元素重复,List接口下的常用方法如下:

int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
Object[] toArray();
<T> T[] toArray(T[] a);
boolean add(E e);
boolean remove(Object o);
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
boolean addAll(int index, Collection<? extends E> c);
boolean removeAll(Collection<?> c);
boolean retainAll(Collection<?> c);
default void replaceAll(UnaryOperator<E> operator) {
        Objects.requireNonNull(operator);
        final ListIterator<E> li = this.listIterator();
        while (li.hasNext()) {
            li.set(operator.apply(li.next()));
        }
    }
default void sort(Comparator<? super E> c) {
        Object[] a = this.toArray();
        Arrays.sort(a, (Comparator) c);
        ListIterator<E> i = this.listIterator();
        for (Object e : a) {
            i.next();
            i.set((E) e);
        }
    }
void clear();
boolean equals(Object o);
int hashCode();
E get(int index);
E set(int index, E element);
void add(int index, E element);
E remove(int index);
int indexOf(Object o);
int lastIndexOf(Object o);
ListIterator<E> listIterator();
ListIterator<E> listIterator(int index);
List<E> subList(int fromIndex, int toIndex);

1.1 实现子类ArrayList

ArrayList本质上就是数组,它是List接口中最常用的一个实现子类

1.1.1 ArrayList的继承关系

在这里插入图片描述

1.1.2 ArrayList中属性介绍
private static final int DEFAULT_CAPACITY = 10;	//ArrayList的初始化大小			
private static final Object[] EMPTY_ELEMENTDATA = {};//调用有参构造器且初始化大小为0时,elementData会引用
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //调用无参构造器时,elementData会引用
transient Object[] elementData;// ArrayList真正存取数据的数组(transient防止被序列化)
private int size; //ArrayList而存入元素的个数
1.1.3 ArrayList中构造器介绍
  public ArrayList(int initialCapacity) { //ArrayList的有参构造器
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
        }
    }
  public ArrayList() { //ArrayList的无参构造器
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
  public ArrayList(Collection<? extends E> c) { 
        elementData = c.toArray();
        if ((size = elementData.length) != 0) { //可以将Collection的实现类集合进行初始化
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
1.1.4 ArrayList 的添加元素方法 add()
 public boolean add(E e) { 
        ensureCapacityInternal(size + 1);  // 此方法会计算添加一个元素后的长度
        elementData[size++] = e;
        return true;
    }
1.1.4.1 add()方法中的ensureCapacityInternal()
    private void ensureCapacityInternal(int minCapacity) { 
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));//内部cal方法是计算需要的长度
    }

1.1.4.2 ensureCapacityInternal()方法中的 calculateCapacity()
private static int calculateCapacity(Object[] elementData, int minCapacity) {
      if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { //判断elementData是否是无参构造器创建的(只判断一次)
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;//返回添加元素后的长度
    }
1.1.4.3 ensureCapacityInternal()方法中的 ensureExplicitCapacity()
   private void ensureExplicitCapacity(int minCapacity) {
        modCount++;//用来记录ArrayList的修改次数
        if (minCapacity - elementData.length > 0) 
            grow(minCapacity);//ArrayList中真正的扩容方法
    }
1.1.5 ArrayList中的扩容方法 grow()
 private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1); //ArrayList的1.5倍扩容
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity); //数组元素的拷贝会调用底层native方法
    }

1.2 实现子类 Vector

与ArrayList一样,Vector的底层也是基于数组实现的,不过Vector是属于线程安全的。

1.2.1 Vector的继承关系

在这里插入图片描述

1.2.1 Vector中的属性介绍
 protected Object[] elementData;// Vector用来存取元素的数组
 protected int elementCount;// 记录Vector集合中有效的元素数量
 protected int capacityIncrement; //扩容增加量
 
1.2.2 Vector中的构造器介绍
  public Vector(int initialCapacity, int capacityIncrement) { //指定初始化容量和 capacityIncrement
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
        this.capacityIncrement = capacityIncrement;
    }
 public Vector(int initialCapacity) { //指定初始化容量和 capacityIncrement为0
        this(initialCapacity, 0);
    }
 public Vector() { //无参构造器,初始化容量默认为10
        this(10);
    }
 public Vector(Collection<? extends E> c) { //指定实现了Collection接口的子类作为参数
        elementData = c.toArray();
        elementCount = elementData.length;
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
    }
1.2.3 Vector中的扩容方法
 private void grow(int minCapacity) { //Vector的扩容方法
        int oldCapacity = elementData.length;
     	//这里需要注意,如果再初始化阶段没有指定capacityIncrement大小,默认增长是原来的两倍,否则按capacityIncrement增长
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
1.2.4 总结

Vector和ArrayList非常类似,Vector是线程安全的,默认扩容机制是2倍且可以自定义扩容容量

1.3 实现子类 LinkedList

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

1.3.1 LinkedList的继承关系

在这里插入图片描述

1.3.2 LinkedList的属性介绍
transient int size = 0;//LinkedList的长度
transient Node<E> first;//LinkedList的头结点
transient Node<E> last;//LinkedList的尾结点
1.3.3 LinkedList的内部类 node
   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;
        }
    }
1.3.4 LinkedList的构造器介绍
  public LinkedList() { //无参构造器
    }
    public LinkedList(Collection<? extends E> c) { //指定Collection的子类作为初始化
        this();//这里还是会调用无参构造器
        addAll(c);//addAll方法会把集合c的元素采用尾插法一一插入到LinkedList后面
    }
1.3.5 LinkedList中头插和尾插方法
 private void linkFirst(E e) { //头插法
        final Node<E> f = first;
        final Node<E> newNode = new Node<>(null, e, f);
        first = newNode;
        if (f == null)
            last = newNode;
        else
            f.prev = newNode;
        size++;
        modCount++;
    }
 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++;
    }
1.3.6 LinkedList 中的remove方法
    public boolean remove(Object o) {
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) { //remove方法删除结点顺序遍历,时间复杂度O(N)
                if (x.item == null) {
                    unlink(x);
                    return true;
                }
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item)) {
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }
1.3.7总结

LinkedList是双链表结构,它的随机查询能力是要比基于数组结构的集合稍差,但是在增加和删除操作中,LinkedList是要比基于数组结构的集合效率高

2. Set接口

Set接口下实现的子类,不能存取重复的元素,Set接口下的常用方法如下:

int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
Object[] toArray();
<T> T[] toArray(T[] a);
boolean add(E e);
boolean remove(Object o);
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
boolean retainAll(Collection<?> c);
boolean removeAll(Collection<?> c);
void clear();
boolean equals(Object o);
int hashCode();

可以看出Set接口中没有带索引的方法

2.1 实现子类 HashSet

HashSet的底层本质上就是HashMap,不过HashSet只存取key值,value用来存放一个Object对象

2.1.1 HashSet的源码
//HashSet中大部分代码都是用HashMap中的代码,仅仅写了个clone和writeObject 和 readObject
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();
public HashSet() {
        map = new HashMap<>();
    }
public HashSet(Collection<? extends E> c) {
        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
        addAll(c);
    }
public HashSet(int initialCapacity, float loadFactor) {
        map = new HashMap<>(initialCapacity, loadFactor);
    }
public HashSet(int initialCapacity) {
        map = new HashMap<>(initialCapacity);
    }
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }
public Iterator<E> iterator() {
        return map.keySet().iterator();
    }
public int size() {
        return map.size();
    }
public boolean isEmpty() {
        return map.isEmpty();
    }
public boolean contains(Object o) {
        return map.containsKey(o);
    }
public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
public boolean remove(Object o) {
        return map.remove(o)==PRESENT;
    }
public void clear() {
        map.clear();
    }
    @SuppressWarnings("unchecked")
public Object clone() {
        try {
            HashSet<E> newSet = (HashSet<E>) super.clone();
            newSet.map = (HashMap<E, Object>) map.clone();
            return newSet;
        } catch (CloneNotSupportedException e) {
            throw new InternalError(e);
        }
    }
private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
        // Write out any hidden serialization magic
        s.defaultWriteObject();

        // Write out HashMap capacity and load factor
        s.writeInt(map.capacity());
        s.writeFloat(map.loadFactor());

        // Write out size
        s.writeInt(map.size());

        // Write out all elements in the proper order.
        for (E e : map.keySet())
            s.writeObject(e);
    }
private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        // Read in any hidden serialization magic
        s.defaultReadObject();

        // Read capacity and verify non-negative.
        int capacity = s.readInt();
        if (capacity < 0) {
            throw new InvalidObjectException("Illegal capacity: " +
                                             capacity);
        }

        // Read load factor and verify positive and non NaN.
        float loadFactor = s.readFloat();
        if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
            throw new InvalidObjectException("Illegal load factor: " +
                                             loadFactor);
        }

        // Read size and verify non-negative.
        int size = s.readInt();
        if (size < 0) {
            throw new InvalidObjectException("Illegal size: " +
                                             size);
        }
        // Set the capacity according to the size and load factor ensuring that
        // the HashMap is at least 25% full but clamping to maximum capacity.
        capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f),
                HashMap.MAXIMUM_CAPACITY);

        // Constructing the backing map will lazily create an array when the first element is
        // added, so check it before construction. Call HashMap.tableSizeFor to compute the
        // actual allocation size. Check Map.Entry[].class since it's the nearest public type to
        // what is actually created.

        SharedSecrets.getJavaOISAccess()
                     .checkArray(s, Map.Entry[].class, HashMap.tableSizeFor(capacity));

        // Create backing HashMap
        map = (((HashSet<?>)this) instanceof LinkedHashSet ?
               new LinkedHashMap<E,Object>(capacity, loadFactor) :
               new HashMap<E,Object>(capacity, loadFactor));

        // Read in all elements in the proper order.
        for (int i=0; i<size; i++) {
            @SuppressWarnings("unchecked")
                E e = (E) s.readObject();
            map.put(e, PRESENT);
        }
    }
2.1.2 HashSet的继承关系

在这里插入图片描述

2.1.3 HashSet中的add()方法
//见后面的HashMap中put方法

2.2 TreeSet

TreeSet的底层数据结构是红黑树,同HashSet一样,底层实际上是TreeMap,它的特点是元素具有唯一性并且是有序的,并且默认情况下是升序的,并且key值不能为空

2.1.1 TreeSet的继承关系

在这里插入图片描述

2.1.2 TreeSet属性介绍
private transient NavigableMap<E,Object> m;//暂时先放这,后面再说
private static final Object PRESENT = new Object();//每个value存取的就是PRESENT
2.1.3 TreeSet的构造器介绍
TreeSet(NavigableMap<E,Object> m) { 
        this.m = m;
}
public TreeSet() { //无参构造器
        this(new TreeMap<E,Object>());//本质上就是TreeMap
}
public TreeSet(Comparator<? super E> comparator) { //带比较器的构造器
        this(new TreeMap<>(comparator));
}
public TreeSet(Collection<? extends E> c) { //Collection的子类的构造器
        this();
        addAll(c);
}
2.1.4 TreeSet中add()方法(本质上是TreeMap中put()方法)
public boolean add(E e) {
        return m.put(e, PRESENT)==null;
}
//TreeMap中的put方法
public V put(K key, V value) {
        Entry<K,V> t = root;//TreeMap里的根节点
        if (t == null) { //根节点为空时添加方式
            compare(key, key); // type (and possibly null) check

            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp
        Entry<K,V> parent;
        // split comparator and comparable paths
        Comparator<? super K> cpr = comparator;
        if (cpr != null) { //如果比较器为null(这里会默认按升序方式存放)
            do {
                parent = t;
                cmp = cpr.compare(key, t.key);//内部自带的compare方法
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        else { //比较器不为null
            if (key == null)
                throw new NullPointerException();
                Comparable<? super K> k = (Comparable<? super K>) key;
            do {
                parent = t;
                cmp = k.compareTo(t.key);//这里compareTo方法是外部匿名内部类重写后的方法
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);//如果两个key相等相当于替换
            } while (t != null);
        }
        Entry<K,V> e = new Entry<>(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        fixAfterInsertion(e);//这个才是真正插入元素的方法(红黑树插入元素),前面只是在找位置
        size++;
        modCount++;
        return null;
    }

2.3 LinkedHashSet

LinkedhashSet作为HashSet的子类,底层本质上是LinkedHashMap,底层维护了一个数组+双链表结构,基于这个特点使得LinkedHashSet能够保证添加元素的顺序和取出的顺序一致。

2.3.1 LinkedHashSet的继承关系
image-20210707223230956
2.3.2 LinkedHashSet的源码
//由于底层是LinkedHashMap,所以源代码很少,只有以下构造器
public LinkedHashSet(int initialCapacity, float loadFactor) { //指定初始化大小和加载因子
        super(initialCapacity, loadFactor, true);
}
public LinkedHashSet(int initialCapacity) { //指定初始化大小
        super(initialCapacity, .75f, true);
}
public LinkedHashSet() { //无参构造器
        super(16, .75f, true);
}
public LinkedHashSet(Collection<? extends E> c) { // 采用Collectiion的实现子类作为参数
        super(Math.max(2*c.size(), 11), .75f, true);
        addAll(c);
}

这个后面总结LinkedHashMap会详细讲解

3. Map接口

map接口是一种键值对的接口(key-value),可以通过键来获取值。

3.1 Map的源码

int size();
boolean isEmpty();
boolean containsKey(Object key);
boolean containsValue(Object value);
V get(Object key);
V put(K key, V value);
V remove(Object key);
void putAll(Map<? extends K, ? extends V> m);
void clear();
Set<K> keySet();
Collection<V> values();
Set<Map.Entry<K, V>> entrySet();
interface Entry<K,V> {	//Map中的Entry接口
K getKey();
V getValue();
V setValue(V value);
boolean equals(Object o);
int hashCode();
public static <K extends Comparable<? super K>, V> Comparator<Map.Entry<K,V>> comparingByKey() {
            return (Comparator<Map.Entry<K, V>> & Serializable)
                (c1, c2) -> c1.getKey().compareTo(c2.getKey());
}
public static <K, V extends Comparable<? super V>> Comparator<Map.Entry<K,V>> comparingByValue() {
            return (Comparator<Map.Entry<K, V>> & Serializable)
                (c1, c2) -> c1.getValue().compareTo(c2.getValue());
}
public static <K, V> Comparator<Map.Entry<K, V>> comparingByKey(Comparator<? super K> cmp) {
            Objects.requireNonNull(cmp);
            return (Comparator<Map.Entry<K, V>> & Serializable)
                (c1, c2) -> cmp.compare(c1.getKey(), c2.getKey());
}
public static <K, V> Comparator<Map.Entry<K, V>> comparingByValue(Comparator<? super V> cmp) {
            Objects.requireNonNull(cmp);
            return (Comparator<Map.Entry<K, V>> & Serializable)
                (c1, c2) -> cmp.compare(c1.getValue(), c2.getValue());
        }
}
boolean equals(Object o);

int hashCode();

default V getOrDefault(Object key, V defaultValue) {
        V v;
        return (((v = get(key)) != null) || containsKey(key))
            ? v
            : defaultValue;
}

default void forEach(BiConsumer<? super K, ? super V> action) {
        Objects.requireNonNull(action);
        for (Map.Entry<K, V> entry : entrySet()) {
            K k;
            V v;
            try {
                k = entry.getKey();
                v = entry.getValue();
            } catch(IllegalStateException ise) {
                // this usually means the entry is no longer in the map.
                throw new ConcurrentModificationException(ise);
            }
            action.accept(k, v);
        }
}
default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
        Objects.requireNonNull(function);
        for (Map.Entry<K, V> entry : entrySet()) {
            K k;
            V v;
            try {
                k = entry.getKey();
                v = entry.getValue();
            } catch(IllegalStateException ise) {
                // this usually means the entry is no longer in the map.
                throw new ConcurrentModificationException(ise);
            }

            // ise thrown from function is not a cme.
            v = function.apply(k, v);

            try {
                entry.setValue(v);
            } catch(IllegalStateException ise) {
                // this usually means the entry is no longer in the map.
                throw new ConcurrentModificationException(ise);
            }
        }
}

default V putIfAbsent(K key, V value) {
        V v = get(key);
        if (v == null) {
            v = put(key, value);
        }

        return v;
}

default boolean remove(Object key, Object value) {
        Object curValue = get(key);
        if (!Objects.equals(curValue, value) ||
            (curValue == null && !containsKey(key))) {
            return false;
        }
        remove(key);
        return true;
}

default boolean replace(K key, V oldValue, V newValue) {
        Object curValue = get(key);
        if (!Objects.equals(curValue, oldValue) ||
            (curValue == null && !containsKey(key))) {
            return false;
        }
        put(key, newValue);
        return true;
}

default V replace(K key, V value) {
        V curValue;
        if (((curValue = get(key)) != null) || containsKey(key)) {
            curValue = put(key, value);
        }
        return curValue;
}
default V computeIfAbsent(K key,
            Function<? super K, ? extends V> mappingFunction) {
        Objects.requireNonNull(mappingFunction);
        V v;
        if ((v = get(key)) == null) {
            V newValue;
            if ((newValue = mappingFunction.apply(key)) != null) {
                put(key, newValue);
                return newValue;
            }
        }

        return v;
}
default V computeIfPresent(K key,
            BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
        Objects.requireNonNull(remappingFunction);
        V oldValue;
        if ((oldValue = get(key)) != null) {
            V newValue = remappingFunction.apply(key, oldValue);
            if (newValue != null) {
                put(key, newValue);
                return newValue;
            } else {
                remove(key);
                return null;
            }
        } else {
            return null;
        }
}
default V compute(K key,
            BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
        Objects.requireNonNull(remappingFunction);
        V oldValue = get(key);

        V newValue = remappingFunction.apply(key, oldValue);
        if (newValue == null) {
            // delete mapping
            if (oldValue != null || containsKey(key)) {
                // something to remove
                remove(key);
                return null;
            } else {
                // nothing to do. Leave things as they were.
                return null;
            }
        } else {
            // add or replace old mapping
            put(key, newValue);
            return newValue;
        }
}
default V merge(K key, V value,
            BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
        Objects.requireNonNull(remappingFunction);
        Objects.requireNonNull(value);
        V oldValue = get(key);
        V newValue = (oldValue == null) ? value :
                   remappingFunction.apply(oldValue, value);
        if(newValue == null) {
            remove(key);
        } else {
            put(key, newValue);
        }
        return newValue;
    }
}

3.2 实现子类 HashMap

HashMap可谓是Map接口中最具有代表的k-v结构,也可以说是整个集合结构中精华所在。

3.2.1 HashMap的继承关系

在这里插入图片描述

3.2.2 HashMap里面内部类的继承关系

在这里插入图片描述

首先Entry是Map接口中的一个内部接口,Node内部类是一个单链表,是HashMap在table数组中存在每个元素的单个结点,然后Entry是LinkedhashMap中的内部类,TreeNode是HashMap中一个内部类,是红黑树结构。

3.2.3 HashMap中存取数据的地方
transient Node<K,V>[] table; //注意上面内部类的继承关系,table数组中既可以存Node数组,也可以存Entry和TreeNode数据
3.2.4 HashMap中的属性介绍
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; 
//HashMap中table的初始容量
static final int MAXIMUM_CAPACITY = 1 << 30;
//table数组的最大容量
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//默认加载因子
static final int TREEIFY_THRESHOLD = 8;
// 树化条件1:table数组结点链表树化临界值(树化条件还需要table长度大于64)
static final int UNTREEIFY_THRESHOLD = 6;
// 当链表长度小于这个阈值,会进行树转成链表
static final int MIN_TREEIFY_CAPACITY = 64;
//树化条件2: table数组的结点必须大于64
transient Node<K,V>[] table;
//HashMap中存放数据的地方
transient Set<Map.Entry<K,V>> entrySet;
//entrySet 存放k-v键值对的地方
transient int size;
//记录table数组中存放的数据个数
transient int modCount;
//对map的修改次数
int threshold;
final float loadFactor;
//自定义加载因子
3.2.5 HashMap中的构造器
public HashMap(int initialCapacity, float loadFactor) { //指定初始化大小和加载因子
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
}
public HashMap(int initialCapacity) { //指定初始化大小,默认加载因子为0.75
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public HashMap() { //无参构造器
        this.loadFactor = DEFAULT_LOAD_FACTOR;
}
public HashMap(Map<? extends K, ? extends V> m) { //用map的子类进行初始化
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
}
3.2.6 HashMap中put方法
public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
}
//计算key键hash值的方法(为了让高16位也参与运算,目的是减少哈希冲突)
 static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
//HashMap中真正的put方法    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时或者长度为0,就会调用resize方法(长度默认为16)
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
     	//将key的hash值和table的数组长度进行&运算,为null就加进去
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else { //不为null的情况
            Node<K,V> e; K k;
            //判断当前table数组索引位置的hash值和新插入值的hash值是否相等,或者内容是否相等
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;//满足上述条件之一就记录下来
            //这里判断下当前p指向的结点是否是红黑树结点,若是按红黑树的方式添加
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else { //以上两个条件都不满足的话,则可判断这个Node结点一定出现形成了链表(须要遍历查找)
                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;
    }

有个问题需要注意下:为什么resize()扩容时都是以2^n次方扩容,原因在哪?

因为Hashmap计算存储位置时,使用了(n - 1) & hash。只有当容量n为2的幂次方,n-1的二进制会全为1,位运算时可以充分散列,避免不必要的哈希冲突,所以扩容必须2倍就是为了维持容量始终为2的幂次方。

比如 初始大小为16 :

二进制: 1 0 0 0 0

n-1 = 15 : 0 1 1 1 1

可以看见n-1的二进制位全1,用来充分散列,避免哈希冲突

3.2.7 HashMap中的get方法
public V get(Object key) { //get方法
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
}
//真正获取元素的方法 getNode
final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) { //判断table表和key的hash对应的索引位置是否为null
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k)))) //判断table的结点是否相等
                return first;
            if ((e = first.next) != null) { //判断first.next后面是否有结点
                if (first instanceof TreeNode)//判断是否属于红黑树结构
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do { //链表结构
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
}

3.3 HashMap中的遍历方式

由于HashMap中存在很多的内部类,而这些内部类也提供了很多的遍历方式,比如根据key,和value来遍历,借用Collection接口的迭代器。要想弄清遍历方式,就要先分析内部类中的属性。

3.3.1 keySet方式

keySet方式是根据HashMap中的所有的key值,然后可以通过key来获取对应的value值。相信大家都会有个疑问,keySet遍历方式是如何去得到HashMap的key值?下面分析下底层源码。

1.HashMap中keySet()方法
public Set<K> keySet() {
      Set<K> ks = keySet;//keySet是在AbstractMap中定义的  transient Set<K> keySet;
      if (ks == null) {
            ks = new KeySet(); //这里的构造器是KeySet类的构造器
            keySet = ks;
        }
      return ks;
}
2.HashMap中的keySet类
final class KeySet extends AbstractSet<K> {
        public final int size()                 { return size; }
        public final void clear()               { HashMap.this.clear(); }
        public final Iterator<K> iterator()     { return new KeyIterator(); }//迭代器方法
        public final boolean contains(Object o) { return containsKey(o); }
        public final boolean remove(Object key) {
            return removeNode(hash(key), key, null, false, true) != null;
        }
        public final Spliterator<K> spliterator() {
            return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);
        }
        public final void forEach(Consumer<? super K> action) {
            Node<K,V>[] tab;
            if (action == null)
                throw new NullPointerException();
            if (size > 0 && (tab = table) != null) {
                int mc = modCount;
                for (int i = 0; i < tab.length; ++i) {
                    for (Node<K,V> e = tab[i]; e != null; e = e.next)
                        action.accept(e.key);
                }
                if (modCount != mc)
                    throw new ConcurrentModificationException();
            }
        }
}

HashMap在调用keySet方式时,会调用内部类keySet中无参构造器,但是内部类keySet中无参构造器什么都没有做,其实最主要的是看上了内部类keySet中迭代器方法

public final Iterator<K> iterator() { 
    return new KeyIterator(); 
}

也就是说,keySet方法中的ks只是一个引用,再看看KeyIterator()是个什么东东

final class KeyIterator extends HashIterator
        implements Iterator<K> {
        public final K next() { return nextNode().key; }
}

层层套娃。。,keyIterator也是HashMap中的一个内部类,同样无参构造器也啥也没干,目的就是想借用next()方法,追到nextNode()中可以看到源码如下:

final Node<K,V> nextNode() {
         Node<K,V>[] t;
         Node<K,V> e = next;//next是table的拷贝
         if (modCount != expectedModCount)
              throw new ConcurrentModificationException();
         if (e == null)
              throw new NoSuchElementException();
         if ((next = (current = e).next) == null && (t = table) != null) {
              do {} while (index < t.length && (next = t[index++]) == null);
            }
          return e;
}

可以看到最终还是在table表中拿的key值。

总结:keySet方式获取key值,主要是keySet方法里的keySet属性指向了内部类KeySet,调用了重写的iterator方法,然后又在 iterator方法中调用KeyIterator里面的next()方法,最后又在next方法中调用了nextNode()方法去得到最终的key。(有亿点绕)

3.3.2 EntrySet方式

EntrySet方式和keySet方式类似:

public Set<Map.Entry<K,V>> entrySet() { //可以发现entrySet的泛型是Map接口中的内部接口Entry
        Set<Map.Entry<K,V>> es;
        return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
}

这里就不一一分析了,不过有两个点需要主要,entrySet虽然它定义时是Map.Entry,但是里面实际上存取的是Entry接口的实现子类Node,还有就是getKey和getValue都是Map.Entry接口里的方法,或者TreeNode,如下图所示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iHqfOHId-1625898247395)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210709220354393.png)]

3.3.3 Values方式

values方式是HashMap中一个方法,实现方式比较简单,它继承了Collection接口,并且得到的是整个HashMap中value值。

public Collection<V> values() {
        Collection<V> vs = values;
        if (vs == null) {
            vs = new Values();
            values = vs;
        }
        return vs;
}

最后总结下,采用以上三种遍历方式,在进行foreach循环时,本质就是调用迭代器。

3.4 LinkedHashMap

LinkedHashMap是HashMap的子类,和HashMap很相似,但是LinkedHashMap维护了一个双向链表的结构,可以使得添加的元素变得有序。

3.4.1 LinkedHashMap的继承关系

在这里插入图片描述

3.4.2 LinkedHashMap的构造器介绍
public LinkedHashMap(int initialCapacity, float loadFactor) { //指定初始化容量和加载因子
        super(initialCapacity, loadFactor);//调用父类的构造器
        accessOrder = false;
}
//其它不一一介绍了,都是调用父类的构造器进行初始化
3.4.3 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);
        }
    }

在table数组中存取的就是LinkedHashMap中Entry结点

在这里插入图片描述

总结:LinkedHashMap 就是双向链表和HashMap的结合体,实现上只是多了链表的操作,其它的地方和HashMap基本一样。

3.5 TreeMap

TreeMap的底层是红黑树结构,与HashMap不同的是,它可以自定义排序方式,即传入一个Comparator接口并重写compare方法。默认是升序。

3.5.1 TreeMap的继承关系

在这里插入图片描述

3.5.2 TreeMap的基本属性
private final Comparator<? super K> comparator;//比较器
private transient Entry<K,V> root;//TreeMap存放数据的地方
private transient int size = 0;//添加数据的数量
private transient int modCount = 0;//修改次数
3.5.3 TreeMap的构造器
public TreeMap() { //无参构造器
        comparator = null;
}
public TreeMap(Comparator<? super K> comparator) { //自定义比较器初始化方式
        this.comparator = comparator;
}
public TreeMap(Map<? extends K, ? extends V> m) { //使用Map的实现类作为初始化
        comparator = null;
        putAll(m);
}
public TreeMap(SortedMap<K, ? extends V> m) { //使用带比较器的SortedMap 进行初始化
        comparator = m.comparator();
        try {
            buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
        } catch (java.io.IOException cannotHappen) {
        } catch (ClassNotFoundException cannotHappen) {
        }
}
3.5.4 TreeMap的put方法
public V put(K key, V value) {
        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;
        }
        int cmp;
        Entry<K,V> parent;
        // split comparator and comparable paths
        Comparator<? super K> cpr = comparator;
        if (cpr != null) { //比较器不为null
            do {
                parent = t;
                cmp = cpr.compare(key, t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value); //替换value
            } while (t != null);
        }
        else { //比较器为null
            if (key == null)
                throw new NullPointerException(); //TreeMap中key不能为空
            @SuppressWarnings("unchecked")
                Comparable<? super K> k = (Comparable<? super K>) key; //默认为升序
            do {
                parent = t;
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        Entry<K,V> e = new Entry<>(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }
3.5.5 TreeMap中的Entry内部类

TreeMap中的root保存的结点都是Entry类型

static final class Entry<K,V> implements Map.Entry<K,V> {
        K key; //key
        V value;//value
        Entry<K,V> left;//左结点
        Entry<K,V> right;//右节点
        Entry<K,V> parent;//根节点
        boolean color = BLACK;

        Entry(K key, V value, Entry<K,V> parent) {
            this.key = key;
            this.value = value;
            this.parent = parent;
        }

        public K getKey() {
            return key;
        }


        public V getValue() {
            return value;
        }


        public V setValue(V value) {
            V oldValue = this.value;
            this.value = value;
            return oldValue;
        }

        public boolean equals(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry<?,?> e = (Map.Entry<?,?>)o;

            return valEquals(key,e.getKey()) && valEquals(value,e.getValue());
        }

        public int hashCode() {
            int keyHash = (key==null ? 0 : key.hashCode());
            int valueHash = (value==null ? 0 : value.hashCode());
            return keyHash ^ valueHash;
        }

        public String toString() {
            return key + "=" + value;
        }
    }

3.6 Hashtable

Hashtable是实现了Map接口并且实现了Dictionary抽象类,并且是线程安全的,key和value都不能为null(key不能为null在Dictionary规定的,value不能为空在Hashtable中规定的)。

3.6.1 Hashtable的继承关系

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gTsHCMTi-1625898247397)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210710130249953.png)]

3.6.2 Hashtable的属性
private transient Entry<?,?>[] table;//Hashtable存放数据的地方

private transient int count;//统计Hashtable中存放数据的总量

private int threshold;//扩容临界值

private float loadFactor;//加载因子

private transient int modCount = 0;//修改次数
3.6.3 Hashtable中的构造器
public Hashtable(int initialCapacity, float loadFactor) { //指定table初始容量和加载因子
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal Load: "+loadFactor);

        if (initialCapacity==0)
            initialCapacity = 1;
        this.loadFactor = loadFactor;
        table = new Entry<?,?>[initialCapacity];
        threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}
public Hashtable(int initialCapacity) { //指定table初始容量,加载因子默认0.75
        this(initialCapacity, 0.75f);
}
public Hashtable() { //无参构造器(table初始容量为11,加载因子0.75)
        this(11, 0.75f);
}
public Hashtable(Map<? extends K, ? extends V> t) { //使用Map的实现子类作为初始化
        this(Math.max(2*t.size(), 11), 0.75f);
        putAll(t);
}
3.6.4 Hashtable中的put方法
//put方法
public synchronized V put(K key, V value) {
        // Make sure the value is not null
        if (value == null) {
            throw new NullPointerException();
        }

        // Makes sure the key is not already in the hashtable.
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;//0x7FFFFFFF是int类型最大值
        @SuppressWarnings("unchecked")
        Entry<K,V> entry = (Entry<K,V>)tab[index];
        for(; entry != null ; entry = entry.next) { //判断有没有相同hash值和key值是否相同
            if ((entry.hash == hash) && entry.key.equals(key)) {
                V old = entry.value;
                entry.value = value;
                return old;
            }
        }

        addEntry(hash, key, value, index);//真正添加数据的方法
        return null;
}
//addEntry方法
private void addEntry(int hash, K key, V value, int index) {
        modCount++;

        Entry<?,?> tab[] = table;
        if (count >= threshold) { //大于等于threshold需要进行扩容再重新进行哈希计算
            // Rehash the table if the threshold is exceeded
            rehash();

            tab = table;
            hash = key.hashCode();
            index = (hash & 0x7FFFFFFF) % tab.length;
        }

        // Creates the new entry.
        @SuppressWarnings("unchecked")
        Entry<K,V> e = (Entry<K,V>) tab[index]; 
        tab[index] = new Entry<>(hash, key, value, e);//直接加入
        count++;
}
//rehash方法
protected void rehash() {
        int oldCapacity = table.length;
        Entry<?,?>[] oldMap = table;

        // overflow-conscious code
        int newCapacity = (oldCapacity << 1) + 1;
        if (newCapacity - MAX_ARRAY_SIZE > 0) {
            if (oldCapacity == MAX_ARRAY_SIZE)
                // Keep running with MAX_ARRAY_SIZE buckets
                return;
            newCapacity = MAX_ARRAY_SIZE;
        }
        Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];

        modCount++;
        threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
        table = newMap;

        for (int i = oldCapacity ; i-- > 0 ;) {
            for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
                Entry<K,V> e = old;
                old = old.next;

                int index = (e.hash & 0x7FFFFFFF) % newCapacity;
                e.next = (Entry<K,V>)newMap[index];
                newMap[index] = e;
            }
        }
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java集合总结 的相关文章

  • Keytool 应用程序在哪里?

    我需要在android中使用mapview控件 但我似乎不明白如何运行keytool 是用eclipse安装的吗 我好像找不到下载链接 Thanks keytool http docs oracle com javase 7 docs te
  • HashMap不写入数据库

    我尝试在我的数据库中写入 但只写入发件人和消息 我不明白为什么会发生这种情况 我认为问题出在我使用 sendMessage 的地方 我认为问题是我没有什么可以做的读 写其他用户的主键 我在数据库中写入消息的活动 public class M
  • 将 jar 作为 Linux 服务运行 - init.d 脚本在启动应用程序时卡住

    我目前正在致力于在 Linux VM 上实现一个可运行的 jar 作为后台服务 我已经使用了找到的例子here https gist github com shirish4you 5089019作为工作的基础 并将 start 方法修改为
  • 添加动态数量的监听器(Spring JMS)

    我需要添加多个侦听器 如中所述application properties文件 就像下面这样 InTopics Sample QUT4 Sample T05 Sample T01 Sample JT7 注意 这个数字可以多一些 也可以少一些
  • 如何在spring mvc中从控制器名称+操作名称获取映射的URL?

    是否有现有的解决方案可以从 Spring MVC3 中的 控制器名称 操作名称 获取映射的 URL 例如 asp net mvc 或 Rails 中的 UrlHelper 我觉得非常有用 thx 也许 你想要这样的东西 in your Co
  • 无法使用maven编译java项目

    我正在尝试在 java 16 0 1 上使用 maven 构建 IntelliJ 项目 但它无法编译我的项目 尽管 IntelliJ 能够成功完成 在此之前 我使用maven编译了一个java 15项目 但我决定将所有内容更新到16 0 1
  • 来自数据库的 jfreechart 散点图

    如何使用java中的jfreechart绘制mysql数据库表中数据的散点图 我使用过 Swing 库 任何链接都会有帮助 我搜索了谷歌但找不到理解的解决方案 如果您有代码 请提供给我 实际上我确实做了条形图并使用 jfreechart 绘
  • 在文本文件中搜索单词并返回其频率

    如何在包含单词文本的文本文件中搜索特定单词并返回其频率或出现次数 使用扫描仪 String text Question how to search for a particular word in a text file containin
  • Firestore - RecycleView - 图像持有者

    我不知道如何编写图像的支架 我已经设置了 2 个文本 但我不知道图像的支架应该是什么样子 你能帮我告诉我图像的文字应该是什么样子才能正确显示吗 holder artistImage setImageResource model getArt
  • 主线程如何在该线程之前运行?

    我有以下代码 public class Derived implements Runnable private int num public synchronized void setA int num try Thread sleep 1
  • 如何在android中设置多个闹钟,在这种情况下最后一个闹钟会覆盖以前的闹钟

    我正在开发一个Android应用程序 用户可以在其中设置提醒时间 但我在以下代码中遇到一个问题 即最后一个警报会覆盖之前的所有警报 MainActivity java public void setreminders DatabaseHan
  • Java:正则表达式排除空值

    在问题中here https stackoverflow com questions 51359056 java regexp for a separated group of digits 我得到了正则表达式来匹配 1 到 99 之间的一
  • RSA OAEP、Golang 加密、Java 解密 -BadPaddingException:解密错误

    我正在尝试解密使用 RSA OAEP 在 Golang 中加密的字符串 但出现 BadPaddingException 解密错误 很难弄清楚我错过了什么 这是Golang加密方法 func encryptString rootPEM io
  • Espresso 和 Proguard 的 Java.lang.NoClassDefFoundError

    我对 Espresso 不太有经验 但我终于成功地运行了它 我有一个应用程序需要通过 Proguard 缩小才能处于 56K 方法之下 该应用程序以 3 秒的动画开始 因此我需要等到该动画结束才能继续 这就是我尝试用该方法做的事情waitF
  • 无法捕获 Spring Batch 的 ItemWriter 中的异常

    我正在编写一个 Spring Batch 流程来将数据集从一个系统迁移到另一个系统 在这种情况下 这就像使用RowMapper实现在传递给查询之前从查询构建对象ItemWriter The ItemWriter称为save我的 DAO 上的
  • 解决错误javax.mail.AuthenticationFailedException

    我不熟悉java中发送邮件的这个功能 我在发送电子邮件重置密码时遇到错误 希望你能给我一个解决方案 下面是我的代码 public synchronized static boolean sendMailAdvance String emai
  • 如何在Java中正确删除数组[重复]

    这个问题在这里已经有答案了 我刚接触 Java 4 天 从我搜索过的教程来看 讲师们花费了大量精力来解释如何分配二维数组 例如 如下所示 Foo fooArray new Foo 2 3 但我还没有找到任何解释如何删除它们的信息 从内存的情
  • 挂钩 Eclipse 构建过程吗?

    我希望在 Eclipse 中按下构建按钮时能够运行一个简单的 Java 程序 目前 当我单击 构建 时 它会运行一些 JRebel 日志记录代码 我有一个程序可以解析 JRebel 日志文件并将统计信息存储在数据库中 是否可以编写一个插件或
  • 在android中跟踪FTP上传数据?

    我有一个运行 Android 的 FTP 系统 但我希望能够在上传时跟踪字节 这样我就可以在上传过程中更新进度条 安卓可以实现这个功能吗 现在 我正在使用org apache common net ftp我正在使用的代码如下 另外 我在 A
  • Android 和 Java 中绘制椭圆的区别

    在Java中由于某种原因Ellipse2D Double使用参数 height width x y 当我创建一个RectF在Android中参数是 left top right bottom 所以我对适应差异有点困惑 如果在 Java 中创

随机推荐

  • (2022 COLING)Context-Tuning情景化提示

    论文题目 Title Context Tuning Learning Contextualized Prompts for Natural Language Generation 研究问题 Question 自然语言生成 生成长文本 研究动
  • 5G+边缘计算,对于VR移动电竞游戏来说意味着什么?

    这是一个5G 边缘计算意义的问题 其实对VR游戏 特别是电竞游戏 这类大流量 低延迟的应用服务来说 大多数人第一时间想到的优点会是高达1Gbps s的数据传输速度 虽然事实确实如此 但并不是全部 从技术上讲 无线传输性能的进步能给我们带来更
  • element 可移动dialog

    import Vue from vue v dialogDrag 弹窗拖拽属性 Vue directive dialogDrag bind el binding vnode oldVnode const dialogHeaderEl el
  • ES6数组方法总结

    1 forEach let array 1 2 3 4 array forEach item index array gt console log item forEach会遍历数组 没有返回值 不允许在循环体内写return 不会改变原来
  • 小程序自定义导航栏返回主页

    小程序自定义导航栏返回主页 效果图 在app js中获取状态栏的高度statusBarHeight 自定义组件navbar wxml 自定义组件navbar wxss 自定义组件navbar json 自定义组件navbar js 调用组件
  • 睿智的目标检测60——Tensorflow2 Focal loss详解与在YoloV4当中的实现

    睿智的目标检测60 Tensorflow2 Focal loss详解与在YoloV4当中的实现 学习前言 什么是Focal Loss 一 控制正负样本的权重 二 控制容易分类和难分类样本的权重 三 两种权重控制方法合并 实现方式 学习前言
  • 如何用Stata完成(shui)一篇经济学论文(九):画线性图

    目录 普通线性图 多图并列 一图多线 什么 为什么只讲线形图 因为我只用过线形图 言归正传 我的确只用过线形图 说了跟没说一样 Stata画图给我的感觉一直都是很复杂 很多命令 我觉得好像也没有很多的地方要画图 一般就画个线形图看看趋势 如
  • 2023年,想要年赚百万必懂的道理?

    1 一个人只有经历过风雨沧桑 才会明白一个道理 这个世界最大的监狱就是人的思维 而越狱最好的方式就是人的觉醒 2 人活明白了就会知道 不要拿自己去跟别人比较 后果不是忘记了自己 就是让自己失落 3 如果一个人不向内求 总是拿自己的一点优势去
  • 机器学习可解释性

    20210508 随笔 后续有时间在对概念有了深入理解之后再进行整理 0 引言 今天不想写论文 就想起了之前关注的一个内容 机器学习的可解释性 在之前的时候 或多或少了解这个东西 发现他更多的是从特征的角度来解释 这个特征怎么影响了模型 但
  • python实现货币转换

    实现美元与人民币的转换 2022 4 16 1美元 6 37人民币 moneyStr input 请输入带有标志 RMB rmb USD usd 的钱数 if moneyStr 3 in RMB rmb dollar eval moneyS
  • [java]线程安全问题

    线程安全问题产生有五个产生原因 1 线程的随机调度和抢占式执行 就是这个机制使得线程安全问题产生 2 代码结构 多个线程对同一个变量进行修改 3 原子性 修改操作的是可拆分的 导致脏读问题 4 内存可见性问题 一个线程读一个线程写 5 指令
  • 自定义屏幕保护

    一 设计器页面及代码 Form2 Designer cs namespace 自定义屏保 partial class Form2
  • 直接执行:sudo su 就可以了。

    直接执行 sudo su 就可以了
  • GD32F405RGT6定时器固件库(所有定时器的配置(12个))

    GD32F405RGT6所有定时器的配置 GD32F4XXX系列拥有12个定时器 定时器的类型如下表 一般我们可以根据定时器的作用以及类型选取合适的定时器 在这次对GD的单片机而言我就将它所拥有的12个定时器撸了一遍 通用定时器以及高级定时
  • 虚拟ip、浮动ip

    虚拟ip 虚拟 IP 是一个虚拟的 软件定义的 IP 地址 它可以用来在网络中隐藏真实的 IP 地址 或者在多个物理服务器之间共享一个 IP 地址 虚拟 IP 通常用于网络负载均衡 高可用性和网络安全等方面 Docker 在Docker中
  • Golang在ARM/Linux平台上函数参数的传递

    一 前言 作为一名初级的嵌入式软件开发从业者 工作中大部分项目以C语言实现 使用C语言来编写代码 通常我们可以预测到编译生成的汇编 机器编码的大致情况 在不同的芯片架构上 有其相应的ABI标准 而近年来逐渐流行起来的Go语言编程 虽然同样语
  • java使用线程池批量插入mysql数据

    首先我们使用最原始的for循环插入数据 for int i 0 i lt 100000000 i service add new LongTest setStatus 1 setName NumberUtil getPwdRandom 5
  • [python爬虫] Selenium常见元素定位方法和操作的学习介绍

    这篇文章主要Selenium Python自动测试或爬虫中的常见定位方法 鼠标操作 键盘操作介绍 希望该篇基础性文章对你有所帮助 如果有错误或不足之处 请海涵 前文目录 Python爬虫 在Windows下安装PhantomJS和Caspe
  • 如何选择适合自己的STM32 微控制器?

    选择控制器型号 俗称选型 首先要搞清楚芯片型号各类参数所表示的含义 STM32 顾名思义 ST表示意法半导体 M Microelectronics的缩写 表示微控制器 32 32位的意思 表示这是一个32位的微处理器芯片 STM32自带了各
  • Java集合总结

    Java常用集合总结 集合的整体框架 Collection的上层是Iterable接口 意味着Collection所有的子类都可以使用迭代器去访问元素 Collection还分为Set和List接口 Set接口下的实现子类都是不允许存在重复