0
点赞
收藏
分享

微信扫一扫

java集合类学习笔记

1kesou 2022-04-04 阅读 67
javalist

不管哪一种数据结构本质上来说都是一种容器, 都需要搞清楚两点

(1)如何储存

(2)储存特点

数组

数组的逻辑结构是线性的, 属于顺序的存储结构, 一旦申请到内存那么内存就固定了

在数组这个存储结构中所有的数据都是紧密分布的, 中间不能有间隔

查询方式:

int[] numbers = new int[length];
for(int i = 0; i < numbers.length; i++){ //数组的下表从0开始
    //TODO 对数组的操作
}
  • 优点

    • 按照索引查询, 查询效率高
  • 缺点

    • 添加/删除效率低, 删除和添加都要设计移动数组中的元素

集合

  1. java集合类库将接口(interface)与实现(implementation)分离.

  2. 集合有两个基本接口: Collection 和 Map

Collection

在java集合中,最基本的就是Collection集合

有如下方法:

boolean add(Object o); //添加元素
addAll(Collection other); //添加另一个集合中的所有元素, 结果为两个集合的并集 
boolean remove(Object o); //从当前集合中删除第一个找到的与Object对象相同的元素
boolean removeIf(Predicate<? super E> filter); //根据过滤器移除集合中的元素
boolean isEmpty(); //判断当前集合是否为空集合
boolean contains(Object o); //判断是否存在该元素
boolean containsAll(Collection c); //判断集合c中的元素是否在当前集合中都存在
int size(); //获取当前集合长度
boolean retainAll(Collectioj coll); //当前集合仅保留与coll集合相同的元素, 保留两个集合的交集
Object[] toArray(); //返回当前集合中所有元素的数组
 <T> T[] toArray(T[] a); //根据集合元素返回数组
Iterator<E> iterator(); //返回一个迭代器对象
Stream<E> stream(); //返回一个Stream流
Stream<E> parallelStream(); //返回一个平行Stream流
void clear(); //删除所有元素

在java 5时Collection接口继承了java.lang.Iterable接口, 因此Collection集合在java 5之后都可使用foreach模式遍历

	@Override
    default Spliterator<E> spliterator() {
        return Spliterators.spliterator(this, 0);
    }

boolean removeIf(Predicate<? super E> filter);

removeIf(filter -> filter something)

Iterator

因为Collection结构扩展了Iterator接口, 因此, 对于标准类库中的任何集合都可以使用"foreach循环"

public interface Iterable<E>{
    Iterator<T> iterator(); //for each循环可以处理任何实现了Iterable的对象, 因为其返回了Iterator迭代器对象
    ...
}

Iterator接口有四个方法

public interface Iterator<E>{
    E next(); //返回迭代的下一个元素, 若没有下一个元素的情况下进行迭代则抛出NoSuchElementException异常
    boolean hasNext(); //返回是否还有元素可以迭代
    void remove(); //从collection中移除返回的最后一个元素, 若已经调用过remove再次调用或者没有调用next就调用remve就会抛出IllegalStateException异常
    default void forEachRemaining(Consumer<? super E> action); //提供一个lambda表达式,将对迭代器的每一个元素一一调用这个lambda表达式
}

遍历的顺序取决于集合类型, 如果是ArrayList, 迭代器将按照索引从0开始, 每迭代一次, 索引值加1. 如果是访问HashSet中的元素, 则会按照一种基本上随机的顺序获得元素.

HashSet使用的是HashMap中的迭代器, HashMap中的迭代器按照key值进行消费, 所以具有一定的随机性.

public void forEachRemaining(Consumer<? super K> action) {
            int i, hi, mc;
            if (action == null) //若消费动作为空则抛出空指针异常
                throw new NullPointerException();
            HashMap<K,V> m = map;
            /**
            * Node中有四个值  
            * final int hash; 节点的hash值
            * final K key; 节点key
            * V value; 节点value
            * Node<K,V> next; 下个节点的引用
            */
            Node<K,V>[] tab = m.table; 
            if ((hi = fence) < 0) { // fence: one past last index 过去的最后一个索引
                mc = expectedModCount = m.modCount; //表被修改次数, 保证线程安全
                hi = fence = (tab == null) ? 0 : tab.length;
            }
            else
                mc = expectedModCount;
            if (tab != null && tab.length >= hi &&
                (i = index) >= 0 && (i < (index = hi) || current != null)) {
                Node<K,V> p = current;
                current = null;
                do {
                    if (p == null)
                        p = tab[i++];
                    else {
                        action.accept(p.key); //根据指针指向node值进行消费
                        p = p.next;
                    }
                } while (p != null || i < hi);
                if (m.modCount != mc)
                    throw new ConcurrentModificationException(); //如果遍历的过程中被修改, 抛出ConcurrentModificationException异常
            }
        }

ListItr extends Itr implements ListIterator<E>

ArrayList中关于ListIterator的实现(列表迭代器)

	/**
     * An optimized version of AbstractList.ListItr
     */
    private class ListItr extends Itr implements ListIterator<E> {
        ListItr(int index) {
            super(); //显示调用父类构造器
            cursor = index; //游标指向当前索引
        }
        public boolean hasPrevious() {
            return cursor != 0; //游标不为初始值0
        }
        public int nextIndex() {
            return cursor; //返回游标指向的元素索引
        }
        public int previousIndex() {
            return cursor - 1; //返回游标上一个索引
        }
        @SuppressWarnings("unchecked")
        public E previous() {
            checkForComodification(); //检查修改次数
            int i = cursor - 1; //赋值为游标前一个索引(返回的当前对象)
            if (i < 0)
                throw new NoSuchElementException(); //元素缺省, 和Iterator中没有元素的情况下调用next()抛出异常一致
            //non-private to simplify nested class access(用于简化嵌套类的访问), 代表元数组
            Object[] elementData = ArrayList.this.elementData; 
            if (i >= elementData.length)
                throw new ConcurrentModificationException(); //检查数组是否被修改过
            cursor = i; //游标向前移
            return (E) elementData[lastRet = i]; //lastRet 最后返回的数据下标
        }

        public void set(E e) {
            if (lastRet < 0) //集合做remove或add操作之后lastRet置为-1, 对于移除的元素没法进行设置, 所以抛出异常
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.set(lastRet, e); //设置返回位置的元素为e
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
       
        public void add(E e) {
            checkForComodification(); //检查集合前后一致性
            try {
                int i = cursor; //设置当前游标位置
                ArrayList.this.add(i, e); //判断容量是否够, 然后调用System.arraycopy(datas, index, data, index +1, size - index)方法,然后赋值和容量增加
                cursor = i + 1; //游标+1
                lastRet = -1; //新增元素后最后修改元素重置为1
                expectedModCount = modCount; //集合修改操作时modCount增加
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
    }

System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length);


List

list是一个有序集合. 元素会增加到容器中的特定位置.

  • 有两种访问List集合元素的方法

    • 使用迭代器Iterator访问
    • 使用一个整数索引来访问(随机访问 Random Access), get(int index);
  • List接口的实现类

    • Vector类
    • ArrayList类
    • Stack类
    • LinkedList类
default void replaceAll(UnaryOperator<E> operator); //对集合中所有元素执行此操作
default void sort(Comparator<? super E> c); //对集合中元素进行排序, 默认调用Arrays.sort
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); //查找到的最后一个该元素的位置
List<E> subList(int fromIndex, int toIndex); //集合切割
default Spliterator<E> spliterator(); //用于遍历和区分元素对象, 提供了单独和批量遍历元素的功能
ListIterator<E> listIterator(); //返回一个列表迭代器, 用来访问列表中的元素

**replaceAll(UnaryOperator operator); **

实例:

@Test
    public void unaryOperatorTest(){
        List<String> list1 = Arrays.asList("abc", "def", "ghi");
        UnaryOperator<String> operator1 = (v) -> v.substring(0,1);
        list1.replaceAll(operator1);
        list1.forEach(System.out::print);
        System.out.println();

        List<Integer> list2 = Arrays.asList(1, 2, 3);
        UnaryOperator<Integer> operator2 = (v) -> v * 2;
        list2.replaceAll(operator2);
        list2.forEach(System.out::print);
        System.out.println();
    }

输出:

adg
246

List sort(Comparator<? super E> c);

List中默认调用Arrays.sort(T[] a, Comparator<? super T> c)方法进行分类(排序), 排序完之后使用迭代器赋值给源集合

Arrays.sort(T[] a, Comparator<? super T> c);

TimSort.sort(T[] a, int lo, int hi, Comparator<? super T> c, T[] work, int workBase, int workLen);

countRunAndMakeAscending(T[] a, int lo, int hi, Comparator<? super T> c)

查找有序片段长度并返回

binarySort(T[] a, int lo, int hi, int start,Comparator<? super T> c)

使用二分查找进行排序

TimSort int minRunLength(int n)

进行分片操作

TimSort void pushRun(int runBase, int runLen)

将这个排序片段的起始位置和长度入栈

TimSort void mergeAt

对已经排好序的连续片段进行合并


LinkedList

  • LinkList是一个有序集合, 将每个对象存放在单独的链接中, 每个链接存放着下一个链接的引用

  • 在java中所有的链表都是双向链接的

  • JDK1.6之后LinkedList实现了Deque接口. 如果要使用堆栈结构的集合, 也可以考虑使用LinkedList而不是Stack

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable{
    transient int size = 0;

    /**
     * Pointer to first node.
     * Invariant: (first == null && last == null) ||
     *            (first.prev == null && first.item != null)
     */
    transient Node<E> first; //头结点

    /**
     * Pointer to last node.
     * Invariant: (first == null && last == null) ||
     *            (last.next == null && last.item != null)
     */
    transient Node<E> last; //尾节点
    public E getFirst(); //取得头结点中的元素
    public E getLast(); //取得尾结点中的元素
    public E removeFirst(); //移除头结点,头结点后移
    public E removeLast(); //移除尾结点, 尾结点前移
    public void addFirst(E e); //添加新的头结点
    public void addLast(E e); //添加新的尾结点
    public boolean add(E e); //添加一个元素, 实际上调用addLast(E e);
    Node<E> node(int index); //根据索引位置取得节点
    ListIterator<E> listIterator(int index); //返回new ListItr(int index);
    ...
}

链表中的节点

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;
        }
    }
for(int i = 0; i < list.size(); i++){
    do something with list.get(i); //这一步操作每一次都会遍历整个链表来取得i位置的节点, 属于虚假的随机访问
}

ArrayList

  • 在ArrayList上进行的操作都是非同步的也就是非线程安全的
  • ArrayList封装了一个动态再分配的数组,初始为一个空数组, 当添加第一个元素时扩容为10, 而之后调用add(或其他)方法使容量不够时就会进行扩容, ArrayList扩容增加原来的50%(Vector增加1倍)
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;

    /**
     * Default initial capacity.
     */
    private static final int DEFAULT_CAPACITY = 10; //初始容量, 若超过这个容量就会进行扩容

    /**
     * Shared empty array instance used for empty instances.
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * Shared empty array instance used for default sized empty instances. We
     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
     * first element is added.
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     */
    transient Object[] elementData; // non-private to simplify nested class access //transient 无法被持久化的变量类型

    /**
     * The size of the ArrayList (the number of elements it contains).
     *
     * @serial
     */
    private int size;
    ...
}

ArrayList的扩容机制

private void grow(int minCapacity) { //minCapacity 要求的最小容量
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1); //增加50%容量
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0) //若超出ArrayList最大长度则取Integer.MAX_VALUE
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

public static <T> T[] copyOf(T[] original, int newLength) {
        return (T[]) copyOf(original, newLength, original.getClass());
    }

Set

  • Set接口是Collection的子接口, 它没有提供额外的方法, 所以同样Set也支持foreach和Iterator.

  • 和List不同的是Set集合不允许包含相同的元素.

HashSet

  • HashSet和LinkedHashSet按hash算法来存储集合中的元素, 具有很好的存取和查找性能.
    • 这种结构无法控制出现的次序, 如果不在意元素的顺序那么这将会是一种很好的结构.
    • 散列表为每一个对象计算一个整数hash code, 散列码(hash code)是由对象实例字段得出的一个整数, 有不同数据的对象将产生不同的散列码.
    • jdk1.8中HashSet含有一个HashMap对象, 对hashSet的操作实际上都是对hashMap的操作
    • HashTable用链表数组实现, 要想查找元素在表中的位置, 要计算他的散列码然后将她的散列码和桶的总数取余, 结果为这个元素在桶中的索引
    • 若这时桶已经被填充满, 就需要先与桶中所有元素进行比较, 查看对象是否已经存在,

HashMap计算hash的方式

 /* ---------------- Static utilities -------------- */

    /**
     * Computes key.hashCode() and spreads (XORs) higher bits of hash
     * to lower.  Because the table uses power-of-two masking, sets of
     * hashes that vary only in bits above the current mask will
     * always collide. (Among known examples are sets of Float keys
     * holding consecutive whole numbers in small tables.)  So we
     * apply a transform that spreads the impact of higher bits
     * downward. There is a tradeoff between speed, utility, and
     * quality of bit-spreading. Because many common sets of hashes
     * are already reasonably distributed (so don't benefit from
     * spreading), and because we use trees to handle large sets of
     * collisions in bins, we just XOR some shifted bits in the
     * cheapest possible way to reduce systematic lossage, as well as
     * to incorporate impact of the highest bits that would otherwise
     * never be used in index calculations because of table bounds.
     */
static final int hash(Object key) {
        int h;
        //保证hashCode不变的情况下, 把hashCode和hashCode高16位进行异或运算, 这种计算方法是灵活性和实用性的一种权衡
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); 
    }

HashMap扩容:

/**
     * Initializes or doubles table size.  If null, allocates in
     * accord with initial capacity target held in field threshold.
     * Otherwise, because we are using power-of-two expansion, the
     * elements from each bin must either stay at same index, or move
     * with a power of two offset in the new table.
     *
     * @return the table
     */
    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) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // 若原来有值小于允许的最大值, 且大于16(默认桶大小), 则进行扩容扩容为2的幂数倍
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults 第一次装填数据时, 桶初始化
            newCap = DEFAULT_INITIAL_CAPACITY; // 默认桶大小为16
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); // 装填因子默认为75%
        }
        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
  • LinkedHashSet是HashSet的子类, 他在HashSet的基础上, 在节点中增加两个属性, before和after, 维护了节点前后添加功能
  • 相比于HashSet, LinkedHashSet插入性能有所不足, 而迭代访问所有元素中LinkedHashSet有较好的性能

TreeSet

  • TreeSet和HashSet十分相似, 但她是一个有序集合. 可以任意顺序将元素插入到集合中
  • TreeSet的排序使用红黑数实现, 添加元素时总会将其放到正确的排序位置上, 因此迭代器可以有序的访问每一个元素
public interface Comparator<T> {
    int compare(T o1, T o2);
    boolean equals(Object obj);
    ...
}

例子:

@Test
    public void treeTest(){
        Apple appleOne = new Apple("red", 1);
        Apple appleTwo = new Apple("blue", 2);
        Apple appleThree = new Apple("green", 3);

        Set<Apple> set = new TreeSet<>(); //使用Apple实现的比较器
        set.add(appleOne);
        set.add(appleTwo);
        set.add(appleThree);
        System.out.println(set);

        TreeSet<Apple> apples = new TreeSet<>(Comparator.comparing(Apple::getId)); //创建TreeSet时传入比较器
        apples.addAll(set);
        System.out.println(apples);
    }
    
    private class Apple implements Comparable<Apple>{

        private int id;

        private String color;

        public Apple(String _color, int _id){
            this.color = _color;
            this.id = _id;
        }

        @Override
        public int compareTo(Apple o) {
            return color.compareTo(o.getColor());
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == this) return true;
            if (obj == null) return false;
            return (((Apple)obj).getColor().equalsIgnoreCase(this.color));
        }

       //Getter ...
        //Setter ...

        @Override
        public String toString() {
            return " [color: "+ color + ", id = " + id + "] ";
        }
    }

输出:

[ [color: blue, id = 2] ,  [color: green, id = 3] ,  [color: red, id = 1] ]
[ [color: red, id = 1] ,  [color: blue, id = 2] ,  [color: green, id = 3] ]

Map

  • 映射(Map)用来存放键值对(key, value), key和value可以是任何类型的值
  • 键值对的映射是唯一的总能通过key找到唯一的value, 所以key不允许重复,而value允许重复
int size(); //取得大小
boolean isEmpty(); //判断为空
boolean containsKey(Object key); //判断是否包含key
boolean containsValue(Object value); //判断是否包含value
V put(K key, V value); //添加(key, value)映射
void putAll(Map<? extends K, ? extends V> m); //添加m中的所有映射
V get(Object key); //取得value
V remove(Object key); //移除(key, xxx)映射
Set<K> keySet(); //返回key集
Collection<V> values(); //返回value集合
Set<Map.Entry<K, V>> entrySet(); //返回键值对组成的EntrySet
default V getOrDefault(Object key, V defaultValue); //若get(key)==null返回defaultValue, 否则返回get(key)
default void forEach(BiConsumer<? super K, ? super V> action); //遍历所有元素并执行操作apply(k,v), 不对原本map产生影响
default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function); //对所有元素v=apply(k,v)然后将v返回到map中
default V putIfAbsent(K key, V value); //若get(key) == null,则put(key, value), 不会覆盖以前的key值
default boolean remove(Object key, Object value); //存在(key, value的情况下), remove(key)
default boolean replace(K key, V oldValue, V newValue); //保证存在(key, oldValue)的情况下, put(key, newValue);
default V replace(K key, V value); //若存在get(key), 则put(key, value)
default V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction); //计算新值并返回, 若不存在则创建

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;
    }

Set keySet();


HashMap

  • HashMap是线程不安全的, 允许使用null键, null值
//为空判断
public boolean isEmpty() {
        return size == 0;
}
//取值
public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
}
//包含判断
public boolean containsKey(Object key) {
        return getNode(hash(key), key) != null;
}
//移除映射
public V remove(Object key) {
        Node<K,V> e;
        return (e = removeNode(hash(key), key, null, false, true)) == null ? null : e.value;
}
//添加映射
public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
}
/* ---------------- Static utilities -------------- */

    /**
     * Computes key.hashCode() and spreads (XORs) higher bits of hash
     * to lower.  Because the table uses power-of-two masking, sets of
     * hashes that vary only in bits above the current mask will
     * always collide. (Among known examples are sets of Float keys
     * holding consecutive whole numbers in small tables.)  So we
     * apply a transform that spreads the impact of higher bits
     * downward. There is a tradeoff between speed, utility, and
     * quality of bit-spreading. Because many common sets of hashes
     * are already reasonably distributed (so don't benefit from
     * spreading), and because we use trees to handle large sets of
     * collisions in bins, we just XOR some shifted bits in the
     * cheapest possible way to reduce systematic lossage, as well as
     * to incorporate impact of the highest bits that would otherwise
     * never be used in index calculations because of table bounds.
     */
static class Node<K,V> implements Map.Entry<K,V> {
        final int hash; //计算出的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; }
        public final V getValue()      { return value; }
        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;
        }
    }

LinkedHashMap

  • 作为HashMap的子类, 由双向链表实现, 定义了迭代顺序.

HashTable

  • HashMap和HashTable都是Hash表
  • HashTable是线程安全的, 而HashMap不是, HashTable, 不允许

HashTable 添加操作分析

    private transient Entry<?,?>[] table; //HashTable中的键值对存放在各个存储桶中, 存储桶为Entry<k,v>数组

    /**
     * The table is rehashed when its size exceeds this threshold.  (The
     * value of this field is (int)(capacity * loadFactor).)
     *
     * @serial
     */
    private int threshold;

	/**
     * Maps the specified <code>key</code> to the specified
     * <code>value</code> in this hashtable. Neither the key nor the
     * value can be <code>null</code>. <p>
     *
     * The value can be retrieved by calling the <code>get</code> method
     * with a key that is equal to the original key.
     *
     * @param      key     the hashtable key
     * @param      value   the value
     * @return     the previous value of the specified key in this hashtable,
     *             or <code>null</code> if it did not have one
     * @exception  NullPointerException  if the key or value is
     *               <code>null</code>
     * @see     Object#equals(Object)
     * @see     #get(Object)
     */
    public synchronized V put(K key, V value) {
        // Make sure the value is not null
        if (value == null) { //要求value不为空
            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; //计算存储桶位置
        @SuppressWarnings("unchecked")
        Entry<K,V> entry = (Entry<K,V>)tab[index]; //取出存储桶
        for(; entry != null ; entry = entry.next) { //遍历该存储桶查找是否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;
    }

private void addEntry(int hash, K key, V value, int index) {
        modCount++;

        Entry<?,?> tab[] = table;
        if (count >= threshold) { //若存储数量达到临界值
            // Rehash the table if the threshold is exceeded
            rehash(); //则进行扩容和table的再散列

            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); //添加在存储桶头部(e==Entry<K,V> next;)
        count++; 
    }

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;
            }
        }
    }

Entry<K,V>

/**
     * Hashtable bucket collision list entry
     */
    private static class Entry<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Entry<K,V> next;

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

        @SuppressWarnings("unchecked")
        protected Object clone() {
            return new Entry<>(hash, key, value,
                                  (next==null ? null : (Entry<K,V>) next.clone()));
        }

        // Map.Entry Ops

        public K getKey() {
            return key;
        }

        public V getValue() {
            return value;
        }

        public V setValue(V value) {
            if (value == null)
                throw new NullPointerException();

            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 (key==null ? e.getKey()==null : key.equals(e.getKey())) &&
               (value==null ? e.getValue()==null : value.equals(e.getValue()));
        }

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

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

WeekHashMap

  • 这个数据结构将和垃圾回收器协同工作, 一起删除键/值对

TreeMap

  • TreeMap的排序使用红黑数实现.
  • 可以根据键的自然顺序进行排序也可以根据创建者提供的Comparator进行排序.
    private final Comparator<? super K> comparator;

    private transient Entry<K,V> root;

举报

相关推荐

0 条评论