首页>JAVA>正文

【上海Java培训】集合类集合汇总详解

时间:2018-03-09 15:15:02   来源:上海尚学堂   阅读:

借用一张集合框架图 

点线框表示接口,实线框表示普通的类。一共只有四种容器:Map、List、Set、Queue,它们各有2-3个实现版本,常用的容器类四个:ArrayList、LinkedList、HashMap、HashSet。任意的Collection都可以生成Iterator,List可以生成ListIterator(也能生成Iterator,因为List继承自Collection)。

常用集合的元素是否有序,元素是否重复的区别:


一、泛型和类型安全的容器,添加一组元素

未使用泛型时的不安全写法:

class Apple{
    public String color;
}

class Orange{
    public int id;
}

ArrarList apples = new ArrayList();
for(int i = 0;i < 3;i++)
    apples.add(new Apple());
apples.add(new Orange());
for(int i = 0;i < apples.size();i++)
    Apple a = (Apple)apples.get(i);//Orange只有在遍历到的时候才会检测到
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

Java中如果一个类没有显式的声明继承自哪个类,那么自动继承自Object(有点万事万物皆对象的意思)。Apple和Orange是有区别的,它们除了是Object之外没有任何共性。因为ArrayList保存的是Object,因此既可以添加Apple,又可以添加Orange,无论在编译期还是运行时都不会有问题。当在使用ArrayList的get()方法来读取对象时,得到的只是Object引用,必须将其强制转型为Apple。当读取到ArrayList中的Orange对象时,试图将Orange转型为Apple就会报错。这样就很不安全了。

使用泛型时的写法:

ArrarList apples = new ArrayList();
for(int i = 0;i < 3;i++)
    apples.add(new Apple());
//apples.add(new Orange());
for(int i = 0;i < apples.size();i++)
    Apple a = apples.get(i);
for(Apple c : apples)
    System.out.print(c.color);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

通过使用泛型可以在编译期防止将错误类型的对象放置到容器中了。在将元素从List中取出来时也不需要再进行类型转换了,List知道它保存的是什么类型。

添加一组元素: 
可以在一个Collection中添加一组元素。 
Arrays.asList()方法接受一个数组或是一个使用逗号分隔的元素列表,并将其转换成一个Lsit对象。 
Collections.addAll()方法接受一个Collection对象,以及一个数组或是一个逗号分隔的列表,并将元素添加到Collection中。

  public static void main(String[] args) {
    Collection collection =
      new ArrayList(Arrays.asList(1, 2, 3, 4, 5));
    Integer[] moreInts = { 6, 7, 8, 9, 10 };
    collection.addAll(Arrays.asList(moreInts));
    Collections.addAll(collection, 11, 12, 13, 14, 15);
    Collections.addAll(collection, moreInts);
    List list = Arrays.asList(16, 17, 18, 19, 20);
    list.set(1, 99);
  } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

collection.addAll()成员方法只能接受另一个Collection对象作为参数,因此它不如Arrays.asList()方法和Collections.addAll()方法灵活,这两个方法使用的都是可变参数列表。


二、Collection与Iterator与ListIterator与Foreach

Collection是描述所有序列容器的共性的接口,Java中没有提供这个接口的直接的实现类。但是却让其被继承产生了三个接口,就是Set、List、Queue。Java用迭代器而不是Collection来表示容器之间的共性,实现了Collection就意味着需要提供iterator()方法,可逐一访问Collection中每一个元素。

1.Collection

public interface Collection<E> extends Iterable<E> {
//将指定的对象从集合中移除,移除成功返回true,不成功返回false
boolean remove(Object o);
//将指定对象添加到集合中
boolean add(E e);
//查看该集合中是否包含指定的对象,包含返回true,不包含返回flase
boolean contains(Object o);
//返回集合中存放的对象的个数。返回值为int
int size();
//移除该集合中的所有对象,清空该集合
void clear();
//返回一个包含所有对象的iterator对象,用来循环遍历
Iterator iterator();
//返回一个包含所有对象的数组,类型是Object
Object[] toArray();
//返回一个包含所有对象的指定类型的数组
  T[] toArray(T[] a);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

2.Iterator遍历

迭代器是一个对象,它的工作是遍历并选择序列中的对象。Iterator只能向前移动。

  1. 使用iterator()方法要求容器返回一个Iterator。
  2. 使用next()获得序列中的下一个元素。
  3. 使用hasNext()检查序列中是否还有元素。
  4. 使用remove()将迭代器新近返回的元素删除。
Iterator it = collection.iterator(); 
  while(it.hasNext()) {
   Object obj = it.next(); 
  }
  • 1
  • 2
  • 3
  • 4

3.ListIterator遍历

ListIterator是一个更加强大的Iterator子类型,它只能用于各种List类的访问。ListIterator可以双向移动。它还可以产生相对于迭代器在列表中指向当前位置的前一个和后一个元素的索引,并且可以使用set()方法替换它访问过的最后一个元素。可以通过调用listIterator()方法产生一个指向List开始出的ListIterator,还可以通过调用listIterator(n)方法创建一个一开始就指向列表索引为n的元素处的ListIterator。 
*省略了Pets相关代码下面的Pets.arrayList(8)为生成指定个数的宠物List集合。

 List pets = Pets.arrayList(8);
 ListIterator it = pets.listIterator();
 while(it.hasNext())
   System.out.print(it.next() + ", " + it.nextIndex() +
     ", " + it.previousIndex() + "; ");//Rat, 1, 0; Manx, 2, 1; Cymric, 3, 2; Mutt, 4, 3; Pug, 5, 4; Cymric, 6, 5; Pug, 7, 6; Manx, 8, 7;
 while(it.hasPrevious())
   System.out.print(it.previous().id() + " ");//7 6 5 4 3 2 1 0
 System.out.println(pets);//[Rat, Manx, Cymric, Mutt, Pug, Cymric, Pug, Manx]
 it = pets.listIterator(3);//从索引3开始
 while(it.hasNext()) {
   it.next();
   it.set(Pets.randomPet());//替换在列表中从位置3开始向前的所有元素
 }
 System.out.println(pets);//[Rat, Manx, Cymric, Cymric, Rat, EgyptianMau, Hamster, EgyptianMau]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

4.foreach遍历

foreach语法主要用于遍历数组,但是它也可以应用于任何Collection对象(但是不包括各种Map)。

Collection<String> cs = new LinkedList<String>();
Collection.addALL(cs,"Take the long way home".split(" "));
for(String s : cs)
    System.out.print("'" + s + "' ");
  • 1
  • 2
  • 3
  • 4

5.for循环遍历

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


三、List

List里存放的对象是有序的,按元素的插入顺序设置元素的索引,元素可以重复,因为往list集合里插入或删除数据时,会伴随着后面数据的移动,所以插入删除数据速度慢。 
有2中类型的List:

  • ArrayList,随机访问元素速度快,但是插入和移除元素速度慢。
  • LinkedList,随机访问、查询元素速度慢,增删元素速度快。
  • ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。对于随机访问get和set,ArrayList优于LinkedList,因为LinkedList要移动指针。对于新增和删除操作add和remove,LinkedList比较占优势,因为ArrayList要移动数据。 这一点要看实际情况的。若只对单条数据插入或删除,ArrayList的速度反而优于LinkedList。但若是批量随机的插入删除数据,LinkedList的速度大大优于ArrayList. 因为ArrayList每插入一条数据,要移动插入点及之后的所有数据。

contains()方法用来确定某个对象是否在列表中,remove()移除对象,indexOf()返回对象的位置索引。

1、ArrayList

List list = new ArrayList();
    list.add("啊啊啊!");
    list.add("别别别!");
    list.add("吃吃吃!");
    list.add("对对对");
    list.add("嗯嗯嗯");
    System.out.println(list.size());//5
    System.out.println(list.contains("abcde"));//false
    System.out.println(list.remove("对对对"));
    System.out.println(list.size());//4
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

2、LinkedList

LinkedList还添加了可以使其用作栈,队列和双端队列的方法。

LinkedList pets = new LinkedList(Pets.arrayList(5));
    print(pets);//[Rat, Manx, Cymric, Mutt, Pug]
    print(pets.getFirst());//Rat
    print(pets.element());//Rat
    print(pets.peek());//Rat
    print(pets.remove());//Rat
    print(pets.removeFirst());//Manx
    print(pets.poll());//Cymric
    print(pets);//[Mutt, Pug]
    pets.addFirst(new Rat());
    print(pets);//[Rat, Mutt, Pug]
    pets.offer(Pets.randomPet());
    print(pets);//[Rat, Mutt, Pug, Cymric]
    pets.add(Pets.randomPet());
    print(pets);//[Rat, Mutt, Pug, Cymric, Pug]
    pets.addLast(new Hamster());
    print(pets);//[Rat, Mutt, Pug, Cymric, Pug, Hamster]
    print(pets.removeLast());//Hamster
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

getFirst()与element()一样,返回列表的头,并不移除它,如果List为空,抛出NoSuchElementException。peek()方法与这两个方法唯一的差异是列表为空时返回null。 
removeFirst()与remove()是一样的,移除并返回列表的头,在列表为空时抛出NoSuchElementException。poll()方法与这两个方法唯一的差异是在列表为空时返回null。 
addFirst()与add()与addLast()相同,将某个元素插入到列表的尾部。 
removeLast()移除并返回列表的最后一个元素。 
此外,LinkedList提供了方法以支持队列的行为,并且实现了Queue接口,稍后记录Queue。

3、Stack

“栈”通常是指“后进先出LIFO”的容器。最后“压入”栈的元素,最先“弹出”栈。 
LinkedList具有能够直接实现栈的所有功能的方法,可以直接将LinkedList作为栈使用。 
写一个自己的Stack:

public class Stack {
  private LinkedList storage = new LinkedList();
  public void push(T v) { storage.addFirst(v); }//把元素插入尾部
  public T peek() { return storage.getFirst(); }//返回第一个元素,不移除它
  public T pop() { return storage.removeFirst(); }//移除并返回列表的第一个元素
  public boolean empty() { return storage.isEmpty(); }
  public String toString() { return storage.toString(); }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
public class StackTest {
    public static void main(String [] args){
        Stack stack = new Stack();
        for(String s : "我 在 这 里".split(" ")){
            stack.push(s);
            System.out.println(stack.toString());
        }
        while (!stack.empty())
                System.out.print(stack.pop() + "---");
        System.out.println();
        System.out.println(stack.toString());
    }
}
//输出结果
[我]
[在, 我]
[这, 在, 我]
[里, 这, 在, 我]
里---这---在---我---
[]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

四、Set

Set不保存重复的元素。Set最常被使用的是测试归属性,可以很容易的询问某个对象是否在某个Set中。查找就成了Set最重要的操作,通常会选择一个HashSet的实现,它专门对快速查找进行的优化。

1、HashSet

public class SetOfInteger {
    public static void main(String[] args){
        Random rand = new Random(47);
        Set intset = new HashSet();
        for (int i = 0;i < 10000;i++)
            intset.add(rand.nextInt(30));
        System.out.println(intset);
    }
}
//输出结果
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 16, 19, 18, 21, 20, 23, 22, 25, 24, 27, 26, 29, 28]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

每一个数字只有一个实例出现在结果中,输出的顺序也是没有规律的。这是因为HashSet使用了散列,以后在深入学习。TreeSet将元素存储在红-黑书数据结果中,而HashSet使用的是散列函数。LinkedList因为查询速度的原因也是用了散列。

2、TreeSet

如果想对结果排序,使用TreeSet:

public class TreeSetOfInteger {
    public static void main(String[] arge){
        Random rand = new Random(47);
        SortedSet intset = new TreeSet();
        for (int i = 0;i < 10000;i++)
            intset.add(rand.nextInt(30));
        System.out.println(intset);
    }
}
//输出结果
[0, 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]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

TreeSet按照比较结果的升序保存对象。

3、常用方法

最常见的操作就是用contains()测试Set的归属性。

public class SetOperations {
  public static void main(String[] args) {
    Set set1 = new HashSet();
    Collections.addAll(set1,"A B C D E F G H I J K L".split(" "));
    set1.add("M");
    print(set1.contains("H"));//true
    print(set1.contains("N"));//false
    Set set2 = new HashSet();
    Collections.addAll(set2, "H I J K L".split(" "));
    print(set1.containsAll(set2));//true
    set1.remove("H");
    print(set1);//[D, K, C, B, L, G, I, M, A, F, J, E]
    print(set1.containsAll(set2));//false
    set1.removeAll(set2);
    print(set1);//[D, C, B, G, M, A, F, E]
    Collections.addAll(set1, "X Y Z".split(" "));
    print(set1);//[Z, D, C, B, G, M, A, F, Y, X, E]
  }
} 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

五、Map

Map集合中存储的是键值对,键不能重复,值可以重复。根据键得到值,对map集合遍历时先得到键的set集合,对set集合进行遍历,得到相应的值。 
利用Random写一个例子:

public class MapTest{
  public static void main(String[] args) {
    Random rand = new Random(47);
    Map<Integer,Integer> m = new HashMap<Integer,Integer>();
    for(int i = 0; i < 10000; i++) {
      int r = rand.nextInt(20);
      Integer freq = m.get(r);
      m.put(r, freq == null ? 1 : freq + 1);
    }
    System.out.println(m);
  }
}
//输出结果
{0=481, 1=502, 2=489, 3=508, 4=481, 5=503, 6=519, 7=471, 8=468, 9=549, 10=513, 11=531, 12=521, 13=506, 14=477, 15=497, 17=509, 16=533, 19=464, 18=478}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

如果键不在容器中,m.get(r)方法将返回null(表示该数字第一次产生)。否则,get()方法将产生于该键相关联的Integer值,这个值+1。覆盖原来的值,保存到m中。

put(K key, V value) 向集合中添加指定的键值对 
putAll (Map< ? extends K,? extends V>t)把一个Map中的所有键值对添加到该集合 
containsKey(Object key) 如果包含该键,则返回true 
containsValue(Object value) 如果包含该值,则返回true 
get(Object key) 根据键,返回相应的值对象 
keySet() 将该集合中的所有键以Set集合形式返回 
values() 将该集合中所有的值以Collection形式返回 
remove(Object key) 如果存在指定的键,则移除该键值对,返回键所对应的值,如果不存在则返回null 
clear() 移除Map中的所有键值对,或者说就是清空集合 
isEmpty() 查看Map中是否存在键值对 
size()查看集合中包含键值对的个数,返回int类型

HashMap:是最常用的Map,它根据键的HashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度,遍历时,取得数据的顺序是完全随机的。因为键对象不可以重复,所以HashMap最多只允许一条记录的键为Null,允许多条记录的值为Null,是非同步的。在Map 中插入、删除和定位元素,HashMap是最好的选择。 
Hashtable :注意Hashtable中的t是小写的,它是HashMap的线程安全版本,现在已经很少使用。它支持线程的同步,即任一时刻只有一个线程能写Hashtable,因此也导致了Hashtale在写入时会比较慢,它继承自Dictionary类,不同的是它不允许记录的键或者值为null,同时效率较低。 

LinkedHashMap :保存了记录的插入顺序,在用Iteraor遍历LinkedHashMap时,先得到的记录肯定是先插入的,在遍历的时候会比HashMap慢,有HashMap的全部特性。 
TreeMap :能够把它保存的记录根据键排序,默认是按键值的升序排序(自然顺序),也可以指定排序的比较器,当用Iterator遍历TreeMap时,得到的记录是排过序的。不允许key值为空,非同步的,如果你需要得到一个有序的结果你就应该使用TreeMap。

Map与数组和其它的Collection一样,可以很容易的扩展到多维,我们只需将其值设置为Map(这些Map的值可以是其它容器,甚至是其它Map)例如这样:Map< Person,List < Pet >>

Map的遍历: 
第一种:将Map中所有的键存入到set集合中。用迭代方式取出所有的键,再根据get方法。获取每一个键对应的值。取得的键值是没有顺序的。

Iterator it = map.keySet().iterator();//先获取map集合的所有键的set集合,keyset()
while(it.hasNext()){
Object key = it.next();
System.out.println(map.get(key));
}
  • 1
  • 2
  • 3
  • 4
  • 5

第二种:Set< Map.Entry< K,V>> entrySet() 返回此映射中包含的映射关系的 Set 视图。(一个关系就是一个键-值对),就是把(key-value)作为一个整体一对一对地存放到Set集合当中的。Map.Entry表示映射关系。entrySet():迭代后可以e.getKey(),e.getValue()两种方法来取key和value。返回的是Entry接口。

Iterator it = map.entrySet().iterator();
while(it.hasNext()){
Entry e =(Entry) it.next();
System.out.println("键"+e.getKey () + "的值为" + e.getValue());
}
  • 1
  • 2
  • 3
  • 4
  • 5

推荐使用第二种方式,即entrySet()方法,效率较高。对于keySet其实是遍历了2次,一次是转为iterator,一次就是从HashMap中取出key所对于的value。而entryset只是遍历了第一次,它把key和value都放到了entry中,所以快了。两种遍历的遍历时间相差还是很明显的。


六、Queue

队列是一个典型的“先进先出FIFO”的容器,区分于Stack栈的“后进先出LIFO”。即从容器的一端放入元素,从另一端取出。与Stack的实现一样,这里又可以用到LinkedList,LinkedList提供了方法以支持队列的行为,并且实现了Queue接口。LinkedList可以用做Queue的一种实现。通过LinkedList向上转型为Queue。

public static void printQ(Queue queue) {
    while(queue.peek() != null)
      System.out.print(queue.remove() + " ");
    System.out.println();
  }
  public static void main(String[] args) {
    Queue queue = new LinkedList();
    Random rand = new Random(47);
    for(int i = 0; i < 10; i++)
      queue.offer(rand.nextInt(i + 10));
    printQ(queue);//8 1 1 1 5 14 3 1 0 1
    Queue qc = new LinkedList();
    for(char c : "Brontosaurus".toCharArray())
      qc.offer(c);
    printQ(qc);//B r o n t o s a u r u s
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

offer()将一个元素插入到队尾。 
peek()和element()在不移除元素的情况下返回队头,peek()方法在队列为空时返回null,element()会抛出NoSuchElementException异常。 
poll()和remove()方法移除并返回队头,poll()在队列为空时返回null,remove()会抛出NoSuchElementException异常。

 

七、主要实现类区别小结

Vector和ArrayList
1,vector是线程同步的,所以它也是线程安全的,而arraylist是线程异步的,是不安全的。如果不考虑到线程的安全因素,一般用arraylist效率比较高。

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

3,如果查找一个指定位置的数据,vector和arraylist使用的时间是相同的,如果频繁的访问数据,这个时候使用vector和arraylist都可以。而如果移动一个指定位置会导致后面的元素都发生移动,这个时候就应该考虑到使用linklist,因为它移动一个指定位置的数据时其它元素不移动。

4、ArrayList 和Vector是采用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,都允许直接序号索引元素,但是插入数据要涉及到数组元素移动等内存操作,所以索引数据快,插入数据慢,Vector由于使用了synchronized方法(线程安全)所以性能上比ArrayList要差,LinkedList使用双向链表实现存储,按序号索引数据需要进行向前或向后遍历,但是插入数据时只需要记录本项的前后项即可,所以插入数度较快。

arraylist和linkedlist
1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。

2.对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。

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

HashMap与TreeMap
1、 HashMap通过hashcode对其内容进行快速查找,而TreeMap中所有的元素都保持着某种固定的顺序,如果你需要得到一个有序的结果你就应该使用TreeMap(HashMap中元素的排列顺序是不固定的)。

2、在Map 中插入、删除和定位元素,HashMap是最好的选择。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。使用HashMap要求添加的键类明确定义了hashCode()和 equals()的实现。

两个map中的元素一样,但顺序不一样,导致hashCode()不一样。
同样做测试:
在HashMap中,同样的值的map,顺序不同,equals时,false;
而在treeMap中,同样的值的map,顺序不同,equals时,true,说明,treeMap在equals()时是整理了顺序了的。

HashTable与HashMap
1、同步性:Hashtable是线程安全的,也就是说是同步的,而HashMap是线程序不安全的,不是同步的。

2、HashMap允许存在一个为null的key,多个为null的value 。

3、hashtable的key和value都不允许为null。

 

感谢您阅读上海Java培训文章,更多内容和支持请点击 上海Java培训
原文地址:http://blog.csdn.net/Axela30W/article/details/78395687  作者:滅魂
 
分享:0

电话咨询

客服热线服务时间

周一至周五 9:00-21:00

周六至周日 9:00-18:00

咨询电话

021-67690939
15201841284

微信扫一扫