什么是集合

  1. Java提供的一个集合类。
  2. 能存储任意的对象。
  3. 它的一个长度是可以改变,长度是随着你元素的增加而增加

    集合与数组的区别

  4. 数组能存基本数据类型,和引用类型

  5. 集合当中只能存放引用数据类型,会把基本数据类型转成对象
  6. 数组长度是固定,不能再去增长
  7. 集合长度是可以改变,根据元素的增长而增加

    选哪个?

    如果元素个数是固定,推荐使用数组。
    需要对元素进行增,删,改,查
    或以一种面向对象的方式操作,推荐使用集合

    数组的框架体系图

    集合 - 图1
    集合 - 图2
    注:上面的类图中,实线边框的是实现类,比如ArrayList,LinkedList,HashMap等,折线边框的是抽象类,比如AbstractCollection,AbstractList,AbstractMap等,而点线边框的是接口,比如Collection,Iterator,List等。

    如何使用抽象类

    如果要自己实现一个集合类,去实现那些抽象的接口会非常麻烦,工作量很大。这个时候就可以使用抽象类,这些抽象类中给我们提供了许多现成的实现,我们只需要根据自己的需求重写一些方法或者添加一些方法就可以实现自己需要的集合类,工作量大大降低。

    Iterator接口

    Iterator接口,这是一个用于遍历集合中元素的接口,主要包含hashNext(),next(),remove()三种方法。它的一个子接口LinkedIterator在它的基础上又添加了三种方法,分别是add(),previous(),hasPrevious()。也就是说如果是先Iterator接口,那么在遍历集合中元素的时候,只能往后遍历,被遍历后的元素不会在遍历到,通常无序集合实现的都是这个接口,比如HashSet,HashMap;而那些元素有序的集合,实现的一般都是LinkedIterator接口,实现这个接口的集合可以双向遍历,既可以通过next()访问下一个元素,又可以通过previous()访问前一个元素,比如ArrayList。

    Collection接口(集合的最大接口)

    Collection定义了集合框架的共性功能。

        集合 - 图3
    add方法的参数类型是Object。以便于接收任意类型对象。
    集合中存储的都是对象的引用(地址)。

    List

    可以存放重复的内容

    特有方法

    凡是可以操作角标的方法都是该体系特有的方法。集合 - 图4

    ArrayList

    线程不安全,查询速度快

    Vector

    线程安全,但速度慢,已被ArrayList替代

    LinkedList

    链表结果,增删速度快

    Set

    元素是无序(存入和取出的顺序不一定一致),元素不可以重复。
    不能存放重复的内容,所以的重复内容靠hashCode()和equals()两个方法区分

    HashSet

    底层数据结构是哈希表。是线程不安全的。不同步。
    HashSet是如何保证元素唯一性的呢?
    是通过元素的两个方法,hashCode和equals来完成。
    如果元素的HashCode值相同,才会判断equals是否为true。
    如果元素的hashcode值不同,不会调用equals。
    注意,对于判断元素是否存在,以及删除等操作,依赖的方法是元素的hashcode和equals方法。

    TreeSet

    有序的存放,线程不安全
    通过compareTo或者compare方法来保证元素的唯一性,元素以二叉树的形式存放。

    Queue

    队列接口

    SortedSet

    可以对Set集合中的数据进行排序

    Map接口

    Correction、Set、List接口都属于单值的操作,而Map中的每个元素都使用key——>value的形式存储在集合中。
    该集合存储键值对。一对一对往里存。而且要保证键的唯一性。

    HashMap

    底层是哈希表数据结构,允许使用 null 值和 null 键,该集合是不同步的。将hashtable替代,jdk1.2.效率高。

    TreeMap

    底层是二叉树数据结构。线程不同步。可以用于给map集合中的键进行排序。

    各种集合的比较

    | 接口/实现类 | | 是否有序 | 是否允许元素重复 | | :—-: | :—-: | :—-: | —- | | Collection | | 否 | 是 | | List | | 是 | 是 | | Set | AbstractSet | 否 | 否 | | | HashSet | | | | | TreeSet | 是 | | | Map | AbstractMap | 否 | 使用key-value来映射和存储数据,key必须唯一,value可以重复 | | | HashMap | | | | | TreeMap | 是(用二叉树排序) | |

总结

List:add/remove/get/set。

  1. ArrayList:其实就是数组,容量一大,频繁增删就是噩梦,适合随机查找;
  2. LinkedList:增加了push/[pop|remove|pull],其实都是removeFirst;
  3. Vector:历史遗留产物,同步版的ArrayList,代码和ArrayList太像;
  4. Stack:继承自Vector。Java里其实没有纯粹的Stack,可以自己实现,用组合的方式,封装一下LinkedList即可;
  5. Queue:本来是单独的一类,不过在SUN的JDK里就是用LinkedList来提供这个功能的,主要方法是offer/pull/peek,因此归到这里。

    Set:add/remove。可以用迭代器或者转换成list。

  6. HashSet:内部采用HashMap实现的;

  7. LinkedHashSet:采用LinkedHashMap实现;
  8. TreeSet:TreeMap。

    Map:put/get/remove。

  9. HashMap/HashTable:散列表,和ArrayList一样采用数组实现,超过初始容量会对性能有损耗;

LinkedHashMap:继承自HashMap,但通过重写嵌套类HashMap.Entry实现了链表结构,同样有容量的问题;

  1. Properties:是继承的HashTable。

    顺便说一下Arrays.asList,这个方法的实现依赖一个嵌套类,这个嵌套类也叫ArrayList!

    集合的遍历

    4种常见的遍历方式

  • Iterator: 迭代输出,使用最多的输出方式
  • ListIterator: Iterator的子接口,专门用于输出List中的内容

集合 - 图5
集合 - 图6
image.png

  • Enumeration
  • foreach

  在迭代时,不可以通过集合对象的方法操作集合中的元素,因为会发生ConcurrentModificationException异常。所以,在迭代器时,只能用迭代器的放过操作元素,可是Iterator方法是有限的,只能对元素进行判断,取出,删除的操作,如果想要其他的操作如添加,修改等,就需要使用其子接口,ListIterator。该接口只能通过List集合的listIterator方法获取。

ListIterator迭代时的并发修改异常

在迭代集合的过程当中,是不允许直接修改集合结构的
比如添加,删除元素(个数的改变)
如果改变了,就会造成并发修改异常(ConcurrentModificationException)
image.png

并发修改异常的原因

在获取迭代器时, 会和集合进行关联
为保持两边数据一致,内部会有一个modCount 和 expectedModCount,他们两个默认是相等的

  • modCount:集合记录修改次数
  • expectedModCount:迭代器当中记录集合修改的次数

在我们取元素的时候,都会先做一个判断,判断modCount与expectedModCount是否相等,如果不相等就会抛出一个并发修改异常
image.png

解决并发修改异常

image.png
也可以使用List自己特有的迭代器ListIterator
使用自己的迭代器可以添加,删除修改并且不会造成并发修改异常
image.png

集合工具类Collections

java.util.Collections 是一个工具类,类似于Math类的存在。它包含有各种有关集合操作的静态方法(对集合的搜索、排序、线程安全化等),大多数方法都是用来处理线性表的。此类不能实例化,就像一个工具类,服务于Java的Collection框架。
提供的方法中有可以对list集合进行排序,二分查找等方法。
通常常用的集合都是线程不安全的。因为要提高效率。
如果多线程操作这些集合时,可以通过该工具类中的同步方法,将线程不安全的集合,转换成安全的。

Object类中区分同一对象的问题

在实际开发中经常会碰到区分同一对象的问题,一个完整的类最好覆写Object类的hashCode()、equals()、toString()三个方法。