Java基础--集合

954 阅读12分钟

集合按照其存储结构可以分为两大类,分别是单列集合java.util.Collection和双列集合java.util.Map

一、Collection

Collection接口是最基本的集合接口,它不提供直接的实现,它代表的是一种规则,它所包含的元素都必须遵循一条或者多条规则。如有些允许重复有些则不行、有些必须要按照顺序插入而有些则是散列,有些支持排序有些不支持。        此外Collection是单列集合类的根接口。它有两个重要的子接口,分别是List和Set。

Collection 常用功能

Collection是所有单列集合的父接口,因此在Collection中定义了单列集合(List和Set)通用的一些方法,这些方法可用于操作所有的单列集合。方法如下:

  • public boolean add(E e): 把给定的对象添加到当前集合中 。

  • public void clear() :清空集合中所有的元素。

  • public boolean remove(E e): 把给定的对象在当前集合中删除。

  • public boolean contains(E e): 判断当前集合中是否包含给定的对象。

  • public boolean isEmpty(): 判断当前集合是否为空。

  • public int size(): 返回集合中元素的个数。

  • public Object[] toArray(): 把集合中的元素,存储到数组中。

二、List接口

List集合代表一个有序、可重复集合,集合中每个元素都有其对应的顺序索引。所有的元素是以一种线性方式进行存储的,在程序中可以通过索引来访问集合中的指定元素。另外,List集合还有一个特点就是元素有序,即元素的存入顺序和取出顺序一致。        

List作为Collection集合的子接口,不但继承了Collection接口中的全部方法,而且还增加了一些根据元素索引来操作集合的特有方法,如下:

  • public void add(int index, E element): 将指定的元素,添加到该集合中的指定位置上。

  • public E get(int index):返回集合中指定位置的元素。

  • public E remove(int index): 移除列表中指定位置的元素, 返回的是被移除的元素。

  • public E set(int index, E element):用指定元素替换集合中指定位置的元素,返回值的更新

实现List接口的集合主要有:ArrayList、LinkedList、Vector、Stack。

(1)ArrayList

 ArrayList是一个动态数组,是List类的典型实现。每一个ArrayList都有一个初始容量(10),该容量代表了数组的大小。随着元素增加,容器也会不断的扩容。最好预估一个合适初始容量,避免过多的进行扩容操作降低效率。 ArrayList擅长于随机访问,元素增删慢,查找快,由于日常开发中使用最多的功能为查询数据、遍历数据,所以是最常用的集合。。同时ArrayList是非同步的。

List list = Collections.synchronizedList( new ArrayList() );//使用Collections工具类实现ArrayList的同步(通常情况下不使用Vector)

ps:**扩容时机:**当数组的大小大于初始容量的时候(比如初始为10,当添加第11个元素的时候),就会进行扩容,新的容量为旧的容量的1.5倍。**扩容方式:**扩容的时候,会以新的容量建一个原数组的拷贝,修改原数组,指向这个新数组,原数组被抛弃,会被GC回收。

(2)LinkedList

LinkedList是一个双向链表,LinkedList的实现机制与ArrayList的实现机制完全不同,ArrayLiat内部以数组的形式保存集合的元素,所以随机访问集合元素有较好的性能;LinkedList内部以链表的形式保存集合中的元素,所以随机访问集合中的元素性能较差,**但在插入删除元素时有较好的性能。**与ArrayList一样,LinkedList也是非同步的

List list = Collections.synchronizedList(new LinkedList(…));//构造同步LinkedList

除了可以根据索引访问集合元素外,LinkedList还实现了Deque接口,可以当作双端队列来使用,也就是说,既可以当作“栈”使用,又可以当作队列使用。

而且LinkedList提供了大量首尾操作的方法:

  • public void addFirst(E e):将指定元素插入此列表的开头。

  • public void addLast(E e):将指定元素添加到此列表的结尾。

  • public E getFirst():返回此列表的第一个元素。

  • public E getLast():返回此列表的最后一个元素。

  • public E removeFirst():移除并返回此列表的第一个元素。

  • public E removeLast():移除并返回此列表的最后一个元素。

  • public E pop():从此列表所表示的堆栈处弹出一个元素。

  • public void push(E e):将元素推入此列表所表示的堆栈。

  • public boolean isEmpty():如果列表不包含元素,则返回true。

ps:

arraylist和linkedlist区别

1.对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。
2.对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。
这一点要看实际情况的。若只对单条数据插入或删除,ArrayList的速度反而优于LinkedList。但若是批量随机的插入删除数 据,LinkedList的速度大大优于ArrayList. 因为ArrayList每插入一条数据,要移动插入点及之后的所有数据。

(3)Vector

Vector与ArrayList一样,也是通过数组实现的,不同的是它支持线程的同步,即某一时刻只有一个线程能够写Vector,避免多线程同时写而引起的不一致性,但实现同步需要很高的花费,因此,访问它比访问ArrayList慢。

ps:如果集合中的元素的数目大于目前集合数组的长度时,vector增长率为目前数组长度的100%,而arraylist增长率为目前数组长度的50%.如过在集合中使用数据量比较大的数据,用vector有一定的优势。

(4)Stack

Stack继承自Vector,实现一个后进先出的堆栈。Stack提供5个额外的方法使得Vector得以被当作堆栈使用。基本的push和pop 方法,还有peek方法得到栈顶的元素,empty方法测试堆栈是否为空,search方法检测一个元素在堆栈中的位置。Stack刚创建后是空栈。

三、Set接口

同样继承自Collection接口,它与Collection接口中的方法基本一致,并没有对Collection接口进行功能上的扩充,只是比Collection接口更加严格了。与List接口不同的是,Set接口中元素无序,并且都会以某种规则保证存入的元素不出现重复

Set集合几个常用实现类:

(1)HashSet类

HashSet是Set集合最常用实现类。HashSet是按照hash算法来存储元素的。它所存储的元素也是是不可重复的,并且元素都是无序的(即存取顺序不一致)。  HashSet是根据对象的哈希值来确定元素在集合中的存储位置,因此具有良好的存取和查找性能。保证元素唯一性的方式依赖于:hashCode与equals方法。

它内部元素的顺序是由哈希码来决定的,所以它不保证set 的迭代顺序;特别是它不保证该顺序恒久不变。另外它不是线程同步的。

HashSet查找原理如下:

  基于HashSet以上的存储原理,在查找元素时,HashSet先计算元素的HashCode值(也就是调用对象的hashCode方法的返回值),然后直接到hashCode值对应的位置去取出元素即可,这就是HashSet速度很快的原因。

  重写hashCode()方法的基本原则如下:

  • 在程序运行过程中,同一个对象的hashCode()方法返回值应相同。
  • 当两个对象通过equals()方法比较返回true时,这两个对象的hashCode()方法返回值应该相等。
  • 对象中用作equals()方法比较标准的实例变量,都应该用于计算hashCode值。

**ps:  **

HashSet集合存储数据的结构(哈希表)

JDK1.8之前,哈希表底层采用数组+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。     哈希表探测方法公式【Hash(key) = key%表长】

      

哈希表是由数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的

(2)LinkedHashSet类

HashSet保证元素唯一,可是元素存放进去是没有顺序的,那么我们要保证有序因此有了LinkedHashSet。LinkedHashSet是HashSet的一个子类,具有HashSet的特性,也是根据元素的hashCode值来决定元素的存储位置。但它使用链表维护元素的次序,元素的顺序与添加顺序一致。由于LinkedHashSet需要维护元素的插入顺序,因此性能略低于HashSet,但在迭代访问Set里的全部元素时由很好的性能。

(3)TreeSet类

基于TreeMap,生成一个总是处于排序状态的set,内部以TreeMap来实现。它是使用元素的自然顺序对元素进行排序或者根据创建Set 时提供的 Comparator 进行排序,具体取决于使用的构造方法。另外它**不是线程同步的。**采用红黑树的数据结构来存储集合元素,TreeSet支持两种排序方法:自然排序和定制排序,默认采用自然排序。

(4)EnumSet类

 EnumSet是一个专为枚举类设计的集合类,不允许添加null值。EnumSet的集合元素也是有序的。枚举的专用Set。所有的元素都是枚举类型。

(5)各Set实现类的性能分析

  HashSet的性能比TreeSet的性能好(特别是添加,查询元素时),因为TreeSet需要额外的红黑树算法维护元素的次序,如果需要一个保持排序的Set时才用TreeSet,否则应该使用HashSet。

  LinkedHashSet是HashSet的子类,由于需要链表维护元素的顺序,所以插入和删除操作比HashSet要慢,但遍历比HashSet快。

  EnumSet是所有Set实现类中性能最好的,但它只能 保存同一个枚举类的枚举值作为集合元素。

以上几个Set实现类都是线程不安全的,如果多线程访问,必须手动保证集合的同步性。

四、Map接口

Map与List、Set接口不同。Map中的集合,元素是成对存在的(理解为夫妻)。每个元素由键与值两部分组成,通过键可以找对所对应的值,提供了key到Value的映射。因此不能存在相同的key值,当然value值可以相同。

**ps:  **Collection中的集合称为单列集合,Map中的集合称为双列集合

Map接口中定义了很多方法,常用的如下:

  • public V put(K key, V value): 把指定的键与指定的值添加到Map集合中。

  • public V remove(Object key): 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值。

  • public V get(Object key) 根据指定的键,在Map集合中获取对应的值。

  • boolean containsKey(Object key) 判断集合中是否包含指定的键。

  • public Set<K> keySet(): 获取Map集合中所有的键,存储到Set集合中。

  • public Set<Map.Entry<K,V>> entrySet(): 获取到Map集合中所有的键值对对象的集合(Set集合)。

tips:

使用put方法时,若指定的键(key)在集合中没有,则没有这个键对应的值,返回null,并把指定的键值添加到集合中;

若指定的键(key)在集合中存在,则返回值为集合中键对应的值(该值为替换前的值),并把指定键所对应的值,替换成指定的新值。 

(1)HashMap

HashMap<K,V>:存储数据采用的哈希表结构,元素的存取顺序不能保证一致。由于要保证键的唯一、不重复,需要重写键的hashCode()方法、equals()方法。   底层哈希表数据结构实现,查找对象时通过哈希函数计算其位置,它是为快速查询而设计的,其不是同步的,且允许空值作为键和值。

工作原理:

  HashMap通过put()和get()方法存储和获取对象。当我们将键值对传递给put()方法时,它调用建对象的hashCode()方法来计算hashCode值,然后找到bucket位置来储存值对象。当获取对象时,通过建对象的equals()方法找到正确的键值对,返回对象。HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会存储在哈希数数组的链表的下一个节点中。

(2)HashTable

也是以哈希表数据结构实现的,解决冲突时与HashMap也一样也是采用了散列链表的形式,不过性能比HashMap要低。其不允许空值作为键和值。

HashMap与Hashtable区别:

  • HashMap是线程不安全,HashTable是线程安全的。
  • HashMap可以使用null值最为key或value;Hashtable不允许使用null值作为key和value,如果把null放进HashTable中,将会发生空指针异常。

ps

2.1)Properties

Properties类时Hashtable类的子类,它相当于一个key、value都是String类型的Map,主要用于读取配置文件。

(3)LinkedHashMap实现类

LinkedHashMap<K,V>:HashMap下有个子类LinkedHashMap,存储数据采用的哈希表结构+链表结构。通过链表结构可以保证元素的存取顺序一致;通过哈希表结构可以保证的键的唯一、不重复,需要重写键的hashCode()方法、equals()方法。

(4)TreeMap

‘键’以自然顺序或自定义比较器来规则排序,内部以red-black(红-黑)树数据结构实现,实现了SortedMap接口。它不是线程同步的

五、Queue

队列是一种特殊的线性表,它只允许在表的前端进行删除操作,而在表的后端进行插入操作。它主要分为两大类,一类是阻塞式队列,队列满了以后再插入元素则会抛出异常,主要包括ArrayBlockQueue、PriorityBlockingQueue、LinkedBlockingQueue。另一种队列则是双端队列,支持在头、尾两端插入和移除元素,主要包括:**ArrayDeque **LinkedBlockingDeque、LinkedList。  LinkedList类实现了Queue接口,因此我们可以把LinkedList当成Queue来用。