java集合研究 与 随机访问和顺序访问

本文深入探讨了Java集合框架中的List接口,重点关注ArrayList和LinkedList的特点。ArrayList基于数组实现,适合随机访问,不适合频繁增删;LinkedList基于链表,适合顺序访问和增删。此外,还涵盖了Stack、Set(如HashSet、LinkedHashSet、TreeSet)以及Map(如HashMap、LinkedHashMap、Hashtable)的特性和使用场景。文章强调了在选择集合类型时要考虑访问速度和操作频率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

java集合:

java集合框架图:
在这里插入图片描述

List(有序、可重复):

  • 数组:
  • 数组在Java中是一个对象,数组实例同样是使用new操作符创建的
  • 数组存储在Java堆的连续内存空间。
  • 数组一个固定长度 的数据结构,一旦声明,你不能改变数组的长度

ArrayList(查询速度快,增删速度慢):

  • ArrayList 是一个数组队列,相当于 动态数组。ArrayList内部存储数据的是数组Object[] elementData。
  • 当容量不足以容纳全部元素时,ArrayList会重新设置容量:新的容量=“(原始容量x3)/2 + 1”。
  • List里存放的对象是有序的、可重复的。
  • 底层是使用数组实现,可直接通过下标访问,为List提供快速访问功能。因为往list集合里插入或删除数据时,会伴随着后面数据的移动,所以插入删除数据速度慢.
  • ArrayList中的操作不是线程安全的

LinkedList(查询速度慢,增删速度快):

  • 是一个继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作。
  • 是基于链表结构实现的,所以查询速度慢,增删速度快
  • LinkedList包含几个重要的成员:first 和 last、size。提供了特殊的方法,对头尾的元素操作(进行增删查)。
  • LinkedList实际上是通过双向链表去实现的。既然是双向链表,那么它的顺序访问会非常高效,而随机访问效率比较低.

Stack:

  • 底层也是使用数组实现,随机访问效率高,增删速度慢
  • 先进后出
  • 继承Vector

List的总结:

1、List 是一个有序的队列。
2、ArrayList, LinkedList, Vector, Stack是List的4个实现类。
  ArrayList 是一个数组队列,相当于动态数组。它由数组实现,随机访问效率高,随机插入、随机删除效率低。
  LinkedList 是一个双向链表。它也可以被当作堆栈、队列或双端队列进行操作。LinkedList随机访问效率低,但随机插入、随机删除效率高。
  Vector 是矢量队列,和ArrayList一样,它也是一个动态数组,由数组实现。但是ArrayList是非线程安全的,而Vector是线程安全的。
  Stack 是栈,它继承于Vector。它的特性是:先进后出(FILO, First In Last Out)。

List的使用场景:

1、对于需要快速插入,删除元素,应该使用LinkedList。
2、 对于需要快速随机访问元素,应该使用ArrayList。
3、 对于“单线程环境” 或者 “多线程环境,但List仅仅只会被单个线程操作”,此时应该使用非同步的类(如ArrayList)。
对于“多线程环境,且List可能同时被多个线程操作”,此时,应该使用同步的类(如Vector)。

LinkedList.java中向指定位置插入元素的代码如下:
// 在index前添加节点,且节点的值为element
public void add(int index, E element) {
    addBefore(element, (index==size ? header : entry(index)));
}

// 获取双向链表中指定位置的节点
private Entry<E> entry(int index) {
    if (index < 0 || index >= size)
        throw new IndexOutOfBoundsException("Index: "+index+
                                            ", Size: "+size);
    Entry<E> e = header;
    // 获取index处的节点。
    // 若index < 双向链表长度的1/2,则从前向后查找;
    // 否则,从后向前查找。
    if (index < (size >> 1)) {
        for (int i = 0; i <= index; i++)
            e = e.next;
    } else {
        for (int i = size; i > index; i--)
            e = e.previous;
    }
    return e;
}

// 将节点(节点数据是e)添加到entry节点之前。
private Entry<E> addBefore(E e, Entry<E> entry) {
    // 新建节点newEntry,将newEntry插入到节点e之前;并且设置newEntry的数据是e
    Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
    // 插入newEntry到链表中
    newEntry.previous.next = newEntry;
    newEntry.next.previous = newEntry;
    size++;
    modCount++;
    return newEntry;
}
//**通过add(int index, E element)向LinkedList插入元素时。先是在双向链表中找到要插入节点的位置index;找到之后,再插入一个新节点。
//双向链表查找index位置的节点时,有一个加速动作:若index < 双向链表长度的1/2,则从前向后查找; 否则,从后向前查找。
 
ArrayList.java中向指定位置插入元素的代码。如下:
// 将e添加到ArrayList的指定位置
public void add(int index, E element) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(
        "Index: "+index+", Size: "+size);

    ensureCapacity(size+1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1,
         size - index);
    elementData[index] = element;
    size++;
}
 
//ensureCapacity(size+1) 的作用是“确认ArrayList的容量,若容量不够,则增加容量。”
//真正耗时的操作是 System.arraycopy(elementData, index, elementData, index + 1, size - index);

随机访问和顺序访问:

对于随机访问和顺序访问用ArrayList和LinkedList来做比较 (ArrayList与LinkedList的遍历方式和比较: http://www.cnblogs.com/aoguren/p/4771589.html)

ArrayList内部的数据是用数据Object[]来存储的,顺序存储,可以通过脚标直接获取get(index),随机访问;

LinkedList是链式存储的,顺序访问;

其实LinkedList也实现了get()方法,看一下他的get方法源码

LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Queue<E>, Cloneable, Serializable :
// 获取location处的节点。
// 若location < 双向链表长度的1/2,则从前先后查找;
// 否则,从后向前查找。
public E get(int location) {
    if (location >= 0 && location < size) {
        Link<E> link = voidLink;
        if (location < (size / 2)) {
            for (int i = 0; i <= location; i++) {
                link = link.next;
            }
        } else {
            for (int i = size; i > location; i--) {
                link = link.previous;
            }
        }
        return link.data;
    }
    throw new IndexOutOfBoundsException();
}

Set(无序,不能重复):

Set里存放的对象是无序,不能重复的,集合中的对象不按特定的方式排序,只是简单地把对象加入集合中。

HashSet:
  • 它是通过HashMap保存内容的(不保证元素的顺序),HashSet中含有一个"HashMap类型的成员变量"map,HashSet的操作函数,实际上都是通过map实现的。
  • HashSet只用到了HashMap的Key,并且HashMap的Key是唯一的,正好满足set元素不可重复的条件。但是HashMap是key-value键值对,而HashSet只用到了key,所以,向map中添加键值对时,键值对的值固定是PRESENT(private
    static final Object PRESENT = new Object()😉
public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
LinkedHashSet:

LinkedHashSet 底层使用 LinkedHashMap 来保存所有元素,它继承于 HashSet,其所有的方法操作上又与 HashSet 相同,底层构造一个 LinkedHashMap 来实现,在相关操作上与父类 HashSet 的操作相同,直接调用父类 HashSet 的方法即可。

TreeSet(存取无序,元素唯一,可以进行排序(排序是在添加的时候进行排序)):
  • TreeSet是基于二叉树的数据结构,二叉树的:一个节点下不能多余两个节点。
  • 二叉树的存储过程:
    如果是第一个元素,那么直接存入,作为根节点,下一个元素进来是会跟节点比较,如果大于节点放右边的,小于节点放左边;等于节点就不存储。后面的元素进来会依次比较,直到有位置存储为止

MAP(键值对、键唯一、值不唯一):

Map集合中存储的是键值对,键不能重复,值可以重复。根据键得到值,对map集合遍历时先得到键的set集合,对set集合进行遍历,得到相应的值。

Hash算法、Hash表:

HASH主要用于信息安全领域中加密算法,它把一些不同长度的信息转化成杂乱的128位的编码,这些编码值叫做HASH值. 也可以说,hash就是找到一种数据内容和数据存放地址之间的映射关系。

数组的特点是:寻址容易,插入和删除困难;而链表的特点是:寻址困难,插入和删除容易。哈希表综合两者的特性,寻址容易,插入删除也容易的数据结构

哈希算法总结 https://www.jianshu.com/p/bf1d7eee28d0 很不错

HashMap(无序):
  • 内部通过单链表存储数据的。
  • HashMap是最常用的Map,内部是用数组实现的,数组中的每一项都是一个链表。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。
  • HashMap的源码解析:https://www.jianshu.com/p/c91b010dc03a
  • https://tech.meituan.com/2016/06/24/java-hashmap.html
  • 扩容是一个特别耗性能的操作,所以当程序员在使用HashMap的时候,估算map的大小,初始化的时候给一个大致的数值,避免map进行频繁的扩容。
  • 负载因子是可以修改的,也可以大于1,但是建议不要轻易修改,除非情况非常特殊。
  • HashMap是线程不安全的,不要在并发的环境中同时操作HashMap,建议使用ConcurrentHashMap。、
  • JDK1.8引入红黑树大程度优化了HashMap的性能。
  • HashMap 的实现不是同步的,这意味着它不是线程安全的
  • 源码分析https://www.hollischuang.com/archives/2091
LinkedHashMap:
  • 内部通过双向链表来存储数据的。
  • LinkedHashMap保存了记录的插入顺序,在用Iteraor遍历LinkedHashMap时,先得到的记录肯定是先插入的,在遍历的时候会比HashMap慢
  • 有HashMap的全部特性。
  • https://blog.youkuaiyun.com/justloveyou_/article/details/71713781
  • HashMap和LinkedHashMap的区别。
  • 源码分析、对比和HashMap的区别
Hashtable:
  • HashMap 的 key 和 value 都允许为 null,而 Hashtable 的 key 和 value 都不允许为null。HashMap 遇到 key 为 null 的时候,调用 putForNullKey 方法进行处理,而对 value没有处理;Hashtable遇到 null,直接返回 NullPointerException。
  • Hashtable 方法是同步,而HashMap则不是。Hashtable 中的几乎所有的 public 的方法都是 synchronized 的,而有些方法也是在内部通过 synchronized 代码块来实现。所以有人一般都建议如果是涉及到多线程同步时采用 HashTable,没有涉及就采用 HashMap,但是在 Collections 类中存在一个静态方法:synchronizedMap(),该方法创建了一个线程安全的 Map 对象,并把它作为一个封装的对象来返回。

遍历方式:for、foreach、Iterator:

foreach循环实际是Iterator实现的
迭代器是一种模式,它可以使得对于序列类型的数据结构的遍历行为与被遍历的对象分离,即我们无需关心该序列的底层结构是什么样子的。只要拿到这个对象,使用迭代器就可以遍历这个对象的内部。

代码如下:

for (Iterator<Integer> iterator = list.iterator(); iterator.hasNext();)
{
    value = iterator.next();            
}

遍历时安全的删除元素

//IndexOutOfBoundsException  删除后count没有及时更改
        int count = list.size();
        for (int i = 0; i< count; i++) {
            if (list.get(i) == 5) {
                list.remove(i);
            }
        }

        //安全  remove方法每次调用时,会将size-1;
        for (int i = 0; i< list.size(); i++) {
            if (list.get(i) == 5) {
                list.remove(i);
            }
        }

        //安全 但是要next() 和 remove()同时用
        Iterator it = list.iterator();
        while(it.hasNext()){
            int value = (int) it.next();
            if (value == 5) {
                it.remove();
            }
        }

参考资料:

java集合系列:http://www.cnblogs.com/skywang12345/p/3323085.html
Java集合类: Set、List、Map、Queue使用场景梳理
SparseArray、HashMap、ArrayMap的比较 :http://www.jianshu.com/p/7b9a1b386265 https://my.oschina.net/jerikc/blog/271914
http://blog.youkuaiyun.com/coslay/article/details/46862273
https://my.oschina.net/jerikc/blog/271914

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值