java 集合详解及如何应用

时间:2021-06-11 17:54:25

1、结构图

            java 集合详解及如何应用 

2、集合对比说明

有序 允许元素重复 同步 描述 备注 实现类

Iterable(A)







Collection






AbstractCollection

List



和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变

AbstractList
ArrayList

基于Array的List,动态数组,根据索引排序

LinkedList


插入的顺序;可被用作堆栈(stack),队列(queue)或双向队列(deque)。
构造一个同步的List:List list=Collections.synchronizedList(new LinkedList(...));

Vector


基于Array的List,一种老的动态数组,是线程同步的,效率很低,一般不赞成使用


Stack


数组结构,继承vector,后进先出的堆栈

Set



检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。

AbstractSet

HashSet



以哈希表的形式存放元素,插入删除速度很快


TreeSet



二叉树排序,升序排列


LinkedHashSet



具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入的次序)


Map




使用key-value来映射和存储数据,Key必须惟一,value可以重复;

AbstractMap

HashMap



它根据键的HashCode 值存储数据,根据键可以直接获取它的值,具有很快的访问速度。HashMap最多只允许一条记录的键为Null;允许多条记录的值为 Null
构造一个同步的Map:Map map=Collections.synchronizedMap(new HashMap(...));

Hashtable



不允许记录的键或者值为空;它支持线程的同步,即任一时刻只有一个线程能写Hashtable,因此也导致了Hashtale在写入时会比较慢。


LinkedHashMap 



保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的.在遍历 的时候会比HashMap慢。


TreeMap



保存的记录根据键排序,默认是按升序排序,也可以指定排序的比较器,当用Iterator 遍历TreeMap时,得到的记录是排过序的。


*ps:哈希表是基于数组、链表的  哈希表(Hash table,也叫散列表)


2、对LinkedList进行插入和删除操作具有较快的速度    1、对Java数组进行随机访问和迭代操作具有最快的速度

3、对ArrayList进行随机访问具有较快的速度

4、Vector没有突出的性能,已不提倡使用


3、集合类详细说明
参考 Java集合详解: http://www.cnblogs.com/eflylab/archive/2007/01/19/625086.html                 1.理解集合类               集合类存放于java.util包中。            集合类存放的都是对象的引用,而非对象本身,出于表达上的便利,我们称集合中的对象就是指集合中对象的引用(reference)。            集合类型主要有3种:set(集)、list(列表)和map(映射)。                          Collection<--List<--Vector
                  Collection<--List<--ArrayList                   Collection<--List<--LinkedList                   Collection<--Set<--HashSet                   Collection<--Set<--HashSet<--LinkedHashSet                   Collection<--Set<--SortedSet<--TreeSet                
                          Map<--HashMap                           Map<--HashMap<--LinkedHashMap                           Map<--SortedMap                           Map<--SortedMap<--TreeMap        

        (1)集

        集(set)是最简单的一种集合,它的对象不按特定方式排序,只是简单的把对象加入集合中,就像往口袋里放东西

        对集中成员的访问和操作是通过集中对象的引用进行的,所以集中不能有重复对象。

        集也有多种变体,可以实现排序等功能,如TreeSet,它把对象添加到集中的操作将变为按照某种比较规则将其插入到有序的对象序列中。它实现的是SortedSet接口,也就是加入了对象比较的方法。通过对集中的对象迭代,我们可以得到一个升序的对象集合。

        (2)列表

        列表的主要特征是其对象以线性方式存储,没有特定顺序,只有一个开头和一个结尾,当然,它与根本没有顺序的集是不同的。

        列表在数据结构中分别表现为:数组和向量、链表、堆栈、队列。

        关于实现列表的集合类,是我们日常工作中经常用到的,将在后边的笔记详细介绍。

        (3)映射

        映射与集或列表有明显区别,映射中每个项都是成对的。映射中存储的每个对象都有一个相关的关键字(Key)对象,关键字决定了对象在映射中的存储位置,检索对象时必须提供相应的关键字,就像在字典中查单词一样。关键字应该是唯一的。

        关键字本身并不能决定对象的存储位置,它需要对过一种散列(hashing)技术来处理,产生一个被称作散列码(hash code)的整数值,

        散列码通常用作一个偏置量,该偏置量是相对于分配给映射的内存区域起始位置的,由此确定关键字/对象对的存储位置。理想情况下,散列处理应该产生给定范围内均匀分布的值,而且每个关键字应得到不同的散列码。



2.List接口 List是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组。 和下面要提到的Set不同,List允许有相同的元素。 除了具有Collection接口必备的iterator()方法外,List还提供一个listIterator()方法,返回一个 ListIterator接口,和标准的Iterator接口相比,ListIterator多了一些add()之类的方法,允许添加,删除,设定元素,还能向前或向后遍历。

List总结:

1. 所有的List中只能容纳单个不同类型的对象组成的表,而不是Key-Value键值对。例如:[ tom,1,c ];

2. 所有的List中可以有相同的元素,例如Vector中可以有 [ tom,koo,too,koo ];

3. 所有的List中可以有null元素,例如[ tom,null,1 ];

4. 基于Array的List(Vector,ArrayList)适合查询,而LinkedList(链表)适合添加,删除操作。


        2.1.Vector类
基于Array的List,其实就是封装了Array所不具备的一些功能方便我们使用,它不可能不受Array的限制。性能也就不可能超越Array。所以,在可能的情况下,我们要多运用Array。另外很重要的一点就是Vector:sychronized”的,这个也是Vector和ArrayList的唯一的区别。
实现一个类似数组一样的表,自动增加容量来容纳你所需的元素。使用下标存储和检索对象就象在一个标准的数组中一样。你也可以用一个迭代器从一个Vector中检索对象。Vector是唯一的同步容器类!!当两个或多个线程同时访问时也是性能良好的。
                2.2.ArrayList        同Vector一样是一个基于Array上的链表,但是不同的是ArrayList不是同步的。所以在性能上要比Vector优越一些,但是当运行到多线程环境中时,可需要自己在管理线程的同步问题。
扩展阅读:在java.util.concurrent包中定义的CopyOnWriteArrayList提供了线程安全的Arraylist,但是当进行add和set等变化操作时它是通过为底层数组创建新的副本实现的,所以比较耗费资源 (源码在此: publicboolean add(E e) {
final     ReentrantLock  lock = this . lock ;
lock.lock();
try  {
Object[] elements = getArray();
int  len = elements. length ;
Object[] newElements = Arrays. copyOf (elements,len + 1);
newElements[len] = e;
setArray(newElements);
return true ;
finally  {
lock.unlock();
}
}
但是如果存在频繁遍历,遍历操作比变化(写入和修改)操作多的时候这种遍历就相对于自己进行的同步遍历效果要好,而且它也允许存在null 元素)

2.1.LinkedList
        LinkedList不同于前面两种List,它不是基于Array的,所以不受Array性能的限制。它每一个节点(Node)都包含两方面的内容:1.节点本身的数据(data);2.下一个节点的信息(nextNode)。所以当对LinkedList做添加,删除动作的时候就不用像基于Array的List一样,必须进行大量的数据移动。只要更改nextNode的相关信息就可以实现了。这就是LinkedList的优势。LinkedList提供额外的get,remove,insert方法在 LinkedList的首部或尾部。这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。注意LinkedList没有同步方法。        如果多个线程同时访问一个List,则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List:             List  list=Collections.synchronizedList(new LinkedList(...));
        
        2.4.Stack
Stack继承自Vector,实现一个后进先出的堆栈。Stack提供5个额外的方法使得Vector得以被当作堆栈使用。基本的push和pop方法,还有peek方法得到栈顶的元素,empty方法测试堆栈是否为空,search方法检测一个元素在堆栈中的位置。Stack刚创建后是空栈。
       3. set接口
虽然Set同List都实现了Collection接口,但是他们的实现方式却大不一样。 List基本上都是以Array为基础。但是Set则是在HashMap的基础上来实现的 ,这个就是Set和List的根本区别。

1. Set实现的基础是Map(HashMap);

2. Set中的元素是不能重复的,如果使用add(Object obj)方法添加已经存在的对象,则会覆盖前面的对象


        3.1HashSet :

HashSet的存储方式是把HashMap中的Key作为Set的对应存储项。看看HashSet的add(Object  obj)方法的实现就可以一目了然了。
    public boolean add(Object obj)
    {
        return map.put(obj, PRESENT) == null;
    }

这个也是为什么在Set中不能像在List中一样有重复的项的根本原因,因为HashMap的key是不能有重复的

        3.2LinkedHashSet : 具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入的次序,不含有重复元素)。于是在使用迭代器遍历Set时,结果会按元素插入的次序显示。 HashSet的一个子类,一个链表。         3.3TreeSetSortedSet的子类,它不同于HashSet的根本就是TreeSet是有序的(升序)。它是通过TreeMap来实现的。

 
       4.Map的功能方法
java为数据结构中的映射定义了一个接口java.util.Map;它有四个实现类,分别是HashMap Hashtable LinkedHashMap 和TreeMap

HashTable: 实现一个映象,所有的键必须非空。为了能高效的工作,定义键的类必须实现hashcode()方法和equal()方法。这个类是前面java实现的一个继承,并且通常能在实现映象的其他类中更好的使用。

HashMap: 实现一个映象,允许存储空对象,而且允许键是空(由于键必须是唯一的,当然只能有一个)。

        LinkedHashMap: 保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的.在遍历 的时候会比HashMap慢。
TreeMap: 实现这样一个映象,对象是按键升序排列的。TreeMap能够把它保存的记录根据键排序,默认是按升序排序,也可以指定排序的比较器,当用Iterator 遍历TreeMap时,得到的记录是排过序的。


    在JDK1.5中引入 ConcurrentHashMap

    ConcurrentHashMap具体是怎么实现线程安全的呢,肯定不可能是每个方法加synchronized,那样就变成了HashTable。

    从ConcurrentHashMap代码中可以看出,它引入了一个“分段锁”的概念,具体可以理解为把一个大的Map拆分成N个小的HashTable(就是段Segment),根据key.hashCode()来决定把key放到哪个HashTable中。



4、工具类

    Collections是针对集合类的一个帮助类。提供了一系列静态方法实现对各种集合的搜索、排序、线程完全化等操作。

    相当于对Array进行类似操作的类——Arrays。

    如:

        Collections.max(Collection coll); 取coll中最大的元素。

        Collections.sort(List list); 对list中元素排序

5、如何选择集合?

* 在各种Lists中,最好的做法是以ArrayList作为缺省选择。当插入、删除频繁时,使用LinkedList();Vector总是比ArrayList慢,所以要尽量避免使用。

* 在各种Sets中,HashSet通常优于TreeSet(插入、查找)。只有当需要产生一个经过排序的序列,才用TreeSet。TreeSet存在的唯一理由:能够维护其内元素的排序状态。

* 在各种Maps中HashMap用于快速查找。

* 当元素个数固定,用Array,因为Array效率是最高的。

结论:最常用的是ArrayList,HashSet,HashMap,Array。而且,我们也会发现一个规律,用TreeXXX都是排序的。

 

注意:

1、Collection没有get()方法来取得某个元素。只能通过iterator()遍历元素。

5、Map用 put(k,v) / get(k),还可以使用containsKey()/containsValue()来检查其中是否含有某个key/value。HashMap会利用对象的hashCode来快速找到key。

* hashing

哈希码就是将对象的信息经过一些转变形成一个独一无二的int值,???,这个值存储在一个array中。

我们都知道所有存储结构中,array查找速度是最快的。所以,可以加速查找。

发生碰撞时,让array指向多个values。即,数组每个位置上又生成一个梿表。

6、Map中元素,可以将key序列、value序列单独抽取出来。

使用keySet()抽取key序列,将map中的所有keys生成一个Set。

使用values()抽取value序列,将map中的所有values生成一个Collection。

为什么一个生成Set,一个生成Collection?那是因为,key总是独一无二的,value允许重复。

7、Vector 还是ArrayList,哪一个更好,为什么? 

要回答这个问题不能一概而论,有时候使用Vector比较好;有时是ArrayList,有时候这两个都不是最好的选择。你别指望能够获得一个简单肯定答案,因为这要看你用它们干什么。下面有4个要考虑的因素:

(1)API

(2)同步处理

(3)数据增长性

(4)使用模式

下面针对这4个方面进行一一探讨

API 
在由Ken Arnold等编著的《Java Programming Language》(Addison-Wesley, June 2000)一书中有这样的描述,Vector类似于ArrayList.。所有从API的角度来看这两个类非常相似。但他们之间也还是有一些主要的区别的。

同步性

Vector是同步的。这个类中的一些方法保证了Vector中的对象是线程安全的。而ArrayList则是异步的,因此ArrayList中的对象并不是线程安全的。因为同步的要求会影响执行的效率,所以如果你不需要线程安全的集合那么使用ArrayList是一个很好的选择,这样可以避免由于同步带来的不必要的性能开销。

数据增长

从内部实现机制来讲ArrayList和Vector都是使用数组(Array)来控制集合中的对象。当你向这两种类型中增加元素的时候,如果元素的数目超出了内部数组目前的长度它们都需要扩展内部数组的长度,Vector缺省情况下自动增长原来一倍的数组长度,ArrayList是原来的50%,所以最后你获得的这个集合所占的空间总是比你实际需要的要大。所以如果你要在集合中保存大量的数据那么使用Vector有一些优势,因为你可以通过设置集合的初始化大小来避免不必要的资源开销。

使用模式

在ArrayList和Vector中,从一个指定的位置(通过索引)查找数据或是在集合的末尾增加、移除一个元素所花费的时间是一样的,这个时间我们用O(1)表示。但是,如果在集合的其他位置增加或移除元素那么花费的时间会呈线形增长:O(n-i),其中n代表集合中元素的个数,i代表元素增加或移除元素的索引位置。为什么会这样呢?以为在进行上述操作的时候集合中第i和第i个元素之后的所有元素都要执行位移的操作。这一切意味着什么呢?
这意味着,你只是查找特定位置的元素或只在集合的末端增加、移除元素,那么使用Vector或ArrayList都可以。如果是其他操作,你最好选择其他的集合操作类。
比如,LinkList集合类在增加或移除集合中任何位置的元素所花费的时间都是一样的—O(1),但它在索引一个元素的使用却比较慢-O(i),其中i是索引的位置.使用ArrayList也很容易,因为你可以简单的使用索引来代替创建iterator对象的操作。LinkList也会为每个插入的元素创建对象,所以你要明白它也会带来额外的开销。

Q:Comparable和Comparator区别A:调用java.util.Collections.sort(List list)方法来进行排序的时候,List内的Object都必须实现了Comparable接口。        java.util.Collections.sort(List list,Comparator c),可以临时声明一个Comparator 来实现排序。        Collections.sort(imageList, new Comparator() {            public int compare(Object a, Object b) {                int orderA = Integer.parseInt( ( (Image) a).getSequence());                int orderB = Integer.parseInt( ( (Image) b).getSequence());                return orderA - orderB;           }        });        如果需要改变排列顺序        改成return orderb - orderA 即可。

4.1 equals()相等的两个对象,hashcode()一定相等,equals()不相等的两个对象,却并不能证明他们的hashcode()不相等。换句话说,equals()方法不相等的两个对象,hashCode()有可能相等。

4.2  Java中的hashCode方法就是根据一定的规则将与对象相关的信息(比如对象的存储地址,对象的字段等)映射成一个数值,这个数值称作为散列值
ps1:HashSet底层是通过HashMap实现的,看如下的构造函数,构造HashSet的时候底层就构造了一个HashMap;hashSet 和 hashMap  是不重复的,对比对象是否相等时,先对比hashcode值(hashcode()效率高),如果哈希值相等,再调用equals方法。object 的equals方法默认调用“==”对比的是两个对象的内存地址
ps2:(有问题)hashCode是所有java对象的固有方法,如果不重载的话,返回的实际上是该对象在jvm的堆上的内存地址,而不同对象的内存地址肯定不同,所以这个hashCode也就肯定不同了。如果重载了的话,由于采用的算法的问题,有可能导致两个不同对象的hashCode相同。

6、示例(map 遍历


附:map 遍历的四种方法:

public static void main(String[] args) {

Map<String, String> map = new HashMap<String, String>(); map.put("1", "value1"); map.put("2", "value2"); System.out.println("通过Map.keySet遍历key和value:"); map.put("3", "value3");

//第一种:普遍使用,二次取值
System.out.println("key= "+ key + " and value= " + map.get(key)); for (String key : map.keySet()) {
}

//第二种 效率也可以
Iterator<Map.Entry<String, String>> it = map.entrySet().iterator(); System.out.println("通过Map.entrySet使用iterator遍历key和value:");
while (it.hasNext()) {
System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue()); Map.Entry<String, String> entry = it.next();
}

//第三种:推荐,尤其是容量大时
System.out.println("通过Map.entrySet遍历key和value");
System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue()); for (Map.Entry<String, String> entry : map.entrySet()) {
}

//第四种
System.out.println("通过Map.values()遍历所有的value,但不能遍历key");
for (String v : map.values()) {
} System.out.println("value= " + v);
}
示例:MapTest 
import java.util.HashMap;import java.util.Hashtable;import java.util.Iterator;import java.util.LinkedHashMap;import java.util.Map;import java.util.TreeMap;
public static void init(Map map) {public class MapTester {String key = null;
if (map != null) {
key = new Integer(i).toString() + ".0";for (int i = 5; i > 0; i--) {
map.put(key, key.toString());
// Map中的键是不重复的,如果插入两个键值一样的记录,
public static void output(Map map) {// 那么后插入的记录会覆盖先插入的记录
map.put(key, key.toString() + "0");
}
}
}

if (map != null) {
Object key = null;
key = it.next();Object value = null;
// 使用迭代器遍历Map的键,根据键取值
Iterator it = map.keySet().iterator();
while (it.hasNext()) {
value = map.get(key);
it = map.entrySet().iterator();System.out.println("key: " + key + "; value: " + value);
}
// 或者使用迭代器遍历Map的记录Map.Entry
Map.Entry entry = null;
while (it.hasNext()) {
// 一个Map.Entry代表一条记录
public static boolean containsKey(Map map, Object key) {entry = (Map.Entry) it.next();
// 通过entry可以获得记录的键和值
// System.out.println("key: " + entry.getKey() + "; value: " +
// entry.getValue());
}
}
}

if (map != null) {
public static void testHashMap() {return map.containsKey(key);
}
return false;
}

public static boolean containsValue(Map map, Object value) {
if (map != null) {
return map.containsValue(value);
}
return false;
}

Map myMap = new Hashtable();Map myMap = new HashMap();
init(myMap);
// HashMap的键可以为null
myMap.put(null, "ddd");
// HashMap的值可以为null
myMap.put("aaa", null);
output(myMap);
}

public static void testHashtable() {
init(myMap);
// LinkedHashMap的键可以为null// Hashtable的键不能为null
// myMap.put(null,"ddd");
// Hashtable的值不能为null
// myMap.put("aaa", null);
output(myMap);
}

public static void testLinkedHashMap() {
Map myMap = new LinkedHashMap();
init(myMap);
// TreeMap的值不能为nullmyMap.put(null, "ddd");
// LinkedHashMap的值可以为null
myMap.put("aaa", null);
output(myMap);
}

public static void testTreeMap() {
Map myMap = new TreeMap();
init(myMap);
// TreeMap的键不能为null
// myMap.put(null,"ddd");
System.out.println("采用LinkedHashMap");// myMap.put("aaa", null);
output(myMap);
}

public static void main(String[] args) {
System.out.println("采用HashMap");
MapTester.testHashMap();
System.out.println("采用Hashtable");
MapTester.testHashtable();
MapTester.testLinkedHashMap();
System.out.println("将myMap clear后,myMap空了么? " + myMap.isEmpty());System.out.println("采用TreeMap");
MapTester.testTreeMap();

Map myMap = new HashMap();
MapTester.init(myMap);
System.out.println("新初始化一个Map: myMap");
MapTester.output(myMap);
// 清空Map
myMap.clear();
MapTester.output(myMap);
myMap.put("aaa", "aaaa");
System.out.println("删除键aaa后,myMap包含键aaa? "myMap.put("bbb", "bbbb");
// 判断Map是否包含某键或者某值
System.out.println("myMap包含键aaa? "
+ MapTester.containsKey(myMap, "aaa"));
System.out.println("myMap包含值aaaa? "
+ MapTester.containsValue(myMap, "aaaa"));
// 根据键删除Map中的记录
myMap.remove("aaa");
}+ MapTester.containsKey(myMap, "aaa"));
// 获取Map的记录数
System.out.println("myMap包含的记录数: " + myMap.size());
}