集合类简介
java集合类是用来存放某类对象的。集合类有一个共同特点,就是它们只容纳对象(实际上是对象名,即指向地址的指针)。这一点和数组不同,数组可以容纳对象和简单数据。如果在集合类中既想使用简单数据类型,又想利用集合类的灵活性,就可以把简单数据类型数据变成该数据类型类的对象,然后放入集合中处理,但这样执行效率会降低。
集合类容纳的对象都是Object类的实例,一旦把一个对象置入集合类中,它的类信息将丢失,也就是说,集合类中容纳的都是指向Object类对象的指针。这样的设计是为了使集合类具有通用性,因为Object类是所有类的祖先,所以可以在这些集合中存放任何类而不受限制。当然这也带来了不便,这令使用集合成员之前必须对它重新造型。
集合类是Java数据结构的实现。在编写程序时,经常需要和各种数据打交道,为了处理这些数据而选用数据结构对于程序的运行效率是非常重要的
java集合框架图
一、Collection接口
Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(Elements)。Java SDK不提供直接继承自Collection的类,Java SDK提供的类都是继承自Collection的“子接口”如List和Set。
所有实现Collection接口的类都必须提供两个标准的构造函数:无参数的构造函数用于创建一个空的Collection,有一个 Collection参数的构造函数用于创建一个新的Collection,这个新的Collection与传入的Collection有相同的元素。后 一个构造函数允许用户复制一个Collection。
Iterator,所有的集合类,都实现了Iterator接口,这是一个用于遍历集合中元素的接口,主要包含以下三种方法:
1.hasNext()是否还有下一个元素。
2.next()返回下一个元素。
3.remove()删除当前元素。
1.1List接口
List是列表类型,以线性方式存储对象,自身的方法都与索引有关,个别常用方法如下。
方法 | 返回值 | 方法描述 |
---|---|---|
add(E e) | boolean | 向列表的尾部添加指定的元素(可选操作)。 |
add(int index, E element) | void | 在列表的指定位置插入指定元素(可选操作)。 |
get(int index) | E | 返回列表中指定位置的元素。 |
contains(Object o) | boolean | 如果列表包含指定的元素,则返回 true。 |
isEmpty() | boolean | 如果列表不包含元素,则返回 true。 |
remove(int index) | E | 移除列表中指定位置的元素(可选操作) |
常用类有LinkedList,ArrayList,Vector和Stack。
LinkedList类
LinkedList实现了List接口,允许null元素。此外LinkedList提供额外的get,remove,insert方法在 LinkedList的首部或尾部。这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。
注意LinkedList没有同步方法。如果多个线程同时访问一个List,则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List:
List list = Collections.synchronizedList(new LinkedList(…));
ArrayList类
ArrayList实现了可变大小的数组。它允许所有元素,包括null。ArrayList没有同步。
size,isEmpty,get,set方法运行时间为常数。但是add方法开销为分摊的常数,添加n个元素需要O(n)的时间。其他的方法运行时间为线性。和LinkedList一样,ArrayList也是非同步的(unsynchronized)。
ArrayList和LinkedList在用法上没有区别,但是在功能上还是有区别的。LinkedList经常用在增删操作较多而查询操作很少的情况下,ArrayList则相反。
Vector类
Vector非常类似ArrayList,但是Vector是同步的。由Vector创建的Iterator,虽然和 ArrayList创建的Iterator是同一接口,但是,因为Vector是同步的,当一个Iterator被创建而且正在被使用,另一个线程改变了 Vector的状态(例如,添加或删除了一些元素),这时调用Iterator的方法时将抛出 ConcurrentModificationException,因此必须捕获该异常。
Stack 类
Stack继承自Vector,实现一个后进先出的堆栈。Stack提供5个额外的方法使得Vector得以被当作堆栈使用。基本的push和pop 方法,还有peek方法得到栈顶的元素,empty方法测试堆栈是否为空,search方法检测一个元素在堆栈中的位置。Stack刚创建后是空栈。
由于Vector底层为数组,所以不要轻易使用Stack。实现栈一定要用LinkedList。
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class test {
public static void main(String args[]){
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
Iterator<String> it = list.iterator(); //创建迭代器
while(it.hasNext()){
System.out.print(it.next()+" ");
}
list.add(0,"D"); //向list中插入
System.out.println();
for(String i:list){ //foreach遍历
System.out.print(i+" ");
}
}
}
1.2Set接口
Set是一种不包含重复的元素的Collection,即任意的两个元素e1和e2都有e1.equals(e2)=false,Set最多有一个null元素。很明显,Set的构造函数有一个约束条件,传入的Collection参数不能包含重复的元素。
常用类HashSet LinkedHashSet, TreeSet等
HashSet类
由哈希表(实际上是一个 HashMap 实例)支持。它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变。此类允许使用 null 元素。
注意,此实现不是同步的。如果多个线程同时访问一个哈希 set,而其中至少一个线程修改了该 set,那么它必须 保持外部同步。这通常是通过对自然封装该 set 的对象执行同步操作来完成的。如果不存在这样的对象,则应该使用 Collections.synchronizedSet 方法来“包装” set。最好在创建时完成这一操作,以防止对该 set 进行意外的不同步访问:
Set s = Collections.synchronizedSet(new HashSet(…));
下面看一个例子,用addAll()方法把List集合对象存入到Set集合中并除掉重复值。
import java.util.*;
public class test {
public static void main(String args[]){
List<String> list = new ArrayList<>();
list.add("Student");
list.add("Campus");
list.add("School");
list.add("Campus");
Iterator<String> it = list.iterator();
while(it.hasNext()){
System.out.print(it.next()+" ");
}
System.out.println();
Set<String> set = new HashSet<>();
set.addAll(list);
it = set.iterator();
while(it.hasNext()){
System.out.print(it.next()+" ");
}
}
}
由于Set集合中的对象是无序的,遍历Set集合的结果与插入Set集合的顺序并不相同。
TreeSet类
使用元素的自然顺序对元素进行排序,或者根据创建 set 时提供的 Comparator 进行排序,具体取决于使用的构造方法。
此实现为基本操作(add、remove 和 contains)提供受保证的 log(n) 时间开销。
注意,如果要正确实现 Set 接口,则 set 维护的顺序(无论是否提供了显式比较器)必须与 equals 一致。这是因为 Set 接口是按照 equals 操作定义的,但 TreeSet 实例使用它的 compareTo(或 compare)方法对所有元素进行比较,因此从 set 的观点来看,此方法认为相等的两个元素就是相等的。即使 set 的顺序与 equals 不一致,其行为也是 定义良好的;它只是违背了 Set 接口的常规协定。
注意,此实现不是同步的。
下面举个例子说明如何使用TreeSet
import java.util.Iterator;
import java.util.TreeSet;
public class set implements Comparable<Object>{
String name;
long id;
public set(String name,long id) {
this.name=name;
this.id=id;
}
@Override
public int compareTo(Object o) { //必须重写compareTo方法
set set1=(set)o;
int result = id>set1.id? 1:(id==set1.id? 0:-1);// TODO Auto-generated method stub
return result;
}
public long getId() {
return id;
}
public static void main(String[] args) {
set stu1=new set("李", 123); //数字前加0表示8进制
set stu2=new set("陈", 120);
set stu3=new set("王", 111);
set stu4=new set("马", 122);
TreeSet<set> tree=new TreeSet<>();
tree.add(stu1);
tree.add(stu2);
tree.add(stu3);
tree.add(stu4);
Iterator<set> it=tree.iterator();
while(it.hasNext()) {
set a=(set) it.next();
System.out.println(a.getName() + " " +a.getId());
}
System.out.println();//public NavigableSet<E> headSet(E toElement, boolean inclusive)
it=tree.headSet(stu4).iterator();//返回此set的部分视图,其元素小于(或等于,如果 inclusive 为 true)toElement。
while(it.hasNext()) {
set a=(set) it.next();
System.out.println(a.getName()+" "+a.getId());
}
}
}
LinkedHashSet类
具有可预知迭代顺序的 Set 接口的哈希表和链接列表实现。此实现与 HashSet 的不同之外在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,即按照将元素插入到 set 中的顺序(插入顺序)进行迭代。注意,插入顺序不受在 set 中重新插入的 元素的影响。(如果在 s.contains(e) 返回 true 后立即调用 s.add(e),则元素 e 会被重新插入到 set s 中。)
此实现可以让客户免遭未指定的、由 HashSet 提供的通常杂乱无章的排序工作,而又不致引起与 TreeSet 关联的成本增加。
LinkedHashSet 有两个影响其性能的参数:初始容量 和加载因子。它们与 HashSet 中的定义极其相同。注意,为初始容量选择非常高的值对此类的影响比对 HashSet 要小,因为此类的迭代时间不受容量的影响。
注意,此实现不是同步的。
总之就是LinkedHashSet继承了HashSet的全部特性,元素不重复,快速查找,快速插入,并且新增了一个重要特性,那就是有序,可以保持元素的插入顺序,所以可以应用在对元素顺序有要求的场景中。
二、Map接口
请注意,Map没有继承Collection接口,Map提供key到value的映射。一个Map中不能包含相同的key,每个key只能映射一个 value。Map接口提供3种集合的视图,Map的内容可以被当作一组key集合,一组value集合,或者一组key-value映射。
实现类:HashMap、Hashtable、LinkedHashMap和TreeMap等
常用方法如下。
返回值 | 方法名 | 方法描述 |
---|---|---|
boolean | containsKey(Object key) | 如果此映射包含指定键的映射关系,则返回 true。 |
V | get(Object key) | 返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null。 |
V | put(K key, V value) | 将指定的值与此映射中的指定键关联(可选操作)。 |
Collection<V> | values() | 返回此映射中包含的值的 Collection 视图。 |
HashMap类
HashMap是最常用的Map,它根据键的HashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度,遍历时,取得数据的顺序是完全随机的。因为键对象不可以重复,所以HashMap最多只允许一条记录的键为Null,允许多条记录的值为Null,是非同步的。
由于作为key的对象将通过计算其散列函数来确定与之对应的value的位置,因此任何作为key的对象都必须实现hashCode和equals方法。
map中key和value的迭代
import java.util.*;
public class test {
public static void main(String args[]){
Map<String,Integer> map = new HashMap<>();
map.put("aaa",22);
map.put("bbb",33);
map.put("ccc",44);
Iterator itKeys = map.keySet().iterator();//遍历所有键的set集合
while(itKeys.hasNext()){
System.out.print(itKeys.next()+" ");
}
System.out.println();
Iterator itValues = map.values().iterator();//values()返回Collection集合
while(itValues.hasNext()){
System.out.print(itValues.next()+" ");
}
System.out.println();
Iterator itEntry = map.entrySet().iterator();// //返回此映射中包含的映射关系的 Set 视图。
while(itEntry.hasNext()){
Map.Entry entry = (Map.Entry)itEntry.next();
System.out.println(entry.getKey()+":"+entry.getValue());
}
}
}
从结果中可以看出迭代的顺序并不是插入的顺序
Hashtable类
Hashtable与HashMap类似,是HashMap的线程安全版,它支持线程的同步,即任一时刻只有一个线程能写Hashtable,因此也导致了Hashtale在写入时会比较慢,它继承自Dictionary类,不同的是它不允许记录的键或者值为null,同时效率较低。
ConcurrentHashMap类
线程安全,并且锁分离。ConcurrentHashMap内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的hash table,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。
LinkedHashMap类
LinkedHashMap保存了记录的插入顺序,在用Iteraor遍历LinkedHashMap时,先得到的记录肯定是先插入的,在遍历的时候会比HashMap慢,有HashMap的全部特性。
TreeMap类
TreeMap实现SortMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序(自然顺序),也可以指定排序的比较器,当用Iterator遍历TreeMap时,得到的记录是排过序的。不允许key值为空,非同步的;key必须实现Comparable接口
三、集合类使用总结
3.1Vector和ArrayList
1.Vector是线程同步的,所以它也是线程安全的,而Arraylist是线程异步的,是不安全的。如果不考虑到线程的安全因素,一般用arraylist效率比较高。
2.如果集合中的元素的数目大于目前集合数组的长度时,Vector增长率为目前数组长度的100%,而Arraylist增长率为目前数组长度的50%.如过在集合中使用数据量比较大的数据,用Vector有一定的优势。
3.如果查找一个指定位置的数据,Vector和Arraylist使用的时间是相同的,都是O(1),这个时候使用Vector和Arraylist都可以。而如果移动一个指定位置的数据花费的时间为O(n-i)n为总长度,这个时候就应该考虑到使用Linklist,因为它移动一个指定位置的数据所花费的时间为O(1),而查询一个指定位置的数据时花费的时间为O(i)。
ArrayList 和Vector是采用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,都允许直接序号索引元素,但是插入数据要设计到数组元素移动等内存操作,所以索引数据快插入数据慢,Vector由于使用了synchronized方法(线程安全)所以性能上比ArrayList要差,LinkedList使用双向链表实现存储,按序号索引数据需要进行向前或向后遍历,但是插入数据时只需要记录本项的前后项即可,所以插入数度较快!
3.2Arraylist和Linkedlist
1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
2.对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。
3.对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。
这一点要看实际情况的。若只对单条数据插入或删除,ArrayList的速度反而优于LinkedList。但若是批量随机的插入删除数据,LinkedList的速度大大优于ArrayList. 因为ArrayList每插入一条数据,要移动插入点及之后的所有数据。
3.3HashMap与TreeMap
1、 HashMap通过hashcode对其内容进行快速查找,而TreeMap中所有的元素都保持着某种固定的顺序,如果你需要得到一个有序的结果你就应该使用TreeMap(HashMap中元素的排列顺序是不固定的)。
2、在Map 中插入、删除和定位元素,HashMap是最好的选择。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。使用HashMap要求添加的键类明确定义了hashCode()和 equals()的实现。
3.4HashTable与HashMap
1.同步性:Hashtable是线程安全的,也就是说是同步的,而HashMap是线程序不安全的,不是同步的。
2.HashMap允许存在一个为null的key,多个为null的value 。
3.hashtable的key和value都不允许为null。
四、总结:
1)如果要求线程安全,使用Vector,Hashtable
2)如果不要求线程安全,使用ArrayList,LinkedList,HashMap
3)如果要求键值对,则使用HashMap,Hashtable
4)如果数据量很大,又要求线程安全考虑Vector
1.ArrayList: 元素单个,效率高,多用于查询
2.Vector: 元素单个,线程安全,多用于查询
3.LinkedList:元素单个,多用于插入和删除,栈和队列
4.HashMap: 元素成对,元素可为空
5.HashTable: 元素成对,线程安全,元素不可为空