08、java集合类----List集合

目录

集合与数组

hashCode方法的作用?

层次关系

集合类遍历

List集合层次图

对比 Vector、ArrayList、LinkedList 有何区别?

读写效率:

扩容:

一般来说,也可以补充一下不同容器类型适合的场景:

Set 集合的几种实例

线程安全

在 Java 9 中,Java 标准类库提供了一系列的静态工厂方法


集合与数组

数组(可以存储基本数据类型)是用来存现对象的一种容器,但是数组的长度固定,不适合在对象数量未知的情况下使用。

集合(只能存储对象,对象类型可以不一样)的长度可变,可在多数情况下使用。

 

hashCode方法的作用?

  Set集合元素无序,但元素不可重复。那么这里就有一个比较严重的问题了:要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢?如果集合中现在已经有1000个元素,那么第1001个元素加入集合时,它就要调用1000次equals方法。这显然会大大降低效率。于是,Java采用了哈希表的原理,hashCode方法实际上返回的就是对象存储的物理地址(实际可能并不是)。这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。所以这里存在一个冲突解决的问题。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。

 

层次关系

图中,实线边框的是实现类,折线边框的是抽象类,而点线边框的是接口

Collection接口是集合类的根接口。扩展开提供了三大类集合,分别是:

  • List,有序集合,它提供了方便的访问、插入、删除等操作。
  • Set,Set 是不允许重复元素的,这是和 List 最明显的区别,也就是不存在两个对象 equals 返回 true。我们在日常开发中有很多需要保证元素唯一性的场合。
  • Queue/Deque,则是 Java 提供的标准队列结构的实现,除了集合的基本功能,它还支持类似先入先出(FIFO, First-in-First-Out)或者后入先出(LIFO,Last-In-First-Out)等特定行为。这里不包括 BlockingQueue,因为BlockingQueue通常是用在并发编程场合,所以被放置在并发包里。

Map是Java.util包中的另一个接口,它和Collection接口没有关系,是相互独立的,但是都属于集合类的一部分。Map包含了key-value键值对。Map不能包含重复的key,但是可以包含相同的value。

Iterator,所有的集合类,都实现了Iterator接口,这是一个用于遍历集合中元素的接口,主要包含以下三种方法:

  • hasNext()是否还有下一个元素。
  • next()返回下一个元素。
  • remove()删除当前元素。

 

集合类遍历

在类集中提供了以下四种的常见输出方式:

1、Iterator:迭代输出,是使用最多的输出方式。

Iterator it = arr.iterator();
while (it.hasNext()) {
    Object o = it.next(); 
}

2、ListIterator:是Iterator的子接口,专门用于输出List中的内容

Iterator it = arr.listIterator();
while (it.hasNext()) {
    Object o = it.next();
}

3、foreach输出:JDK1.5之后提供的新功能,可以输出数组或集合。

for (Object i:arr) {
    ...            
}

4、for循环

for (int i = 0; i <arr.size() ; i++) {
    ... 
}

 

对比 Vector、ArrayList、LinkedList 有何区别?

这三者都是实现集合框架中的 List,也就是所谓的有序集合,因此具体功能也比较近似,比如都提供按照位置进行定位、添加或者删除的操作,都提供迭代器以遍历其内容等。但因为具体的设计区别,在行为、性能、线程安全等方面,表现又有很大不同。

Vector 是 Java 早期提供的线程安全的动态数组,如果不需要线程安全,并不建议选择,毕竟同步是有额外开销的。Vector 内部是使用对象数组来保存数据,可以根据需要自动的增加容量,当数组已满时,会创建新的数组,并拷贝原有数组数据。

ArrayList 非线程安全动态数组实现,它本身不是线程安全的,所以性能要好很多。与 Vector 近似,ArrayList 也是可以根据需要调整容量,不过两者的调整逻辑有所区别,vector增长率为目前数组长度的100%,而Arraylist增长率为目前数组长度的50%。

LinkedList 顾名思义是 Java 提供的双向链表,所以它不需要像上面两种那样调整容量,它也不是线程安全的。它本身有自己特定的方法,如: addFirst(),addLast(),getFirst(),removeFirst(),removeLast()等.。

 

读写效率:

在删除可插入对象的动作时,为什么ArrayList的效率会比较低呢?

  • Vector ArrayList 作为动态数组,除了尾部,从数组的其它位置插入和删除元素,需要移动后段的数组元素,从而会重新调整索引顺序,调整索引顺序会消耗一定的时间,所以速度上就会比LinkedList要慢许多,比如我们在中间位置插入一个元素,需要移动后续所有元素。
  • 相反,LinkedList是使用链表实现的,若要从链表中删除或插入某一个对象,只需要改变前后对象的引用即可。

 

扩容:

ArrayList 在执行插入元素是超过当前数组预定义的最大值时,数组需要扩容,扩容过程需要调用底层System.arraycopy()方法进行大量的数组复制操作;在删除元素时并不会减少数组的容量(如果需要缩小数组容量,可以调用trimToSize()方法);在查找元素时要遍历数组,对于非null的元素采取equals的方式寻找。

Vector ArrayList 仅在插入元素时容量扩充机制不一致。Vector ArrayList 默认创建一个大小为10的Object数组,如 Vector的容量为10,一次扩容后是容量为20,如 ArrayList的容量为10,一次扩容后是容量为16。

对于Vector,将capacityIncrement设置为0;当插入元素数组大小不够时,如果capacityIncrement大于0,则将Object数组的大小扩大为现有size+capacityIncrement;如果capacityIncrement<=0,则将Object数组的大小扩大为现有大小的2倍

 

一般来说,也可以补充一下不同容器类型适合的场景:

  • Vector ArrayList 作为动态数组,其内部元素以数组形式顺序存储的,所以非常适合随机访问的场合。
  • LinkedList 经常用在增删操作较多而查询操作很少的情况下,ArrayList则相反。
  • LinkedList 实现Stack(堆栈)使用removeFirst()方法,实现Queue(队列)使用removeLast()方法。前者先进后出,后者是先进先出。LinkedList还是实现了Queue接口,因此可以直接作为队列使用。

 

Set 集合的几种实例

TreeSet  

  • TreeSet 实际是利用 TreeMap 实现的,TreeSet 支持自然顺序访问,但是添加、删除、包含等操作要相对低效(log(n) 时间)
  • TreeSet支持两种排序方式,自然排序和定制排序,其中自然排序为默认的排序方式。
  • TreeSet判断两个对象不相等的方式是两个对象通过equals方法返回false,或者通过CompareTo方法比较没有返回0

HashSet 

  • 其实是以 HashMap 为基础实现的。
  • HashSet 不能保证元素的排列顺序,顺序有可能发生变化,不是同步的,集合元素可以是null,但只能放入一个null。
  • 当向HashSet结合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据 hashCode值来决定该对象在HashSet中存储位置。

LinkedHashSet

  • 是根据元素的hashCode值来决定元素的存储位置。
  • 内部构建了一个记录插入顺序的双向链表,当遍历该集合时候,LinkedHashSet将会以元素的添加顺序访问集合的元素。
  • LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍微逊色于HashSet。

在遍历元素时,HashSet 性能受自身容量影响,所以初始化时,除非有必要,不然不要将其背后的 HashMap 容量设置过大。而对于 LinkedHashSet,由于其内部链表提供的方便,遍历性能只和元素多少有关系。

 

线程安全

不是线程安全的集合,并不代表这些集合完全不能支持并发编程的场景,在 Collections 工具类中,提供了一系列的 synchronized 方法,比如:

static <T> List<T> synchronizedList(List<T> list)

我们完全可以利用类似方法来实现基本的线程安全集合:

List list = Collections.synchronizedList(new ArrayList());

它的实现,基本就是将每个基本方法,比如 get、set、add 之类,都通过 synchronizd 添加基本的同步支持,非常简单粗暴,但也非常实用。注意这些方法创建的线程安全集合,都符合迭代时 fail-fast 行为,当发生意外的并发修改时,尽早抛出 ConcurrentModificationException 异常,以避免不可预计的行为。

 

在 Java 9 中,Java 标准类库提供了一系列的静态工厂方法

比如,List.of()、Set.of(),大大简化了构建小的容器实例的代码量。根据业界实践经验,我们发现相当一部分集合实例都是容量非常有限的,而且在生命周期中并不会进行修改。但是,在原有的 Java 类库中,我们可能不得不写成:

ArrayList<String>  list = new ArrayList<>();
list.add("Hello");
list.add("World");

 而利用新的容器静态工厂方法,一句代码就够了,并且保证了不可变性。

List<String> simpleList = List.of("Hello","world");

更进一步,通过各种 of 静态工厂方法创建的实例,还应用了一些我们所谓的最佳实践,比如,它是不可变的,符合我们对线程安全的需求;它因为不需要考虑扩容,所以空间上更加紧凑等。

如果我们去看 of 方法的源码,你还会发现一个特别有意思的地方:我们知道 Java 已经支持所谓的可变参数(varargs),但是官方类库还是提供了一系列特定参数长度的方法,看起来似乎非常不优雅,为什么呢?这其实是为了最优的性能,JVM 在处理变长参数的时候会有明显的额外开销,如果你需要实现性能敏感的 API,也可以进行参考。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值