Collection土办法丶

集合框架

Java 集合框架主要包括两种类型的容器,一种是集合(Collection),存储一个元素集合,另一种是图(Map),存储键/值对映射。

img

大概情况就是:各种集合类继承各种抽象类,实现各种接口;各种数据结构进行存储;各种算法进行排序和搜索。

集合接口

接口 关键词丶印象
List 允许有相同的元素。List 接口存储一组不唯一有序(插入顺序)的对象。List和数组类似可以动态增长查找元素效率高插入删除效率低,因为会引起其他元素位置改变 <实现类有ArrayList,LinkedList,Vector> 。
Set Set 不保存重复的元素。Set 接口存储一组唯一无序的对象。Set检索效率低下,删除和插入效率高插入和删除不会引起元素位置改变 <实现类有HashSet,TreeSet>
SortedSet 继承于Set保存有序的集合
Map Map 接口存储一组键值对象,提供key(键)到value(值)的映射。
Map.Entry 描述在一个Map中的一个元素(键/值对)。是一个Map的内部类
SortedMap 继承于 Map,使 Key 保持在升序排列

集合实现类

一些是抽象类,提供了接口的部分实现。

  • AbstractCollection 实现了大部分的集合接口。
  • AbstractList 继承于AbstractCollection 并且实现了大部分List接口。
  • AbstractSequentialList 继承于 AbstractList ,提供了对数据元素的链式访问而不是随机访问。
  • LinkedList 该类实现了List接口,允许有null(空)元素。主要用于创建链表数据结构,该类没有同步方法,如果多个线程同时访问一个List,则必须自己实现访问同步,解决方法就是在创建List时候构造一个同步的List。例如:List list=Collections.synchronizedList(newLinkedList(...));LinkedList 查找效率低
  • ArrayList 该类也是实现了List的接口,实现了可变大小的数组随机访问和遍历元素时,提供更好的性能。该类也是非同步的,在多线程的情况下不要使用。ArrayList 增长当前长度的50%插入删除效率低
  • AbstractSet 继承于AbstractCollection 并且实现了大部分Set接口。
  • HashSet该类实现了Set接口,不允许出现重复元素不保证集合中元素的顺序允许包含值为null的元素但最多只能一个
  • LinkedHashSet 具有可预知迭代顺序的 Set 接口的哈希表和链接列表实现。
  • TreeSet 该类实现了Set接口,可以实现排序等功能
  • AbstractMap 实现了大部分的Map接口。
  • HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。 该类实现了Map接口,根据键的HashCode值存储数据具有很快的访问速度,最多允许一条记录的键为null,不支持线程同步
  • TreeMap 继承了AbstractMap,并且使用一颗
  • WeakHashMap 继承AbstractMap类,使用弱密钥的哈希表
  • LinkedHashMap 继承于HashMap,使用元素的自然顺序对元素进行排序.
  • IdentityHashMap 继承AbstractMap类,比较文档时使用引用相等

java.util包中

类名 类描述
Vector Vector 该类和ArrayList非常相似,但是该类是同步的,可以用在多线程的情况,该类允许设置默认的增长长度,默认扩容方式为原来的2倍
Stack Stack 栈是Vector的一个子类,它实现了一个标准的后进先出的栈
Dictionary Dictionary 类是一个抽象类,用来存储键/值对,作用和Map类相似
Hashtable Hashtable 是 Dictionary(字典) 类的子类,位于 java.util 包中。不允许存null
Properties Properties 继承于 Hashtable,表示一个持久的属性集,属性列表中每个键及其对应值都是一个字符串
BitSet 一个Bitset类创建一种特殊类型的数组来保存位值。BitSet中数组大小会随需要增加。

迭代器

一般遍历数组都是采用for循环或者增强for,这两个方法也可以用在集合框架,但是还有一种方法是采用迭代器遍历集合框架,它是一个对象,实现了 Iterator 接口或 ListIterator(以允许双向遍历列表和修改元素)接口。

public class Test {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("Hello");
        list.add("World");
        list.add("HAHAHAHA");
        //第一种遍历方法使用 For-Each 遍历 List
        for (String str : list) {            //也可以改写 for(int i=0;i<list.size();i++) 这种形式
            System.out.println(str);
        }

        //第二种遍历,把链表变为数组相关的内容进行遍历
        String[] strArray = new String[list.size()];
        list.toArray(strArray);
        for (int i = 0; i < strArray.length; i++) //这里也可以改写为  for(String str:strArray) 这种形式
        {
            System.out.println(strArray[i]);
        }

        //第三种遍历 使用迭代器进行相关遍历(长度变了也不担心)
        Iterator<String> ite = list.iterator();
        while (ite.hasNext())//判断下一个元素之后有值
        {
            System.out.println(ite.next());
        }
    }
}

遍历Map

public class Test {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<String, String>();
        map.put("1", "value1");
        map.put("2", "value2");
        map.put("3", "value3");

        //第一种:普遍使用,二次取值
        System.out.println("通过Map.keySet遍历key和value:");
        for (String key : map.keySet()) {
            System.out.println("key= " + key + " and value= " + map.get(key));
        }

        //第二种
        System.out.println("通过Map.entrySet使用iterator遍历key和value:");
        Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry<String, String> entry = it.next();
            System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
        }

        //第三种:推荐,尤其是容量大时
        System.out.println("通过Map.entrySet遍历key和value");
        for (Map.Entry<String, String> entry : map.entrySet()) {
            System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
        }

        //第四种
        System.out.println("通过Map.values()遍历所有的value,但不能遍历key");
        for (String v : map.values()) {
            System.out.println("value= " + v);
        }

        //第五种
        System.out.println("lambda表达式");
        map.forEach((key, value) -> System.out.println("key= " + key + " and value= " + value));
    }
}

ArrayList 和 LinkedList

List 基于 内存
ArrayList 使用数组来实现 遍历和查找元素比较快 遍历和查找元素比较慢 浪费内存空间(扩容、删除时)
LinkedList 使用链表来实现 LinkedList 添加、删除元素比较快 添加、删除元素比较慢 专门弥补ArrayList的缺陷!!

image-20201126215114357

//ArrayList 动态数组集合
public class MyArrayList {
    //数组保存数据
    private Object[] elementData;

    //定义数组长度
    private int size;

    //初始容量为10
    public MyArrayList() {
        elementData = new Object[10];
    }

    public void add(Object obj) {
        if (size >= elementData.length) {
            Object[] temp = new Object[elementData.length * 2];
            System.arraycopy(elementData, 0, temp, 0, size);
            elementData = temp;
        }
        elementData[size++] = obj;
        System.out.println("============================================");
        System.out.println("size:"+size);
        System.out.println("elementData.length:"+elementData.length);
    }

    public static void main(String[] args) {
        MyArrayList myArrayList = new MyArrayList();
        for (int i = 0; i < 11; i++) {
            myArrayList.add(i);
        }
    }
}

LinkedList(单链表,双链表,循环链表)

  • new LinkedList<>();//此对象保存了size和第一个Node的地址
  • 每次add对应着新开辟了一个Node节点,每个Node节点还保存着下一个节点的引用信息

image-20201126211447110

//LinkedList 链表结构
public class MyLinkedList<E> {
    private Node First;//头指针
    private Node Last;//尾指针
    private int size;

    class Node {
        //最核心的变量,用以保存当前节点的数据
        Object element;

        //保存前一个节点
        Node Previous;

        //保存后一个节点
        Node Next;

        public Node(Object element, Node Previous, Node Next) {
            this.element = element;
            this.Previous = Previous;
            this.Next = Next;
        }

        public Node(Object element) {
            this.element = element;
        }

        public Node() {
        }
    }

    //添加节点
    public void add(E element) {
        Node node = new Node(element);//创建一个新的链表节点
        if (First == null) {//如果是第一个节点
            First = node;//头指针指向这个节点
            Last = node;//尾指针指向这个新添加的节点
        } else {//如果不是第一个节点
            node.Previous = Last;//新添加的节点的头指向上一个节点
//            node.Next = null;
            Last.Next = node;//此处有疑问!!!
            Last = node;//尾指针指向这个新添加的节点
            First.Previous = Last;//头尾相连
        }
        size++;
    }

    //校验传入的索引是否合法
    private void checkIndex(int index) {
        if (index < 0 || index > size - 1) {
            throw new RuntimeException("索引不合法" + index);
        }
    }

    //获取指定索引的节点
    private Node getNode(int index) {
        checkIndex(index);
        Node temp = null;
        if (index <= (size >> 1)) {
            temp = First;
            for (int i = 0; i < index; i++) {
                temp = temp.Next;
            }
        } else {
            temp = Last;
            for (int i = size - 1; i > index; i++) {
                temp = temp.Previous;
            }
        }
        return temp;
    }

    public E getValue(int index) {
        Node temp = getNode(index);
        return (E) temp.element;
    }

    //移除特定索引节点
    public void remove(int index) {
        checkIndex(index);
        Node temp = null;
        if (index != (size - 1) && (index != 0)) {//不是最后一个节点和第一个节点
            temp = getNode(index);//获取指定位置的节点
            Node nodeNext = temp.Next;
            Node nodePrevious = temp.Previous;//借助两个中间节点指针
            nodePrevious.Next = nodeNext;
            nodeNext.Previous = nodePrevious.Next;
        } else if (index == (size - 1)) {//是最后一个节点
            temp = Last.Previous;
            temp.Next = null;
            First.Previous = temp;
        } else {
            temp = First.Next;
            temp.Previous = Last;
            First = temp;
        }
        size--;
    }

    //在指定位置插入节点
    public void insert(int index, E element) {
        checkIndex(index);
        Node nodeInsert = new Node(element);
        if ((index != 0)) {//不是最后一个节点和第一个节点
            Node nodeRight = getNode(index);
            Node nodeLeft = nodeRight.Previous;
            nodeLeft.Next = nodeInsert;
            nodeInsert.Previous = nodeLeft;
            nodeRight.Previous = nodeInsert;
            nodeInsert.Next = nodeRight;//连接新节点
        } else {//是第一个节点
            nodeInsert.Next = First;
            First.Previous = nodeInsert;
            nodeInsert.Previous = Last;
            First = nodeInsert;
        }
        size++;
    }

    @Override
    public String toString() {
        StringBuilder stringBuilder = new StringBuilder("[");
        Node temp = First;
        while (temp != null) {
            stringBuilder.append(temp.element + ",");
            temp = temp.Next;
        }
        stringBuilder.setCharAt(stringBuilder.length() - 1, ']');
        return stringBuilder.toString();
    }

    public static void main(String[] args) {
        MyLinkedList<String> testLinkedList01 = new MyLinkedList<>();
        for (int i = 0; i < 5; i++) {
            testLinkedList01.add("a" + i);
        }
        System.out.println(testLinkedList01.getValue(2));
        System.out.println(testLinkedList01);//此处有疑问
        testLinkedList01.remove(0);
        System.out.println(testLinkedList01);
        testLinkedList01.insert(0, "fada");
        System.out.println(testLinkedList01);
        testLinkedList01.insert(4, "fada");
        System.out.println(testLinkedList01);
    }
}

HashMap

存数据的过程:

  • 遍历数组,若存在值,则比较key的hash值;
  • 若相同(hash碰撞)则进一步equals比较内容,相同则进行覆盖,无相同则添加到数组末端;(若存在hash碰撞情况(重地、通话)则采用链表解决)
  • 链表长度大于阈值8并且数组长度大于64则将链表变为红黑树

image-20201126124106053

HashMap继承关系

image-20201126124849514

(图中子类和父类继承同一个接口是一个历史原因,是一个失误,jdk维护人员认为不值当为其修改)

成员变量

//初始容量必须是2的次方:减少索引相同的情况,使数组均匀分布;如果构造函数传入10则设为16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
//最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
//负载因子(加载因子)默认是0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//链表长度为8时转换为红黑树
static final int TREEIFY_THRESHOLD = 8;
//红黑树节点为6时转换为链表
static final int UNTREEIFY_THRESHOLD = 6;
//数组长度大于64(转红黑树的另一个条件)
static final int MIN_TREEIFY_CAPACITY = 64;
//存放键值对的
transient Node<K,V>[] table;
//存放缓存
transient Set<Map.Entry<K,V>> entrySet;
//存放元素的个数(注意不等于数组长度)
transient int size;
//每次扩容和更改结构的计数器
transient int modCount;
//边界值默认16,超过这个值就扩容
int threshold;
//哈希表的加载因子!!默认0.75。反映数组存储的满的程度。16*0.75=12
final float loadFactor;

put方法源码(各种计算索引避免hash碰撞存储扩容)

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}
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;
    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;
}

扩容方法源码

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; // double threshold
    }
    else if (oldThr > 0) // initial capacity was placed in threshold
        newCap = oldThr;
    else {               // zero initial threshold signifies using defaults
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    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;
}

如果预先知道了要使用的容量的大小,最好在构造的时候指定(阿里开发手册)

image-20201126180238201

posted @ 2020-11-26 21:55  夜雨秋池  阅读(90)  评论(0编辑  收藏  举报