Java集合数据结构

集合类型

Set集合:集合元素是不能重复的。元素是没有顺序的。所以它不能基于位置访问元素。TreeSet和HashSet是它的实现类。

List集合: 集合元素是可以重复的。元素是有顺序的。所以它可以基于位置访问元素。ArrayList和LinkedList是它的实现类。

Map:它包含键值对。Map的键是不能重复的。Map不能保证存储的顺序。HashMap和TreeMap是它的实现类。

怎样来选择?

事实上,选择Set,List或者Map是依赖你的数据结构的。如果你将要存储的数据没有重复且不需要顺序,你可以选择用Set。如果你将要存储的数据需要保证顺序,你可以选择用List。如果你有一个键值对来关联两个不同的对象或者用一个标识符来标识对象,那么你可以选择Map。

举个例子:

颜色的集合最好放入Set中。

球队最好放在List中。因为球队的出场需要顺序。

Web Sessions的集合最好在Map上;唯一的session ID会更好的引用实际对象。

当我们选择用哪个集合的时候,我们主要关心的是集合的速度:

  • 访问元素的速度
  • 添加一个新元素的速度
  • 移除一个元素的速度
  • 迭代的速度

此外, 也有一致性的问题。一些实现会保证访问的速度,而一些实现将有个变化的速度。我们关心的这些速度依赖于集合的具体实现。

链表实现

具体的类:LinkedList, LinkedHashSet

内部原理:每个节点中都持有一个元素和下一个元素的指针。如下图:

  • 如果我们想加入一个元素到上图的第二个位置上,是很简单的。就像下图一样,它只须把原图中的第一个节点中的指针指向新加入的这个元素,把新加入的这个元素的指针指向原图中的第二个节点就可以了。这个速度是非常快的!不需要拷贝,移动和记录原集合中的元素。
  • 移除元素也是同理的,只要把原图中第一个节点中的指针指向原图中第二个节点的元素就可以了。

当我们想访问集合中的元素是很慢的。先看看LinkedList的源码:

/**
     * 返回指定索引位置的元素
     */
    Node<E> node(int index) {
        // assert isElementIndex(index);

        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

当我们想获取指定位置的一个元素时,程序会先将集合大小size右移一位(也就是说,把size的大小会减少一半),然后判断索引位置是离第一个节点位置近还是最后一个节点位置近,然后开始遍历,得到指定的节点。

举个例子:如果集合中有50个元素,如果你想获取第20个元素,那么它会从集合中第一个元素开始遍历,直到获取到第20个元素为止;如果你想获取第40个元素,那么它会从集合中最后一个元素开始遍历,直到获取到第40个元素为止。

所以对于链表实现的集合来说,它访问元素的速度很慢。而且访问不同位置的元素,速度还不一致。还有一点我们要深深牢记的是:当我们做添加和移除操作时,都会调用到上面的node方法,从而遍历获取所要操作节点的前一个节点,因此,这会对速度有一定的影响。

因此,当你需要更快的添加/移除,而且并不怎么关心访问时间的时候,像LinkedList这样的链表集合是更合适的。如果你打算在你的集合中有很多的添加/移除元素操作,那么这是个不错的选择。

数组实现

具体的类:ArrayList

ArrayList是集合类中的唯一基于数组实现的。

看下图:在ArrayList中,当我们把一个新元素添加到第四个位置上时,它会把第四个位置(包括第四个位置)以后的每一个位置上的元素都向后挪一位,然后在把新加入的元素插入到第四个位置上。这是很慢的,而且这并不能保证时间,它依赖于有多少的元素需要拷贝。同理,移除一个元素就是把后面的元素全部向前挪一位。

还有一种更糟糕的情况。当我们创建ArrayList对象的时候,它的数组长度是固定的(这个长度可以在构造函数中设置,如果不设置默认为10)。当我们操作集合的时候,如果它的容量超过这个固定长度的话,就不得不创建一个更大容量的数组,然后把当前集合中的所有元素拷贝到新创建的这个集合当中。这是非常非常慢的。

然而,ArrayList访问元素的速度是很快的。对于数组来说,它在内存空间中的位置是连续的,所以我们不需要遍历整个集合就可以准确的算出元素引用在内存中的位置。并且,耗费的时间也是一致的。

因此,如果你有一些并不怎么修改,而且需要快速访问的元素,那么ArrayList是一个很好的选择。

Hash实现

具体的类:HashSet, HashMap

HashSet是基于HashMap实现的,所以这里我就只解释HashMap了。

HashMap的工作原理:HashMap底层就是一个数组结构(叫做Entry Table),数组中的每一项又是一个链表(叫做Bucket)。当新建一个HashMap的时候,就会初始化一个数组。

    public V put(K key, V value) {
        ......
        int hash = hash(key);
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        ......
        addEntry(hash, key, value, i);
        return null;
    }

上面是HashMap中put方法的部分代码(JDK7)。当我们向HashMap中put元素的时候,先根据key重新计算hash值,根据hash值得到这个元素在数组中的位置(即下标),再通过这个下标访问到Bucket的链头并开始遍历整个Bucket(也就是整个链表),如果Bucket中已经存在新添加的key的值,则将原有的值设置成新添加的值,并返回旧值。否则,新加入的元素放在链头,最先加入的放在链尾。

总的来说,程序首先根据准备放入的元素的key(通过hash算法)决定该Entry在Entry Table中的存储位置。如果新加入的这个Entry 的key与原有Entry的Key通过equals比较返回 true,那么新添加 Entry的value将覆盖集合中原有Entry的value,但key不会覆盖。如果这两个 Entry 的 key 通过 equals 比较返回 false,新添加的 Entry 将与集合中原有 Entry 形成 Entry 链,而且新添加的 Entry 位于 Entry 链的头部(也就是Bucket的链头)。

同理,从HashMap中get元素时,首先通过key计算出的Hash值来确定Entry在Entry Table中的存储位置,然后通过key的equals方法在对应位置的链表中找到需要的元素。

总结:HashMap 底层采用一个 Entry[] 数组来保存所有的Entry对象(里面包含hash值、key-value对、下一个Entry的指针),当需要存储一个 Entry 对象时,会根据hash算法来决定其在数组中的存储位置,在根据equals方法决定其在该数组位置上的链表中的存储位置;当需要取出一个Entry时,也会根据hash算法找到其在数组中的存储位置,再根据equals方法从该位置上的链表中取出该Entry。

HashMap的rehashing

当HashMap中的元素越来越多的时候,hash冲突的几率也就越来越高,因为数组的长度是固定的。所以为了提高查询的效率,就要对HashMap的数组进行扩容。数组容量将会自动扩大两倍,在数组扩容时,所有原存在的Entry会重新计算索引值,并且Entry链的顺序也会发生颠倒(如果还在同一个链中的话),而该新添加的Entry的索引值也会重新计算,这是最消耗性能的,这个过程就是rehashing。

那么HashMap什么时候进行扩容呢?当HashMap中的元素个数超过数组小loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,这是一个折中的取值。也就是说,默认情况下,数组大小为16,那么当HashMap中元素个数超过16*0.75=12的时候,就把数组的大小扩展为 2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-05 00:05:33

Java集合数据结构的相关文章

Java集合-数据结构

数据结构部分,复习栈,队列,数组,链表和红黑树,参考博客和资料学习后记录到这里方便以后查看,感谢被引用的博主. 栈 栈(stack)又称为堆栈,是线性表,它只能从栈顶进入和取出元素,有先进后出,后进先出(LIFO, last in first out)的原则,并且不允许在除了栈顶以外任何位置进行添加.查找和删除等操作.栈就相当如手枪的弹夹,先进入栈的数据被压入栈底(bottom),而后入栈的数据存放在栈顶(top).当需要出栈时,是先让栈顶的数据出去后,下面的数据才能出去,这就是先进后出的特点.

给jdk写注释系列之jdk1.6容器(13)-总结篇之Java集合与数据结构

是的,这篇blogs是一个总结篇,最开始的时候我提到过,对于java容器或集合的学习也可以看做是对数据结构的学习与应用.在前面我们分析了很多的java容器,也接触了好多种常用的数据结构,今天我们就来总结下这些内容. 下面我们以数据结构的维度来总结下,在Java集合的实现过程中,底层到底使用了哪些常用的数据结构中,他们分别又有什么特点.      1. 数组(Array) 结构说明:在程序设计中,为了处理方便, 把具有相同类型的若干变量按有序的形式组织起来.这些按序排列的同类数据元素的集合称为数组

java集合(一)——数据结构详解

当我们要处理一串数据的时候,相比较c++和c中的数组和指针,在Java中我们更为常用的是ArrayList.HashMap等集合数据结构.c语言对指针的支持成就了他的深度,而Java中多种多样的包装类成就了他的广度.在java中,我们一般将List.Map.Set等数据结构通归为集合数据结构,这些类都存在于集合类库中. (一) 集合接口 1.集合的接口和实现分离 与其他的数据结构类库相似的,java的集合类库也采用了这种接口和实现分离的方法. 这种方法的好处是不言而喻的.当你要实例化一个队列时,

JAVA (集合和数据结构)

Collection和Collections的区别: 1.java.util.Collection 是一个集合接口.它提供了对集合对象进行基本操作的通用接口方法.Collection接口在Java 类库中有很多具体的实现.Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式.声明了适用于JAVA集合(只包括Set和List)的通用方法.Set 和List 都继承了Conllection,Map没有. 2.java.util.Collections 是一个包装类.它包含有各种

【Java】Java集合框架源码和数据结构简要分析——List

前言 之前一直把集合框架分成Collection和Map来对待,主要是基于储存内容是单列和双列,实际上这样来区分不太正确,set实际上是双列的结构. 现在回顾集合框架,看到很多当初看不到的东西. 现在来看集合框架,一部分是List,一部分是Set和Map,Set和Map几乎就是一回事. 一.数据结构 不讲太深入的东西,实际上我也讲不了多深入. 数据结构,就是一堆数据的关系. 逻辑结构--数据逻辑上的关系,其实就是数据结构,而数据的逻辑结构几乎可以分成四种:线性结构.集合结构.树形结构和图结构.

一起学 Java集合框架、数据结构、泛型

一.Java 集合框架 集合框架是一个用来代表和操纵集合的统一架构.所有的集合框架都包含如下内容: 接口:是代表集合的抽象数据类型.接口允许集合独立操纵其代表的细节.在面向对象的语言,接口通常形成一个层次. 实现(类):是集合接口的具体实现.从本质上讲,它们是可重复使用的数据结构. 算法:是实现集合接口的对象里的方法执行的一些有用的计算,例如:搜索和排序.这些算法被称为多态,那是因为相同的方法可以在相似的接口上有着不同的实现. 集合接口 序号 name 接口描述 1 Collection Col

Java学习笔记——浅谈数据结构与Java集合框架(第一篇、List)

横看成岭侧成峰,远近高低各不同.不识庐山真面目,只缘身在此山中. --苏轼 这一块儿学的是云里雾里,咱们先从简单的入手.逐渐的拨开迷雾见太阳.本次先做List集合的三个实现类的学习笔记 List特点:有序,元素可重复.其实它的本质就是一个线性表(下面会说到) 先上图,Java集合有Collection体系和Map体系: 然后简单介绍一下数据结构和算法: 数据结构就是数据和数据之间的关系,好比分子结构,晶体结构.碳原子按照一定的方式组合在一起形成碳分子,碳分子再按照一定方式形成晶体. 算法是对解题

JAVA集合框架及其背后的数据结构

一:介绍:Java 集合框架 Java Collection Framework ,又被称为容器 container ,是定义在 java.util 包下的一组接口 interfaces 和其实现类 classes .其主要表现为将多个元素 element 置于一个单元中,用于对这些元素进行快速.便捷的存储 store .检索retrieve .管理 manipulate ,即平时我们俗称的增删查改 CRUD .如:一副扑克牌(一组牌的集合).一个邮箱(一组邮件的集合).一个通讯录(一组姓名和电

Java—集合框架List

集合的概念 现实生活中:很多的事物凑在一起 数学中的集合:具有共同属性的事物的总和 Java中的集合类:是一种工具类,就像是容器,存储任意数量的具有共同属性的对象 集合的作用 在类的内部,对数据进行组织(针对作用与意义一样的属性,将他们放到一个集合中) 简单而快速的搜索大数量的条目 有的集合接口,提供了一系列排列有序的元素,并且可以在序列中快速的插入或删除有关元素 有的集合接口,提供了映射关系,可以通过关键字(key)去快速查找到对应的唯一对象,而这个关键字可以是任意类型 与数组相比 数组的长度