Java集合框架详解

一、Java集合框架总览

在这里插入图片描述

二、接口介绍

  1. Iterator和Iterable
    Iterator是一个迭代器,能够对集合进行元素的遍历,下面是Iterator迭代器的所有内部函数在这里插入图片描述
    而Iterable内部定义了iterator()方法,属于对Iterator的再次封装
    在这里插入图片描述
    简单写了个迭代器方式遍历List集合
List list=new ArrayList();
        Iterator iterator=list.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }

疑问一:为什么Iterable要对Iterator再次封装,为什么不直接实现Iterator或在Iterable内部定义hasNext()和next()等方法?
答案可参考这篇文章:https://www.jianshu.com/p/cf82ab7e51ef

  1. Collection接口
    Collection接口是集合结构层次中的根接口,它代表了一组Object对象,一些集合允许重复元素,一些集合不允许重复元素。JDK并没有提供任何这个接口的直接实现。他提供了特定的子接口(例如:List、Set)。此接口通常用于在需要最大通用性的地方传递和操作集合。
    (1)Collection接口继承了Iterable接口
    (2)元素无序且可重复
  2. List接口和Set接口特点
    (1)List接口
    List接口继承了Collection接口,它是一个有序集合,使用此接口的用户可以精确地控制每个元素在列表中的插入位置。用户可以通过其整数索引访问元素,并搜索列表中的元素。与Set不同,List允许重复元素。
    (2)Set接口
    Set接口继承了Collection接口,集合内元素无序且不重复,最多包含一个null元素。
  3. Map接口
    Map中键和值一一映射(Key-Value),一个键最多映射一个值,并且键不允许重复且是无序的。Map中的Key和Value可以是任意引用类型的数据,当非系统类作为Map的Key时,必须重写equals()和hashCode()方法。
    遍历Map的四种方法:
Map<String,Object> map=new HashMap();
        /**
         * 1.第一种方式:使用 entrySet 遍历 Map 类(最常用)
         */
        for (Map.Entry<String,Object> entry:map.entrySet()){
            String key=entry.getKey();
            Object value=entry.getValue();
            System.out.println("key="+key+" value="+value);
        }
        /**
         * 第二种方式:通过Map.keySet得到所有的key,遍历key得到value
         */
        for (String key:map.keySet()){
            Object value=map.get(key);
            System.out.println("key="+key+" value="+value);
        }
        /**
         * 第三种方式:通过Map.values()遍历所有的value,但不能遍历key
         */
        for (Object value:map.values()){
            System.out.println("value="+value);
        }
        /**
         * 第四种方式:通过Map.entrySet使用iterator遍历key和value(不推荐)
         */
        Iterator<Map.Entry<String,Object>> iterator1=map.entrySet().iterator();
        while (iterator1.hasNext()){
            Map.Entry<String,Object> entry=iterator1.next();
            String key=entry.getKey();
            Object value=entry.getValue();
            System.out.println("key="+key+" value="+value);
        }

三、接口实现类介绍

  1. List接口的实现类
    (1)ArrayList
    ArrayList类实现了List接口,它的底层数据结构是数组,所以删除插入操作时间复杂度较高。不是线程安全的。
    两个带参的构造方法:
    a.public ArrayList(int initialCapacity)
    initialCapacity是初始化容量,initialCapacity是否为0决定创建的是不是空数组,
    b.public ArrayList(Collection<? extends E> c)
    这个构造方法表示可接收所有实现Collection接口的类,并在构造方法内部调用toArray()赋值给elementData
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);
        }
    }
public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // 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;
        }
    }

ArrayList的扩容函数grow(int minCapacity),扩容规则:新数据容量=当前数组容量+(当前数组 容量/2),即当前数组容量*1.5。新容量不能小于最小容量minCapacity,否则将声明最小容量的数组大小。新容量也不能大于最大容量MAX_ARRAY_SIZE ,否则将声明最大容量的数组大小。

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        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);
    }

ArrayList的底层数据结构就是数组,所以根据下标查询某元素时间复杂度O(1),某位置增加删除元素的时间负责度则是O(n),因为不论增加还是删除都涉及到后面的元素的移动。

(2)LinkedList
LinkedList类实现了List接口,底层数据结构是双向链表,由于是链表,所以搜索的时间复杂度O(n),增加删除的时间复杂度为O(1)。不是线程安全的。

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

增加元素、在固定位置增加元素等可查看源码,就是对双向链表的操作,很清晰

	public boolean add(E e) {
        linkLast(e);
        return true;
    }
    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++;
    }
	public void add(int index, E element) {
        checkPositionIndex(index);

        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }
    void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        final Node<E> pred = succ.prev;
        final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

(3) Vector
Vector实现了List接口,从构造函数中可以很容易发现Vector的底层数据结构使数组:

public Vector(int initialCapacity, int capacityIncrement) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
        this.capacityIncrement = capacityIncrement;
    }

Vector是线程安全的,add()、set()、get()方法都是synchronized的,可见锁粒度较大,会导致操作效率低下。ConcurrentHashMap降低了锁粒度。下面也会说到。

public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }

扩容方法如下:

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        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. Set接口的实现类
    (1)HashSet
    HashSet实现了Set接口,元素不可重复。实际上底层是一个HashMap实例。
 public HashSet() {
        map = new HashMap<>();
    }

但HashMap是以键值对的方式进行存储的,所以每次add新的元素时会默认赋值一个Object对象给值。不是线程安全的。

// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();

public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

(2)TreeSet
TreeSet实现了NavigableSet接口,NavigableSet接口继承了SortedSet接口,SortedSet又继承了Set接口,从SortedSet这个接口的命名就已经清楚的告诉我们,直接或间接实现这个接口的类都是有序的。
TreeSet底层实现是一个TreeMap实例,同样键值对中的值被赋值为Object。不是线程安全的。

public TreeSet() {
        this(new TreeMap<E,Object>());
    }
  1. Map接口的实现类
    (1)HashMap
    HashMap的详解可参考我的另一篇文章:https://blog.csdn.net/m0_37841477/article/details/105009417
    (2)Hashtable
    先总结Hashtable的特点,元素是无序不重复的,线程安全,使用共享锁,性能差
    Hashtable实现了Map接口,它的底层的数据结构使哈希桶和单项链表
private static class Entry<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Entry<K,V> next;
}
public Hashtable(int initialCapacity, float loadFactor) {
        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);
    }

Hashtable的线程安全很好理解,它的put()、get()方法都使用了关键字synchronized

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;
        @SuppressWarnings("unchecked")
        Entry<K,V> entry = (Entry<K,V>)tab[index];
        for(; entry != null ; entry = entry.next) {
            if ((entry.hash == hash) && entry.key.equals(key)) {
                V old = entry.value;
                entry.value = value;
                return old;
            }
        }

        addEntry(hash, key, value, index);
        return null;
    }

未完待续
(3)ConcurrentHashMap
未完待续
(4)TreeMap
未完待续

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值