JAVA集合(容器)类知识点汇总

来源:互联网 发布:流星网络电视下载安装 编辑:程序博客网 时间:2024/06/12 22:11

汇总集合(容器)类的详细知识点

总结写在前面:

  1. Java有多种方式保存对象(应该说是对象的引用)。数组将数字与对象联系起来。保存基本类型的数据,保存数据类型明确的对象,取对象时也不需要对对象进行类型转换。它可以是多维的。数组一旦生成容量不能改变。
  2. Collection保存单一的元素,Map保存相关联的键值对,通过泛型,可以指定容器中存放的类型,这样获取元素时才不必进行类型转换,否则,获取的元素则为Object对象,需要进行强制类型转换。集合类会自动调整尺寸。容器类不能持有基本类型,但是Java的自动拆箱装箱机制会自动执行基本类型到容器中所持有的包装器类型之间的相互转换。
  3. List像数组一样建立数字索引与对象的关联,List是有序的,自动扩充尺寸。
  4. 如果需要进行大量的随机访问,用ArrayList;如果经常插入或删除元素,用LinkedList。
  5. LinkedList提供了方法以支持队列的行为,并且实现了Queue接口。各种Queue和栈的行为,由LinkedList提供支持。
  6. Map是将对象与对象关联起来的键值对(key-value)设计。HashMap用来快速访问;TreeMap按照比较结果升序保存键,保持键始终处于排序状态。LinkedHashMap保持元素插入的顺序,但是通过散列提供了快速访问能力。
  7. Set不会保存重复的元素。HashSet提供最快的查询速度,TreeSet保持元素处于排序状态。LinkedHashSet以插入顺序保存元素。

借用别人的一张集合框架图 (原地址):

这里写图片描述

点线框表示接口,实线框表示普通的类。一共只有四种容器: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只有在遍历到的时候才会检测到

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

使用泛型时的写法:

ArrarList<Apple> apples = new ArrayList<Apple>();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);

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

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

  public static void main(String[] args) {    Collection<Integer> collection =      new ArrayList<Integer>(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<Integer> list = Arrays.asList(16, 17, 18, 19, 20);    list.set(1, 99);  } 

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,不成功返回falseboolean remove(Object o);//将指定对象添加到集合中boolean add(E e);//查看该集合中是否包含指定的对象,包含返回true,不包含返回flaseboolean contains(Object o);//返回集合中存放的对象的个数。返回值为intint size();//移除该集合中的所有对象,清空该集合void clear();//返回一个包含所有对象的iterator对象,用来循环遍历Iterator<E> iterator();//返回一个包含所有对象的数组,类型是ObjectObject[] toArray();//返回一个包含所有对象的指定类型的数组 <T> T[] toArray(T[] a);}

2.Iterator遍历

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

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

3.ListIterator遍历

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

 List<Pet> pets = Pets.arrayList(8); ListIterator<Pet> 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]

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 + "' ");

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<String> list = new ArrayList<String>();    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

2、LinkedList

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

LinkedList<Pet> pets = new LinkedList<Pet>(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

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<T> {  private LinkedList<T> storage = new LinkedList<T>();  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(); }}
public class StackTest {    public static void main(String [] args){        Stack<String> stack = new Stack<String>();        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());    }}//输出结果[我][在, 我][这, 在, 我][里, 这, 在, 我]里---这---在---我---[]

四、Set

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

1、HashSet

public class SetOfInteger {    public static void main(String[] args){        Random rand = new Random(47);        Set<Integer> intset = new HashSet<Integer>();        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]

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

2、TreeSet

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

public class TreeSetOfInteger {    public static void main(String[] arge){        Random rand = new Random(47);        SortedSet<Integer> intset = new TreeSet<Integer>();        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]

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

3、常用方法

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

public class SetOperations {  public static void main(String[] args) {    Set<String> set1 = new HashSet<String>();    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<String> set2 = new HashSet<String>();    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]  }} 

五、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}

如果键不在容器中,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));}

第二种: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());}

推荐使用第二种方式,即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<Integer> queue = new LinkedList<Integer>();    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<Character> qc = new LinkedList<Character>();    for(char c : "Brontosaurus".toCharArray())      qc.offer(c);    printQ(qc);//B r o n t o s a u r u s  }

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

参考博客:
JAVA集合类汇总
Java集合类: Set、List、Map、Queue使用场景梳理
java中的几个集合类

原创粉丝点击