回顾集合体系

集合体系整理

前言

  • 面向对象语言对事物的体现都是以对象的形式,所以为了方便对多个对象进行操作,就需要对对象进行存储
  • 数组虽然可以存储对象,但是长度上固定的
  • 集合的长度是可变的
  • 数组中可以存储基本数据类型
  • 集合中只能存储对象

集合体系图

集合体系图

Iterator接口

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

Collection接口

  • Collection是集合类根接口,衍生出两个子类接口List和Set
  • Collection定义了集合框架的共性功能

Collection接口体系图

List接口

List里存放的对象是有序的,同时也是可以重复的,List关注的是索引,拥有一系列和索引相关的方法,查询速度快。因为往list集合里插入或删除数据时,会伴随着后面数据的移动,所有插入删除数据速度慢。

  • ArrayList:线程不安全,查询速度快,元素有序,可重复
  • Vector:线程安全,但速度慢,已被ArrayList替代
  • LinkedList:链表结构,增删速度快

LinkedList经常用在增删操作较多而查询操作很少的情况下,ArrayList则相反

Set接口

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

  • HashSet:
  1. 底层数据结构是哈希表。是线程不安全的。不同步
  2. 通过元素的两个方法,hashCode和equals来保证唯一性
  3. 如果元素的HashCode值相同,才会判断equals是否为true。
  4. 如果元素的hashcode值不同,不会调用equals。
  5. 无序
  • TreeSet:
  1. 有序
  2. 线程不安全,可以对Set集合中的元素进行排序
  3. 通过compareTo或者compare方法来保证元素的唯一性,元素以二叉树的形式存放。

Map接口

  • Map提供了一种映射关系,元素是以键值对(key-value)的形式存储的,能根据key快速查找value;
  • Map中的键值对以Entry类型的对象实例形式存在;
  • key值不能重复,value值可以重复;
HashMap

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

HashMap原理:
  • HashMap底层就是一个数组结构,数组中的每一项又是一个链表。当新建一个HashMap的时候,就会初始化一个数组。
  • 数组中的每一项又是一个Entry,其中包含了key和value,也就是键值对,另外还包含了一个next的Entry指针
  • 因为持有下一个Entry指针,所以构成链表
  • 往HashMap中put元素的时候,先根据key的hashCode重新计算hash值
  • 根据hash值得到这个元素在数组中的位置(即下标)
  • 如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。
  • 如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上
  • 从HashMap中get元素时,首先计算key的hashCode,找到数组中对应位置的某一元素,然后通过key的equals方法在对应位置的链表中找到需要的元素。
一句话原理总结:

简单来说,HashMap在底层将key-value当作一个整体处理,这个整体就是一个Entry对象。HashMap底层采用一个Entry[]数组来保存所有的key-value,每个Entry包含了key-value,还包含了next的Entry指针,因此构成一个链表。当要存储一个Entry对象时,会根据hash算法决定在数组中的存储位置,再根据equals方法,决定其在该数组位置上的链表的存储位置;在需要取出一个Entry时,也会根据hash算法找到其在这个数组的存储位置,再根据equel方法,从该位置找到对应的Entry对象。

hashmap两种遍历方式

第一种:

1
2
3
4
5
6
7
Map map = new HashMap();
  Iterator iter = map.entrySet().iterator();
  while (iter.hasNext()) {
  Map.Entry entry = (Map.Entry) iter.next();
  Object key = entry.getKey();
  Object val = entry.getValue();
  }

第二种:

1
2
3
4
5
6
 Map map = new HashMap();
  Iterator iter = map.keySet().iterator();
  while (iter.hasNext()) {
  Object key = iter.next();
  Object val = map.get(key);
  }
浅析ConcurrentHashMap
  • public V get(Object key)不涉及到锁,也就是说获得对象时没有使用锁;
  • put、remove方法要使用锁,但并不一定有锁争用,原因在于ConcurrentHashMap将缓存的变量分到多个Segment,每个Segment上有一个锁,只要多个线程访问的不是一个Segment就没有锁争用,就没有堵塞,各线程用各自的锁,ConcurrentHashMap缺省情况下生成16个Segment,也就是允许16个线程并发的更新而尽量没有锁争用;
  • Hashtable对get,put,remove都使用了同步操作,也就是说如果有线程正在遍历集合,其他的线程就暂时不能使用该集合了,这样无疑就很容易对性能和吞吐量造成影响。而ConcurrentHashMap则不同,它只对put,remove操作使用了同步操作,get操作并不影响
  • Hashtable在使用iterator遍历的时候,如果其他线程,包括本线程对Hashtable进行了put,remove等更新操作的话,就会抛出ConcurrentModificationException异常,但如果使用ConcurrentHashMap的话,就不用考虑这方面的问题了
浅析HashMap,HashTable,ConcurrentHashMap
  • HashMap如上所诉,不同步,线程不安全,不使用用与多线程高并发情况下
  • Hashtable,被遗弃的类,线程安全是因为在所有方法上都加了synchronized来实现线程安全,导致多线程访问效率低
  • Synchronized Map(通过Collections.synchronizedMap()来包装一个hashmap)和hashtable区别不大,唯一区别就是没有被遗弃
  • ConcurrentHashMap,默认允许16个线程读写这个map,不像Hashtable和Synchronized Map一样,没有锁整个整个map,而是划分了多个段(Segment),只会锁需要操作的那一段数据

点击查看详细博客

点击查看相关博客

TreeMap

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

集合输出(遍历)

  • Iterator: 迭代输出,使用最多的输出方式
  • ListIterator:是Iterator的子接口,专门用于输出List中的内容。
  • foreach输出:JDK1.5之后提供的新功能,可以输出数组或集合。
  • for循环

集合的工具类

Collections:集合框架的工具类。里面定义的都是静态方法。

  Collections和Collection有什么区别?

    Collection是集合框架中的一个顶层接口,它里面定义了单列集合的共性方法。

      它有两个常用的子接口,

      ——List:对元素都有定义索引。有序的。可以重复元素。

      ——Set:不可以重复元素。无序。

    Collections是集合框架中的一个工具类。该类中的方法都是静态的。

      提供的方法中有可以对list集合进行排序,二分查找等方法。

      通常常用的集合都是线程不安全的。因为要提高效率。

      如果多线程操作这些集合时,可以通过该工具类中的同步方法,将线程不安全的集合,转换成安全的。

总结

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。

    1,HashSet:内部采用HashMap实现的;

    2,LinkedHashSet:采用LinkedHashMap实现;

    3,TreeSet:TreeMap。

  Map:put/get/remove。

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

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

    3,Properties:是继承的HashTable。

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

手写集合

ArrayList
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
/** 
* 手写ArrayList
*/
public class MyArrayList{


private Object[] value = null;

private int size = 0;

MyArrayList() {
value = new Object[10];
}


public boolean add(Object obj){
if(size == value.length)
expansion();
value[size++]=obj;
return true;
}


public Object get(int index){
return value[index];
}

public void remove(Object obj){
Object[] obj2 = new Object[size];
int index = 0;
int id = 0;
for (int i = 0; i <= size; i++) {
if(!(value[i].toString().equals(obj.toString()))){
obj2[index] = value[i];
index ++;
}else{
id ++ ;
if(id == 1)
size --;
else{
obj2[index] = value[i];
index ++;
}
}
}
value = obj2;
}

@SuppressWarnings("null")
public void set(int index,Object obj){
Object[] newObj = new Object[size];;
for (int i = 0; i < size; i++) {
if(i == index)
newObj[i] = obj;
else
newObj[i] = value[i];
}
value = newObj;
}

public int size(){
return size;
}

private boolean expansion() {
Object[] temp = new Object[value.length + 5];
temp = value.clone();
/**
* 注意:clone只对一维数组起作用,而不能用于二维数组, 因为java没有二维数组的概念,而只有数组的数组,二维
* 数组存储的是几个一维数组的引用,而使用clone也只是 拷贝了这几个引用,说白了还是原来那几个一维数组对象。
* 如果想用于二维数组,那么就遍历其中的一维数组,挨个 拷贝一维数组到目标二维数组中的一维数组下。
*/
value = temp;
return true;
}


public void clear(){
size = 0;
value = null;
}

public static void main(String[] args) {
MyArrayList ma = new MyArrayList();
ma.add("hello");
ma.add("world");
ma.add("java");
System.out.println(ma.get(1));
System.out.println(ma.size());
ma.set(1, "new");
System.out.println(ma.get(1));
System.out.println(ma.size());
}

}
  • LinkedList
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
public class MyLinkedList<AnyType> {
private static class Node<AnyType>{
public AnyType data;
public Node<AnyType> prev;
public Node<AnyType> next;
public Node(AnyType d, Node<AnyType> p, Node<AnyType> n){
data = d;
prev = p;
next = n;
}
}

private int theSize;
private int modCount;
private Node<AnyType> beginMarker;
private Node<AnyType> endMarker;

public MyLinkedList(){
clear();
}

public void clear(){
beginMarker = new Node<AnyType>(null, null, null);
endMarker = new Node<AnyType>(null, beginMarker, null);
beginMarker.next = endMarker;
theSize = 0;
}

public int size(){
return theSize;
}
public boolean add(AnyType x){
add(size(), x);
return true;
}

public void add(int idx, AnyType x){
addBefore(getNode(idx), x);
}

public AnyType get(int idx){
return getNode(idx).data;
}

private void addBefore(Node<AnyType> p, AnyType x){
Node<AnyType> newNode = new Node<AnyType>(x, p.prev, p);
newNode.prev.next = newNode;
p.prev = newNode;
theSize++;
modCount++;
}

private Node<AnyType> getNode(int idx){
Node<AnyType> p;

if(idx < 0 || idx > size()){
System.out.println("IndexOutOfBoundsException");
}

if(idx <= size()/2){
System.out.println(idx);
p = beginMarker.next;
for(int i = 0; i < idx; i++){
p = p.next;
}
}else{
p = endMarker;
for(int i = size(); i > idx; i--){
p = p.prev;
}
}

return p;

}

public boolean find(AnyType x){
Node<AnyType> p = beginMarker.next;
for(int i = 0; i < size(); i++){
if(p.data == x){
return true;
}
p = p.next;
}
return false;
}


public String toString(){
String s = "";
Node<AnyType> p = beginMarker.next;
for(int i = 0; i < size(); i++){
s += p.data +",";
p = p.next;
}
return s;
}


}
文章目录
  1. 1. 前言
  2. 2. 集合体系图
  3. 3. Iterator接口
  4. 4. Collection接口
    1. 4.1. List接口
    2. 4.2. Set接口
  5. 5. Map接口
    1. 5.1. HashMap
      1. 5.1.1. HashMap原理:
      2. 5.1.2. 一句话原理总结:
      3. 5.1.3. hashmap两种遍历方式
      4. 5.1.4. 浅析ConcurrentHashMap
      5. 5.1.5. 浅析HashMap,HashTable,ConcurrentHashMap
    2. 5.2. TreeMap
  6. 6. 集合输出(遍历)
  7. 7. 集合的工具类
  8. 8. 总结
  9. 9. 手写集合
    1. 9.1. ArrayList