Java中数据结构的介绍

Java的所有数据结构底层分别是:数组、链表、哈希表。下面我们就先来讲解这三类结构的特点:

数组:1.查询快;2.插入和删除慢。如果你注重查询速度,建议使用该数据结构。

链表:2.查询慢;2.插入和删除快。如果你插入和删除的操作较多,查询的操作少,建议使用该数据结构。

哈希表:1.同时兼并数组和链表的优势。如果你插入和删除的操作多,同时要求查询速度要快,那么就建议使用该数据结构。

但是根据面向对象的规范来说,Java中的数据结构有下面这几种:

1.数组

2.列表(List)。它包括了ArrayList、Vector、LinkedList、SynchronizedList。

3.键值对(Map)。它包括了HashMap、TreeMap等,还有其他类需要各位同学自己去研究了。

4.集合(Set)。它包括了HashSet、LinkedHashSet、TreeSet。

5.栈(Stack)。它只有一个类那就是,Stack.java,它就有先进后出的特点。

6.队列(Queue)

7.堆(Heap)

一、数组

数组是我们常见的一种数据结构,这里就不过多介绍它的内容。数组有以下特点:

1.查询快

2.插入、删除慢

上面的代码就是数组的实现方式

二、列表(List)

列表类主要有分别是ArrayList、Vector、SynchronizedList、LinkedList。我们先来看看ArrayList类。

2.1ArrayList

各位可以去看它的源码,我可能先说一下怎么看ArrayList这些类的源码。ArrayList的源码在jdk中,如果大家有安装了jdk那么就可以找到源码。源码路径是%JAVA_HOME%/lib/src.zip,把src.zip复制出来解压你就看到这样的目录:

ArrayList.java就在java.base\java\util路径下,你也可以用Idea编程软件打开这个目录以便更好的查看代码。

我们看到了ArrayList实现了List<T>接口,而List<T>接口继承了Collection接口。那么它就属于Collection类数据结构。

然后我们一路追踪add方法,发现ArrayList底层竟然是数组elementData[index] = element;

从代码中看出,if判断是判断数组是否已满,如果已经满就调用grow()创建一个新的数组,那我们就重点来看看grow()这个方法。

从代码上看出它调用newCapacity(minCapacity)方法来获取新数组的大小。那我们再来看看它是怎么扩容的。

从代码上我们看到有一行代码

int newCapacity = oldCapacity + (oldCapacity >> 1)

这行代码是什意思呢?原大小+0.5个原大小(向右移一位相当于除于2),那么基本可以确定ArrayList扩容是1.5倍扩容。

下面的那些判断是判断扩容后的大小是否正常。还有就是ArrayList默认大小是10

好了,ArrayList介绍到这里就行了,其他我就不在过多介绍了,需要各位同志继续深耕。

2.2Vector

从源码上看实现的接口也是List,和ArrayList都属于列表。下面继续分析。

查看了Vector的方法,发现了Vector和ArrayList底层都是数组,只不过Vector在很多方法上加上了synchronized关键字,表明了这个类是线程安全的。

那么得出的结论是ArrayList是线程不安全,Vector是线程安全。

然而,Vector已经不推荐使用了。推荐方案如下:

List<String> synList = Collections.synchronizedList(list);

2.3synchronizedList

从上面的代码,我们可以看出,这个方法也是返回了一个List列表,那就继续来看看SynchronizedList这个列表

分析SynchronizedList可以得出,它的底层是使用传递进来的List来进行一系列操作,这些操作时线程安全的。

那么就知道SynchronizedList类到底是干什么的了,这个类是对List对象进行线程安全的封装,封装成一个线程安全的List。

2.4LinkedList

查看这个类的源码和注释,我们知道这个类的底层并非数组,而是链表。它的功能和ArrayList这些是一样的,只不过它的底层是链表。上面我们讲过数组的特点是:1.查询快 2.插入、删除慢。那么链表就是相反的,链表的特点是:1.查询慢。2插入、删除快。

如果你得某个功能插入删除操作多,查询少,你可以使用该数据结构。

三、键值对(Map)

Map数据结构有很多种,但主要看HashMap这个常用的就行了,知道太多也不太好,人的精力和时间是有限的。那我们来看看HashMap。

3.1HashMap

从HashMap类的注释解释来说,它基于哈希表是的的Map,它是无序的,并且运行key和value为空。

我们着重看的是它如何扩容,经过研究它的源码,它扩容的方法是

从上面的代码中,我们看到了一个关键代码地方,newThr = oldThr << 1,并且newCap大小也扩大了一倍;这个意思是阈值向左移一位,那么就是扩大了一倍。那么可以理解为容量*2。

还有一个知识点要说明一下,当哈希表的链条长度等于设置的阈值时,这条链表就会转换为红黑树存储数据。

这个阈值默认是8

3.2TreeMap

从注释上来看它是一个树的结构

从源码上看TreeMap确实是一个树形结构,下面我分析它的核心方法put(key,value)

/**
     * Associates the specified value with the specified key in this map.
     * If the map previously contained a mapping for the key, the old
     * value is replaced.
     *
     * @param key key with which the specified value is to be associated
     * @param value value to be associated with the specified key
     *
     * @return the previous value associated with {@code key}, or
     *         {@code null} if there was no mapping for {@code key}.
     *         (A {@code null} return can also indicate that the map
     *         previously associated {@code null} with {@code key}.)
     * @throws ClassCastException if the specified key cannot be compared
     *         with the keys currently in the map
     * @throws NullPointerException if the specified key is null
     *         and this map uses natural ordering, or its comparator
     *         does not permit null keys
     */
    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;
    }

这个方法的主要意思是遍历,比较结果决定插入到左子树还是右子树,直到找到合适的位置。

这个数据结构的使用场景是,如果你要有序的存储和访问数据可以使用该结构。

3.3LinkedHashMap

从LinkedHashMap的注释上我们可以看出这个数据结构是双向链表和哈希表结构。比HashMap多了一个双向链表。这个双向链表主要是用来记录插入的顺序,然后遍历的时候也按照这个顺序遍历。

我们看到LinkedHashMap是继承了HashMap,那么就主要看它新增或重写了哪些东西。

这两个参数从注释上得出,它们是双向链表的首节点和尾节点。

从LinkedHashMap的源码上,我们看出它重写的不是put方法而是newNode等方法,newNode意思是新建节点、插入双向链表。

然后他遍历key的时候是根据双向链表来遍历的。

四、集合(Set)

集合主要有这三个,HashSet、LinkedHashSet、TreeSet,我们先来看看他们有什么特点。

4.1HashSet

根据HashSet类注释的解释,它底层是哈希表,并且是无序的、允许null元素。

我们来看看他的的核心方法add(element)

我们看到add方法里面,把元素e对象当做key值保存到HashMap那么就说明,set列表的元素是不会重复的。

4.2.TreeSet

从源码我们看到TreeSet底层是使用TreeMap,我们上一章有讲解过TreeMap的特点:它安装key来排序,并且是非重的。那么TreeSet也是有相同的特点。从下面的源码就可以确定它的特点了。

我们基本可以确定,TreeSet是一个会根据数据来自动排序的集合,前提是你的数据是String。如果要实现object的排序,那么就需要你重新数据类的compare方法。

4.3LinkedHashSet

从这个注释来它的结构是哈希表+双向链表,保持原生插入顺序,例如插入的时候是A,B,C,D,那么变量的时候也是这个顺序。

我看到它继承了HashSet,那么这里就有同学有疑问?既然它继承了HashSet,那为何他们的结果不一致,HashSet是哈希表,而LinkedHashSet是哈希表+双向链表。原因如下:

我们看到他调用的是HashSet(int initialCapacity, float loadFactor, boolean dummy)构造器

因为构造器的不同,而导致的结构不同。总之需要记住的就是HashMap是无序的LinkedHashMap是无序的。

五.栈(Stack)

在Java中,栈只有一种,那就是Stack类。

从源码上我们可以看出,它继承了Vector类,那么它可能具有线程安全的特点。我们看到了push实际调用了Vector的addElement方法,说明push是线程安全的。

这个方法就是栈弹出数据的方法,从源码我们可以看出,它获取了最后一个数据,并在列表里把它删除掉。这个方法也是线程安全的。

那么我们基本可以确定Stack是线程安全的。

六、队列(Queue)

队列也是一种常见的数据结构,它具有先进先出的特点。

5.1ConcurrentLinkedQueue

看ConcurrentLinkedQueue的节点,我们可以看出,它是一个单向链表

从出列和入列的代码看出,它使用了cas算法实现线程安全,和使用synchronized互斥锁不同,它是乐观锁。那我们就来区分一下乐观锁和互斥锁有何不同。

互斥锁:也就是平时的synchronized关键字,ReentrantLock等方式实现的锁,当多个线程执行到加互斥锁的代码时,第一个线程可以执行,但是后面的线程会阻塞在加锁的地方,等第一个线程执行晚后,剩下的线程继续竞争锁。

乐观锁:乐观锁是一个概念,它有很多种方式,例如上面代码的cas算法。它的意思是,当多个线程执行到修改的地方,只有一个线程可以修改,其他的线程会立即返回失败的操作不会阻塞线程。

ConcurrentLinkedQueue最适合高并发的场景使用,所以正常情况下不会用到这个东西。

5.2LinkedBlockingQueue

从名字上我们就可以看出,它是一个阻塞队列。

通过源码分析,看出这个阻塞队列它的底层是一个链表结构。

这个队列是我们平时常用的队列,它也可以用于线程之间通信。

从源码上我们看出阻塞是通过锁的方式来实现的,通过互斥锁ReentrantLock来实现的。

5.3ArrayBlockingQueue

它也是一个阻塞队列,不过它和LinkedBlockingQueue不同它的底层是数组结构。

它阻塞的方式和LinkedBlockingQueue一样也是通过锁的方式。

那么这两个如何区分,什么情况下用哪个?

其实这两个除了底层结构不一样其他都是相同的。那么就从底层去考虑使用长期。

链表没有是没有长度限制的,那么如果你得场景需要的数据不固定那么就LinkedBlockingQueue就非常适合了。但是如果你的使用场景数据个数是固定的那么数组就非常适合的。

七、堆(Heap)

堆的结构是一颗完全二叉树,在Java中直接使用堆结构的类不多,但是堆结构的概念和算法在Java中得到了广泛的应用,以下就是涉及到堆结构的类。

1.TreeMap和TreeMap:这两个类的底层是一颗红黑树,红黑树是完全二叉树的一种,只不过红黑树设计到顺序的概念。

Java中的结构就介绍到这里了,了解更多知识可以关注微信公号

  • 38
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值