集合框架
//ArrayList --- 实现了List接口,ArrayList是容量大小可变的数组的实现
ArrayList al = new ArrayList();
//将元素添加到al集合中的尾部
String str1 = "a";
al.add(str1);
String str2 = "b";
al.add(str2);
//将元素添加到al集合中的指定位置
String str3 = "c";
al.add(0, str3);
//可以添加重复元素
al.add(str1);
al.add(str2);
//按下标移除元素
al.remove(0);
//按元素移除集合中指定元素,PS:如果该元素在集合中不止一个,移除的元素是正序第一个匹配到的元素
al.remove(str2);
//遍历输出
for(int i = 0; i < al.size(); i++){
//按下标获取元素,PS:因为get()方法返回的是Object类型的数据,所以要强制类型转换
String tmp = (String)al.get(i);
System.out.print(tmp + " ");
}
System.out.println();
//擦除集合中所有元素
al.removeAll(al);
//LinkedList --- 实现的接口比较多,用的较多的是它的双端队列的特性
LinkedList ll = new LinkedList();
//上述ArrayList所使用的方法LinkedList都能使用
//除此之外,还有以下这些
//将元素添加到集合的首部
ll.addFirst(str1);
//将元素添加到集合的尾部
ll.addLast(str2);
ll.addLast(str3);
ll.addLast(str1);
ll.addLast(str2);
ll.addLast(str3);
ll.addLast(str1);
//移除集合首部元素
ll.removeFirst();
ll.remove();
//移除集合尾部元素
ll.removeLast();
//移除与指定元素相同的,从首部到尾部之间第一个元素
ll.removeFirstOccurrence(str1);
//移除与指定元素相同的,从首部到尾部之间最后一个元素
ll.removeLastOccurrence(str2);
//获取首部元素
ll.getFirst();
//获取尾部元素
ll.getLast();
//移除所有元素
ll.removeAll(ll);
//Vector --- Vector可以实现可增长的对象数组,Vector的大小可以根据需要增大或缩小,以适应创建 Vector后进行添加或移除项的操作。
Vector v = new Vector();
//上述ArrayList所使用的方法Vector都能使用
//除此之外,还有以下这些
//添加一个组件到向量向量末尾
v.addElement(str1);
v.addElement(str2);
//返回向量当前的大小
v.capacity();
//判断向量是否为空
v.isEmpty();
//获取首部组件
v.firstElement();
//获取尾部组件
v.lastElement();
//扩大该向量的大小
v.ensureCapacity(5);
//移除指定位置的组件
v.removeElementAt(0);
//移除正序第一个与指定组件相同的组件
v.removeElement(str1);
//移除所有组件,并将向量大小设置为0
v.removeAllElements();
//Stack --- Stack表示栈结构。继承至 Vector。
//它提供了push和 pop操作,以及取堆栈顶点的 peek方法、测试栈是否为空的empty方法、在栈中查找项并确定到栈顶距离的search方法。
Stack s = new Stack();
//压栈方法
s.push(str1);
s.push(str2);
s.push(str3);
//取栈顶元素方法
s.peek();
//出栈方法
s.pop();
//测试堆栈是否为空的方法
s.empty();
//在栈中查找项并确定到栈顶距离的方法,PS:返回值以1为栈顶基数,-1为未找到
System.out.println(s.search(str1));
ArrayList与Vector都是java的集合类,都可以用来存放java对象,这是他们的相同点,但是他们也有区别:
1、同步性
Vector是线程同步的。这个类中的一些方法保证了Vector中的对象是线程安全的。而ArrayList则是线程异步的,因此ArrayList中的对象并不是线程安全的。因为同步的要求会影响执行的效率,所以如果你不需要线程安全的集合那么使用ArrayList是一个很好的选择,这样可以避免由于同步带来的不必要的性能开销。
2、数据增长
从内部实现机制来讲ArrayList和Vector都是使用数组(Array)来控制集合中的对象。当你向这两种类型中增加元素的时候,如果元素的数目超出了内部数组目前的长度它们都需要扩展内部数组的长度,Vector缺省情况下自动增长原来一倍的数组长度,ArrayList是原来的50%,所以最后你获得的这个集合所占的空间总是比你实际需要的要大。所以如果你要在集合中保存大量的数据那么使用Vector有一些优势,因为你可以通过设置集合的初始化大小来避免不必要的资源开销。
//Map结构常用的集合类
//HashMap --- 此实现假定哈希函数将元素适当地分布在各桶之间,其取出和存入的时间复杂度非常低,为常数级别,PS:不同存在相同键
HashMap hm = new HashMap();
//放入键值对
int num1 = 1;
hm.put(num1, str1);
//如果键以存在,则替换该键值对
hm.put(num1, str2);
int num2 = 2;
hm.put(num2, str2);
int num3 = 3;
hm.put(num3, str3);
//通过键获取相应值
hm.get(num1);
//通过键判断集合是否存在该键值对
hm.containsKey(num1);
//通过值判断集合是否存在该键值对
hm.containsValue(str1);
//利用迭代器遍历集合中的所有键值对
//keySet()返回此映射中所包含的键的Set视图
//iterator()返回在此 set中的元素上进行迭代的迭代器
Iterator it = hm.keySet().iterator();
//hasNext()如果仍有元素可以迭代,则返回 true。
while(it.hasNext()){
//取出key
//next()返回迭代的下一个元素。
//toString()返回String类型数据,PS:这里不能强制类型转换成String
int key = Integer.parseInt(it.next().toString());
//通过key获取value
String tmp = (String)hm.get(key);
//输出value
System.out.print(tmp + " ");
}
System.out.println();
//HashTable --- Hashtable具有同步性,线程安全
//HashTable在用法上和HashMap基本相似
Hashtable ht = new Hashtable();
//放入空值会报错
//ht.put(null, null);
//测试此哈希表是否没有键映射到值。
ht.isEmpty();
HashMap与Hashtable都是java的集合类,都可以用来存放java对象,这是他们的相同点,但是他们也有区别。
1、历史原因
Hashtable是基于陈旧的Dictionary类的,HashMap是java 1.2引进的Map接口的一个实现。
2、同步性
Hashtable是线程同步的。这个类中的一些方法保证了Hashtable中的对象是线程安全的。而HashMap则是线程异步的,因此HashMap中的对象并不是线程安全的。因为同步的要求会影响执行的效率,所以如果你不需要线程安全的集合那么使用HashMap是一个很好的选择,这样可以避免由于同步带来的不必要的性能开销,从而提高效率。
3、值
HashMap可以让你将空值作为一个表的条目的key或value但是Hashtable是不能放入空值的(null)
java的设计者给我们提供了这些集合类,在后面编程中是相当有用的,具体什么时候用什么集合,要根据我们刚才分析的集合异同来选取。
如何选用集合类?
1、要求线程安全,使用Vector、Hashtable
2、不要求线程安全,使用ArrayList,LinkedList,HashMap
3、要求key和value键值,则使用HashMap,Hashtable
4、数据量很大,又要线程安全,则使用Vector
HashSet是基于HashMap实现的,HashSet底层采用HashMap来保存所有元素。
hashCode和equal()是HashMap用的,因为无需排序所以只需要关注定位和唯一性即可
hashCode是用来计算hash值的,hash值是用来确定hash表索引的
hash表中的一个索引存放的是一张链表,所以还要通过equal方法循环比较链上的每一个对象才可以真正定位到键值对应的Entry
put时,如果hash表中没定位到,就在链表前加一个Entry,如果定位到了,则更换Entry中的value(值)并返回旧value(值)
覆写key的hashCode()和equal()时一定要注意,不要把它们和可变属性关联上,否则属性变了之后hashCode会变,equal也会为false,这样在Map中就找不到它了而且这样的对象因为找不到它所以得不到释放,这样就变成了一个无效引用(相当于内存泄漏)
//Set结构常用的集合类
//HashSet --- 以HashMap为底层
//HashSet中无get方法,只能通过迭代器获取键
HashSet hs = new HashSet();
//添加键到集合尾部
hs.add(str1);
hs.add(str2);
hs.add(str3);
//自动去掉重复元素,PS:其实也是像HashMap一样,用新键覆盖旧键
hs.add(str1);
//返回包含全部键的Object类型数组
Object[] o = hs.toArray();
for(int i = 0; i < o.length; i++){
System.out.print(o[i] + " ");
}
System.out.println();
TreeSet集合类是一个有序集合,它的元素按照升序排序,默认是自然顺序排列,也就是说
TreeSet中的对象元素需要实现Comparable接口。TreeSet与HashSet类一样没有get()方法来获取列表中的元素,所以也只能通过迭代器方法来获取。
由于TreeMap需要排序,所以需要一个Comparator为键值进行大小比较,当然也是用Comparator定位的Comparator可以在创建TreeMap时指定,这时排序时使用Comparator.compare
如果创建时没有指定Comparator那么就会使用key.compareTo()方法,这就要求key必须实现Comparable接口
TreeMap是使用Tree数据结构实现的,所以使用compare接口就可以完成定位了。
TreeSet是依靠TreeMap来实现的。
//TreeSet --- 使用元素的自然顺序对元素进行排序,PS:自然顺序是升序,或者根据创建 set时提供的 Comparator进行排序,具体取决于使用的构造方法
TreeSet ts = new TreeSet();
//添加键到集合
ts.add(str1);
ts.add(str2);
ts.add(str3);
//迭代器遍历
for(it = ts.iterator(); it.hasNext();){
String tmp = it.next().toString();
System.out.print(tmp + " ");
}
System.out.println();
//返回此 set 中小于等于给定元素的最大元素;如果不存在这样的元素,则返回 null。
ts.floor(str2);
//返回此 set 中大于等于给定元素的最小元素;如果不存在这样的元素,则返回 null。
ts.ceiling(str2);
//返回此 set 中严格小于给定元素的最大元素;如果不存在这样的元素,则返回 null。
ts.lower(str2);
//返回此 set 中严格大于给定元素的最小元素;如果不存在这样的元素,则返回 null。
ts.higher(str2);
//获取并移除第一个(最低)元素;如果此 set为空,则返回 null。
ts.pollFirst();
//获取并移除最后一个(最高)元素;如果此 set为空,则返回 null。
ts.pollLast();
HashSet是基于hash算法实现的,性能优于TreeSet。通常使用HashSet,在我们需要对其中元素排序的时候才使用TreeSet。
Queue接口与List、Set同一级别,都是继承了Collection接口。LinkedList实现了Queue接口。Queue接口窄化了对LinkedList的方法的访问权限(即在方法中的参数类型如果是Queue时,就完全只能访问Queue接口所定义的方法了,而不能直接访问 LinkedList的非Queue的方法),以使得只有恰当的方法才可以使用。BlockingQueue继承了Queue接口。
队列是一种数据结构。它有两个基本操作:在队列尾部加人一个元素,和从队列头部移除一个元素。就是说,队列以一种先进先出的方式管理数据,如果你试图向一个已经满了的阻塞队列中添加一个元素或者是从一个空的阻塞队列中移除一个元索,将导致线程阻塞。
在多线程进行合作时,阻塞队列是很有用的工具。工作者线程可以定期地把中间结果存到阻塞队列中而其他工作者线线程把中间结果取出并在将来修改它们。队列会自动平衡负载。如果第一个线程集运行得比第二个慢,则第二个线程集在等待结果时就会阻塞。如果第一个线程集运行得快,那么它将等待第二个线程集赶上来。
add |
增加一个元索 |
如果队列已满,则抛出一个IIIegaISlabEepeplian异常 |
remove |
移除并返回队列头部的元素 |
如果队列为空,则抛出一个NoSuchElementException异常 |
element |
返回队列头部的元素 |
如果队列为空,则抛出一个NoSuchElementException异常 |
offer |
添加一个元素并返回true |
如果队列已满,则返回false |
poll |
移除并返问队列头部的元素 |
如果队列为空,则返回null |
peek |
返回队列头部的元素 |
如果队列为空,则返回null |
put |
添加一个元素 |
如果队列满,则阻塞 |
take |
移除并返回队列头部的元素 |
如果队列为空,则阻塞 |
remove、element、offer 、poll、peek 其实是属于Queue接口。 |
阻塞队列的操作可以根据它们的响应方式分为以下三类:add、remove和element操作在你试图为一个已满的队列增加元素或从空队列取得元素时 抛出异常。当然,在多线程程序中,队列在任何时间都可能变成满的或空的,所以你可能想使用offer、poll、peek方法。这些方法在无法完成任务时 只是给出一个出错示而不会抛出异常。
注意:poll和peek方法出错进返回null。因此,向队列中插入null值是不合法的。
一些队列有大小限制,因此如果想在一个满的队列中加入一个新项,多出的项就会被拒绝。这时新的 offer 方法就可以起作用了。它不是对调用 add() 方法抛出一个 unchecked 异常,而只是得到由 offer() 返回的 false。
remove()和poll()方法都是从队列中删除第一个元素(head)。remove()的行为与Collection 接口的版本相似,但是新的 poll()方法在用空集合调用时不是抛出异常,只是返回null。因此新的方法更适合容易出现异常条件的情况。
element()和peek()用于在队列的头部查询元素。与 remove()方法类似,在队列为空时,element()抛出一个异常,而peek()返回null。
一个由数组支持的有界队列。基于数组的阻塞循环队列,先进先出原则对元素进行排序。
一个由链接节点支持的可选有界队列。基于链表的队列,先进先出排序元素。
一个由优先级堆支持的无界优先级队列。PriorityBlockingQueue是对PriorityQueue的再次包装,是基于堆数据结构的,而PriorityQueue是没有容量限制的,与ArrayList一样,所以在优先阻塞队列上put时是不会受阻的。但是由于资源被耗尽,所以试图执行添加操作可能会导致OutOfMemoryError),但是如果队列为空,那么取元素的操作take就会阻塞,所以它的检索操作take是受阻的。另外,往入该队列中的元素要具有比较能力。
一个由优先级堆支持的、基于时间的调度队列。(基于PriorityQueue来实现的)是一个存放Delayed 元素的无界阻塞队列,只有在延迟期满时才能从中提取元素。该队列的头部是延迟期满后保存时间最长的 Delayed 元素。如果延迟都还没有期满,则队列没有头部,并且poll将返回null。当一个元素的 getDelay(TimeUnit.NANOSECONDS) 方法返回一个小于或等于零的值时,则出现期满,poll就以移除这个元素了。此队列不允许使用null元素。
一个利用 BlockingQueue接口的简单聚集(rendezvous)机制。
SynchronousQueue 类是最简单的。它没有内部容量。它就像线程之间的手递手机制。在队列中加入一个元素的生产者会等待另一个线程的消费者。当这个消费者出现时,这个元素就直接在消费者和生产者之间传递,永远不会加入到阻塞队列中。
注意:Queue队列是不能直接实例化的。
List按对象进入的顺序保存对象,不做排序和编辑操作。
Set对每个对象只接受一次,并使用自己内部的排序方法(通常,你只关心某个元素是否属于Set而不关心它的顺序--否则使用List)。
Map同样对每个元素保存一份,但这是基于"键"(key)的,Map也有内置的排序,因而不关心元素添加的顺序。
如果添加元素的顺序对程序设计很重要,应该使用LinkedHashSet或者LinkedHashMap。
实际上有两种List:一种是基本的ArrayList其优点在于随机访问元素,另一种是更强大的LinkedList它并不是为快速随机访问设计的,而是具有一套更通用的方法。
List:次序是List最重要的特点:它保证维护元素特定的顺序。List为Collection添加了许多方法,使得能够向List中间插入与移除元素(这只推荐LinkedList使用)一个List可以生成Listlterator,使用它可以从两个方向遍历List,也可以从List中间插入和移除元素。
ArrayList:由数组实现的List。允许对元素进行快速随机访问,但是向List中间插入与移除元素的速率很慢。Listlterator只应该用来由后向前遍历ArrayList。而不是用来插入和移除元素。因为那比LinkedList开销要大很多。
LinkedList:对顺序访问进行了优化,向List中间插入与删除的开销并不大。随机访问则相对较慢。(使用ArrayList代替)还具有下列方法:addFirst(),addLast(),getFirst(),getLast(),removeFirst()和removeLast()这些方法(没有在任何接口或基类中定义过)使得LinkedList可以当作堆栈、队列和双向队列使用。
Set具有与Collection完全一样的接口,因此没有任何额外的功能,不象前面有两个不同的List。实际上Set就是Collection,只是行为不同。(这是继承与多态思想的典型应用:表现不同的行为。)Set不保存重复的元素(至于如何判断元素相同则较为负责)
Set:存入Set的每个元素都必须是唯一的,因为Set不保存重复元素。加入Set的元素必需定义equals()方法以确保对象的唯一性。Set与Collection有完全一样的接口。Set接口不保证维护元素的次序。
HashSet:为快速查找设计的Set。存入HashSet的对象必须定义hashCode()。
TreeSet:保存次序的Set,底层为树结构。使用它可以从Set中提取有序的序列。
LinkedHashSet:具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入的次序)。于是在使用迭代器遍历Set时,结果会按元素插入的次序显示。
方法put(Object key,Object value)添加一个"值"(想要得东西)和与"值"相关的"键"(key)(使用它来查找)。方法get(Object key)返回与给定"键"相关联的"值"。可以用containsKey()和containsValue()测试Map中是否包含某个"键"或"值"。标准的java类库中包含了几种不同的Map:HashMap,TreeMap,LinkedHashMap,WeakHashMap,ldentityHashMap。它们都有同样的基本接口Map,但是行为、效率、排序策略、保存对象的生命周期和判定"键"等价的策略等各不相同。
执行效率是Map的一个大问题。看看get()要做哪些事,就会明白为什么在ArrayList中搜索"键"是相当慢的。这正是HashMap提高速度的地方。HashMap使用了特殊的值,称为"散列码"(hash code),来取代对键的缓慢搜索。"散列码"是"相对唯一"用以代表对象的int值,它是通过将该对象的某些信息进行转换而生成的。所有java对象都能产生散列码,因为hashCode()是定义在基类Object中的方法。
HashMap就是使用对象的hashCode()进行快速查询的。此方法能够显著提高性能。
Map:维护"键值对"的关联性,使你可通过"键"查找"值"
HashMap:Map基于散列表的实现。插入和查询"键值对"的开销是固定的。可以通过构造器设置容量capacity和负载因子load factor,以调整容器的性能。
LinkedHashMap:类似于HashMap,但是迭代遍历它时,取得"键值对"的顺序是其插入次序,或者是最近最少使(LRU)的次序。只能HashMap慢一点。而在迭代访问时发而更快,因为它使用键表维护内部次序。
TreeMap:基于红黑树数据结果的实现。查看"键"或"键值对"时,它们会被排序(次序由Comparabel或Comparator决定)。TreeMap的特点在于,你得到的结果是经过排序的。TreeMap是唯一的带有subMap()方法的Map,它可以返回一个子树。
-------------参考《韩顺平.循序渐进学.java.从入门到精通》
-------------参考《JDK_API_1_6_zh_CN》