Java util之常用数据类型特性盘点

Java util之常用数据类型特性盘点 


java.util就相当于c++的STL,是Java的一个非常重要的包,有很多常用的数据类型,不同数据类型有不同的用途,而有些数据类似乎很相似,怎样选择应用,就需要对它们进行辨析。 
下面列出了这些数据类型的特点,根据这些特点,就可以有针对性的选用 
  
* 蓝色为接口,绿色为具体实现类 
* 缩进的层次结构,就是implement或extend的层次关系 
* 每个接口或类都具备其所有上层接口、类的特性 
  
Collection 
........|--------List 
........|..........|----------ArrayList 
........|..........|----------Vector 
........|..........|.............|-----Stack 
........|..........|----------LinkedList 
........|--------Set 
...................|----------HashSet
...................|.............|-----LinkedHashSet 
...................|----------SortedSet 
.................................|-----TreeSet 
  
Iterator 
.....|-------ListIterator 
  
Map 
.....|------Hashtable 
.....|..........|------Properties 
.....|------HashMap 
.....|..........|------LinkedHashMap 
.....|------WeakHashMap 
.....|------SortedMap 
................|------TreeMap 

  
Collection
●..实现该接口及其子接口的所有类都可应用clone()方法,并是序列化类. 

.....List. 
.....●..可随机访问包含的元素 
.....●..元素是有序的 
.....●..可在任意位置增、删元素 
.....●..不管访问多少次,元素位置不变 
.....●..允许重复元素 
.....●..用Iterator实现单向遍历,也可用ListIterator实现双向遍历 

..........ArrayList 
..........●..用数组作为根本的数据结构来实现List 
..........●..元素顺序存储 
..........●..新增元素改变List大小时,内部会新建一个数组,在将添加元素前将所有数据拷贝到新数组中 
..........●..随机访问很快,删除非头尾元素慢,新增元素慢而且费资源 
..........●..较适用于无频繁增删的情况 
..........●..比数组效率低,如果不是需要可变数组,可考虑使用数组 
..........●..非线程安全 

..........Vector
..........●..另一种ArrayList,具备ArrayList的特性 
..........●..所有方法都是线程安全的(双刃剑,和ArrayList的主要区别) 
..........●..比ArrayList效率低 

...............Stack 
...............●..LIFO的数据结构 

..........LinkedList. 
..........●..链接对象数据结构(类似链表) 
..........●..随机访问很慢,增删操作很快,不耗费多余资源 
..........●..非线程安全 

.....Set
.....●..不允许重复元素,可以有一个空元素 
.....●..不可随机访问包含的元素 
.....●..只能用Iterator实现单向遍历 

..........HashSet 
..........●..用HashMap作为根本数据结构来实现Set 
..........●..元素是无序的 
..........●..迭代访问元素的顺序和加入的顺序不同 
..........●..多次迭代访问,元素的顺序可能不同 
..........●..非线程安全 

...............LinkedHashSet 
...............●..基于HashMap和链表的Set实现 
...............●..迭代访问元素的顺序和加入的顺序相同 
...............●..多次迭代访问,元素的顺序不便 
...............●..因此可说这是一种有序的数据结构 
...............●..性能比HashSet差 
...............●..非线程安全 

..........SortedSet 
..........●..加入SortedSet的所有元素必须实现Comparable接口 
..........●..元素是有序的 

...............TreeSet
...............●..基于TreeMap实现的SortedSet 
...............●..排序后按升序排列元素 
...............●..非线程安全 

----------------------------------- 
  
Iterator.. 
●..对Set、List进行单向遍历的迭代器 

..........ListIterator. 
..........●..对List进行双向遍历的迭代器 

----------------------------------- 

Map 
●..键值对,键和值一一对应 
●..不允许重复的键. 

.....Hashtable. 
.....●..用作键的对象必须实现了hashcode()、equals()方法,也就是说只有Object及其子类可用作键 
.....●..键、值都不能是空对象 
.....●..多次访问,映射元素的顺序相同 
.....●..线程安全的 

..........Properties 
..........●..键和值都是字符串 

.....HashMap 
.....●..键和值都可以是空对象 
.....●..不保证映射的顺序 
.....●..多次访问,映射元素的顺序可能不同 
.....●..非线程安全 

...............LinkedHashMap 
...............●..多次访问,映射元素的顺序是相同的 
...............●..性能比HashMap差 

.....WeakHashMap.. 
.....●..当某个键不再正常使用时,垃圾收集器会移除它,即便有映射关系存在 
.....●..非线程安全 

.....SortedMap. 
.....●..键按升序排列 
.....●..所有键都必须实现.Comparable.接口. 

...............TreeMap
...............●..基于红黑树的SortedMap实现 
...............●..非线程安全

 

 

ArrayList是List接口的一个可变长数组实现。实现了所有List接口的操作,并允许存储null值。除了没有进行同步,ArrayList基本等同于Vector。在Vector中几乎对所有的方法都进行了同步,但ArrayList仅对writeObject和readObject进行了同步,其它比如add(Object)、remove(int)等都没有同步。 

 

 

1.存储 

ArrayList使用一个Object的数组存储元素。 
private transient Object elementData[]; 
ArrayList实现了java.io.Serializable接口,这儿的transient标示这个属性不需要自动序列化。下面会在writeObject()方法中详细讲解为什么要这样作。 

 

 

2.add和remove 

 

Java代码  收藏代码
  1. public boolean add(Object o)   
  2. {   
  3.     ensureCapacity(size + 1);  // Increments modCount!!   
  4.     elementData[size++] = o;   
  5.     return true;   
  6. }   
 

 

注意这儿的ensureCapacity()方法,它的作用是保证elementData数组的长度可以容纳一个新元素。在“自动变长机制”中将详细讲解。 

Java代码  收藏代码
  1. public Object remove(int index)  
  2. {  
  3.     RangeCheck(index);  
  4.     modCount++;  
  5.     Object oldValue = elementData[index];  
  6.     int numMoved = size - index - 1;  
  7.     if (numMoved > 0)  
  8.         System.arraycopy(elementData, index + 1, elementData, index, numMoved);  
  9.     elementData[--size] = null// Let gc do its work  
  10.     return oldValue;  
  11. }  

 
RangeCheck()的作用是进行边界检查。由于ArrayList采用一个对象数组存储元素,所以在删除一个元素时需要把后面的元素前移。删除一个元素时只是把该元素在elementData数组中的引用置为null,具体的对象的销毁由垃圾收集器负责。 
modCount的作用将在下面的“iterator()中的同步”中说明。 
注:在前移时使用了System提供的一个实用方法:arraycopy(),在本例中可以看出System.arraycopy()方法可以对同一个数组进行操作,这个方法是一个native方法,如果对同一个数组进行操作时,会首先把从源部分拷贝到一个临时数组,在把临时数组的元素拷贝到目标位置。 

 

 

3.自动变长机制 

在实例化一个ArrayList时,你可以指定一个初始容量。这个容量就是elementData数组的初始长度。如果你使用: 

Java代码  收藏代码
  1. <span style="font-family: Tahoma;">    ArrayList list = new ArrayList(); </span>  

则使用缺省的容量:10。 

 

Java代码  收藏代码
  1. public ArrayList()  
  2. {  
  3.     this(10);  
  4. }  

 
ArrayList提供了四种add()方法, 

Java代码  收藏代码
  1. public boolean add(Object o)   
  2.   
  3. public void add(int index, Object element)   
  4.   
  5. public boolean addAll(Collection c)   
  6.   
  7. public boolean addAll(int index, Collection c)   

 
在每一种add()方法中,都首先调用了一个ensureCapacity(int miniCapacity)方法,这个方法保证elementData数组的长度不小于miniCapacity。ArrayList的自动变长机制就是在这个方法中实现的。 

Java代码  收藏代码
  1. public void ensureCapacity(int minCapacity)  
  2. {  
  3.     modCount++;  
  4.     int oldCapacity = elementData.length;  
  5.     if (minCapacity > oldCapacity)  
  6.     {  
  7.         Object oldData[] = elementData;  
  8.         int newCapacity = (oldCapacity * 3) / 2 + 1;  
  9.         if (newCapacity < minCapacity)  
  10.             newCapacity = minCapacity;  
  11.         elementData = new Object[newCapacity];  
  12.         System.arraycopy(oldData, 0, elementData, 0, size);  
  13.     }  
  14. }  

 
从这个方法实现中可以看出ArrayList每次扩容,都扩大到原来大小的1.5倍。 
每种add()方法的实现都大同小异,下面给出add(Object)方法的实现: 

Java代码  收藏代码
  1. public boolean add(Object o)  
  2. {  
  3.     ensureCapacity(size + 1); // Increments modCount!!  
  4.     elementData[size++] = o;  
  5.     return true;  
  6. }  
4.iterator()中的同步 

在父类AbstractList中定义了一个int型的属性:modCount,记录了ArrayList结构性变化的次数。 

Java代码  收藏代码
  1. protected transient int modCount = 0;   


在ArrayList的所有涉及结构变化的方法中都增加modCount的值,包括:add()、remove()、addAll()、removeRange()及clear()方法。这些方法每调用一次,modCount的值就加1。 
注:add()及addAll()方法的modCount的值是在其中调用的ensureCapacity()方法中增加的。 

AbstractList中的iterator()方法(ArrayList直接继承了这个方法)使用了一个私有内部成员类Itr,生成一个Itr对象(Iterator接口)返回: 

Java代码  收藏代码
  1. public Iterator iterator()  
  2. {  
  3.     return new Itr();  
  4. }  

 
Itr实现了Iterator()接口,其中也定义了一个int型的属性:expectedModCount,这个属性在Itr类初始化时被赋予ArrayList对象的modCount属性的值。 

Java代码  收藏代码
  1. int expectedModCount = modCount;   

注:内部成员类Itr也是ArrayList类的一个成员,它可以访问所有的AbstractList的属性和方法。理解了这一点,Itr类的实现就容易理解了。 

在Itr.hasNext()方法中: 

 

Java代码  收藏代码
  1. public boolean hasNext()  
  2. {  
  3.     return cursor != size();  
  4. }  

 
调用了AbstractList的size()方法,比较当前光标位置是否越界。 

在Itr.next()方法中,Itr也调用了定义在AbstractList中的get(int)方法,返回当前光标处的元素: 

Java代码  收藏代码
  1. public Object next()  
  2. {  
  3.     try  
  4.     {  
  5.         Object next = get(cursor);  
  6.         checkForComodification();  
  7.         lastRet = cursor++;  
  8.         return next;  
  9.     }  
  10.     catch (IndexOutOfBoundsException e)  
  11.     {  
  12.         checkForComodification();  
  13.         throw new NoSuchElementException();  
  14.     }  
  15. }  

 
注意,在next()方法中调用了checkForComodification()方法,进行对修改的同步检查: 

Java代码  收藏代码
  1. final void checkForComodification()  
  2. {  
  3.     if (modCount != expectedModCount)  
  4.         throw new ConcurrentModificationException();  
  5. }  

 
现在对modCount和expectedModCount的作用应该非常清楚了。在对一个集合对象进行跌代操作的同时,并不限制对集合对象的元素进行操作,这些操作包括一些可能引起跌代错误的add()或remove()等危险操作。在AbstractList中,使用了一个简单的机制来规避这些风险。这就是modCount和expectedModCount的作用所在。 

 

 

5.序列化支持 

ArrayList实现了java.io.Serializable接口,所以ArrayList对象可以序列化到持久存储介质中。ArrayList的主要属性定义如下: 

Java代码  收藏代码
  1. private static final long serialVersionUID = 8683452581122892189L;   
  2.   
  3. private transient Object elementData[];   
  4.   
  5. private int size;   


可以看出serialVersionUID和size都将自动序列化到介质中,但elementData数组对象却定义为transient了。也就是说ArrayList中的所有这些元素都不会自动系列化到介质中。为什么要这样实现?因为elementData数组中存储的“元素”其实仅是对这些元素的一个引用,并不是真正的对象,序列化一个对象的引用是毫无意义的,因为序列化是为了反序列化,当你反序列化时,这些对象的引用已经不可能指向原来的对象了。所以在这儿需要手工的对ArrayList的元素进行序列化操作。这就是writeObject()的作用。 

Java代码  收藏代码
  1. private synchronized void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException  
  2. {  
  3.     // Write out element count, and any hidden stuff  
  4.     s.defaultWriteObject();  
  5.     // Write out array length  
  6.     s.writeInt(elementData.length);  
  7.     // Write out all elements in the proper order.  
  8.     for (int i = 0; i < size; i++)  
  9.         s.writeObject(elementData[i]);  
  10. }  

 
这样元素数组elementData中的所以元素对象就可以正确地序列化到存储介质了。 
对应的readObject()也按照writeObject()方法的顺序从输入流中读取: 

Java代码  收藏代码
  1. private synchronized void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException  
  2. {  
  3.     // Read in size, and any hidden stuff  
  4.     s.defaultReadObject();  
  5.     // Read in array length and allocate array  
  6.     int arrayLength = s.readInt();  
  7.     elementData = new Object[arrayLength];  
  8.     // Read in all elements in the proper order.  
  9.     for (int i = 0; i < size; i++)  
  10.         elementData[i] = s.readObject();  
  11. }  
 
阅读更多
换一批

没有更多推荐了,返回首页