Java中的集合框架

背景

在我们的日常开发中经常会使用到集合,比如ArrayList、HashMap等,你是不是平时就只用这俩?哈哈 别装就是说的你,开个玩笑。集合框架是在Java.Util包下的,它是从Java1.2才开始完善的,在此之前呢,也有几个比如Vecctor、Dictionary等。


Collection

我们来简单看一下Java整个集合框架的架构:

  

在介绍之前先说一下:任何架构(我认为是的),都具有高度的抽象思维,大师们会把最公共的功能抽象成为接口,比如这里最顶层接口Collection / Map, 其次呢在接口的基础上定义一些公共的抽象类去实现接口,并且实现一些最基本的的方法,最后具体实现类会根据自身特点呢实现其他方法,或者重写抽象类实现的方法。所以我们在架构设计的时候应该也要学会采用这种高度的抽象思维。

比如:

在AbstractCollection抽象类里实现了 remove()方法

    public boolean remove(Object o) {
        Iterator<E> it = iterator();
        if (o==null) {
            while (it.hasNext()) {
                if (it.next()==null) {
                    it.remove();
                    return true;
                }
            }
        } else {
            while (it.hasNext()) {
                if (o.equals(it.next())) {
                    it.remove();
                    return true;
                }
            }
        }
        return false;
    }

ArrayList根据自己底层是数组的特点又对该方法进行了重写:

    public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

在上面的架构图中我们明显可以看到,最顶层的接口是:Collection 和 Map,一个是单元素的,一个是K,V 键值对形式的。 Collection 接口实现了Iterator 接口,这个借口的作用是迭代器,用户集合内元素的遍历操作。

在Collection 接口下,又分为三种子接口:List 、Set、Queue。List接口的实现类存放的元素是有序的(但是内存空间并不一定是连续的),Set是接口的实现类存放的是无序的,Queue队列,也是有序的,他是一种First In First  Out 先进先出的数据结构。


List

在List的接口实现类中,最主要有三个 ArrayList 、LinkedList 、Vetcor

ArrayList底层是Object[] ,在堆中是一段连续的内存,LinkedList 底层是静态内部类构成的双向链表,所以呢,他在堆中内存地址不一定是连续的,并且呢LinkedList还实现了Queue队列,也可以进行首尾操作。

两者的区别呢:ArrayList 遍历下标查询速度是比LinkedList快的,因为LinkedList在查找元素过程中伴随着一个寻址的过程。ArrayList插入或者删除是非常慢的,在删除中间元素时,我们需要移动后面的元素组成一个新的数组,而LinkedList是双向链表的形式,删除中间元素只需要将前一个节点指向被删元素的下一个节点,下一个节点的前节点指向。如下图:添加也是一样的套路。

在ArrayList里,数组的初始化大小为10,添加元素超过数组长度时会动态扩容,创建一个新的数组出来,然后将旧数组的赋值到新数组中,然后将旧数组回收。

但是在这个过程中,扩容并且实现数据复制是非常消耗性能的,所以我们在使用时尽量定义好数组的长度,比如 List<String>  list = new ArrayList<>(24); //这是数组长度为24的集合。

/**
     * Default initial capacity.
     */
    private static final int DEFAULT_CAPACITY = 10;


//扩容过程,新数组长度为原数组长度的1.5倍。数组最大长度为Integer.MAX_VALUE.
    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);
    }

LinkedList由于也实现了Queue的子接口 Deque,所以他也具备队列中的方法,比如添加头节点,删除尾节点等等。

    //实现接口方法
    public void addFirst(E e) {
        linkFirst(e);
    }

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

...

 还有一个Vector是在Java1.0中出现的,他和ArrayList的功能上基本一致,这里就不过多介绍了,区别在于:

    1、Vector的方法都被synchronized的修饰的,他是线程安全的,Arraylist是线程不安全的。

    2、Vector扩容之后的数组长度是原来的2倍,ArrayList只有1.5倍。


Set 

Set的主要实现类:HashSet、LinkedHashSet、TreeSet。

HashSet 其实自己呢啥也不是,只不过是借住了HashMap的key作为自己的集合,HashMap是键值对存在的,所以map的value其实是一个不可变的Object对象。如下:

//内部维护了一个hashMap,值都存在HashMap的key里
 private transient HashMap<E,Object> map;

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


public HashSet(int initialCapacity) {
    map = new HashMap<>(initialCapacity);
}



// 添加元素时,元素就相当于map的key,value为 不可变object对象
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

所以利用HashMap的特点,HashSet的值是不可以重复的,而且是无序的。

LinkedHashSet 也是一样耍流氓,其底层利用了LinkedHashMap, 在LinkedHashMap中维护了一个双向链表,从而来保存插入顺序。


    //LinkedHashSet的结构 这里是定义HashMap的初始化大小,以及负载因子等。
    public LinkedHashSet(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor, true);
    }

    public LinkedHashSet(int initialCapacity) {
        super(initialCapacity, .75f, true);
    }

    public LinkedHashSet() {
        super(16, .75f, true);
    }

TreeSet 也是一样,其底层就是TreeMap,具体如👇的Map中介绍。

    //TreeSet的结构
    public TreeSet() {
        this(new TreeMap<E,Object>());
    }

    public TreeSet(Comparator<? super E> comparator) {
        this(new TreeMap<>(comparator));
    }

Queue

Queue是基于队列的一种结构,它可以保证元素先进先出的特点,Deque是双端队列的实现,继承于Queue。LinkedList又是实现与Qeque双端队列,所以如果要使用队列,通常是直接使用LinkedList。PriorityQueue叫做优先队列,它的特点是为每个元素提供一个优先级,优先级高的元素会优先出队列(默认排序是自然排序,队头元素是最小元素,可以通过comparator比较器修改排序的比较)。

该数据结构的实现主要是在JUC包下的阻塞队列中。


Map

Map的接口实现主要是:HashMap、LinkedHashMap、TreeMap 和HashTable。

HashMap 在1.8中底层数据结构是:数组+链表+红黑树的形式,他是key / value形式的存储元素,元素的存储位置主要是由key的hash值来决定的。hash值就是对象在内存中存储的一串地址码也叫散列码。

所以HashMap中存的元素也是无序的,且不重复,它允许key和value都为null。

LinkedHashMap中,他继承了HashMap,他没有自己的add() remove()等方法,都是利用了父类HashMap的方法,在LinkedHashMap中维护了一个Entry 也是继承了HashMap中的Node内部类,并且Entry里存在before after双向链表,从而记录了插入元素时的顺序。

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

TreeMap中,在put()的时候,是保证key实现了Comparator接口,否则则报错,就是通过比较器的形式,来保证插入元素的大小顺序。

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) {
            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);
            } while (t != null);
        }
        else {
            if (key == null)
                throw new NullPointerException();
            @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;
    }

HashTable 是在Java1.0中出现了,他在1.2中也实现了Map,所以他也是键值对形式的集合,他的功能实现上也是和HashMap没有很大区别,只不过HashTable的所有方法都是synchronized修饰的,类似于Vector 和 ArrayList的关系,所以HashTable是线程安全的HashMap,只不过就算他是线程安全,在多线程情况下我们也会考虑其他线程安全的Map 比如 ConcurrentHashMap Collections.synchronizedMap等,因为直接在方法上加synchronized 效率实在是太低了。

最后总结:


 

内容借鉴:

https://blog.csdn.net/u014494148/article/details/117201976?utm_medium=distribute.pc_feed.none-task-blog-yuanlijihua_tag_v1-1.nonecase&depth_1-utm_source=distribute.pc_feed.none-task-blog-yuanlijihua_tag_v1-1.nonecase

https://www.runoob.com/java/java-collections.html

感谢大家的观看,欢迎大佬指点。

后面会持续更新多优质好文,点关注不迷路。😂  同时小弟公众号👇,感谢大家的支持。

Java 集合框架主要分为两个部分:`java.util` 和 `java.util.concurrent`。其,`java.util` 包含了大部分的集合框架,而 `java.util.concurrent` 则包含了一些并发集合框架。 在 `java.util` 包集合框架主要分为两类:`Collection` 和 `Map`。`Collection` 接口表示一组对象,它是所有集合框架的基础接口,提供了对集合元素的基本操作,例如添加、删除、查找等。`Map` 接口则表示一组键值对,提供了根据键来查找值的操作。 在 `Collection` 接口下,Java 常用的集合框架包括: 1. `List`:有序列表,元素可以重复。常用的实现类有 `ArrayList`、`LinkedList` 和 `Vector`。 2. `Set`:无序集合,元素不可以重复。常用的实现类有 `HashSet`、`LinkedHashSet` 和 `TreeSet`。 3. `Queue`:队列,元素按照一定的顺序进行排列,常用的实现类有 `LinkedList` 和 `PriorityQueue`。 4. `Deque`:双端队列,可以在队列的两端进行插入和删除操作,常用的实现类有 `ArrayDeque` 和 `LinkedList`。 在 `Map` 接口下,Java 常用的集合框架包括: 1. `HashMap`:无序键值对集合,键不可以重复。 2. `LinkedHashMap`:有序键值对集合,键不可以重复。 3. `TreeMap`:有序键值对集合,键可以按照自然顺序进行排序。 4. `Hashtable`:与 `HashMap` 类似,但是是线程安全的,不推荐使用。 总之,Java 集合框架提供了丰富的数据结构和算法,可以大大简化程序的开发,提高程序的效率和可维护性。需要根据具体的需求来选择合适的集合框架和实现类。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

雪蓑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值