`
tooby
  • 浏览: 117308 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

Java Collection

    博客分类:
  • Java
 
阅读更多

1) 首先查看jdk中Collection类的源码后会发现如下内容:

    

1
2
3
4
5
6
7
<span style="font-size: 16px;"> ...</span><br><span style="font-size: 16px;"> * @see        AbstractCollection
 @since 1.2
 */
 
public interface Collection<E> extends Iterable<E> {
    // Query Operations
</span>

  通过查看可以发现Collection是一个接口类,其继承了java迭代接口Iterable。

 

  众所周知在我们使用Java中的类的存储的时候经常会使用一些容器,链表的概念,本文将彻底帮您弄清链表的各种概念和模型!!!!

  注意理解哦~~~ 大致框架如下:                                                                            

  Collection接口有两个主要的子接口List和Set,注意Map不是Collection的子接口哦这个要牢记。

  Collection中可以存储的元素间无序,可以重复组各 自独立的元素, 即其内的每个位置仅持有一个元素,同时允许有多个null元素对象。

  Collection接口中的方法如下:

  

   

  1)List接口

   List接口对Collection进行了简单的扩充

   查看List接口的源码会发现:

   

1
2
3
4
5
6
7
8
9
10
11
12
13
...<br> * @see AbstractList
 @see AbstractSequentialList
 @since 1.2
 */
 
public interface List<E> extends Collection<E> {
    // Query Operations
 
    /**
     * Returns the number of elements in this list.  If this list contains
     * more than <tt>Integer.MAX_VALUE</tt> elements, returns
     * <tt>Integer.MAX_VALUE</tt>.
<br>   ...

  这里也就知道为什么Collection接口时List接口的父接口了吧。

  List接口中的元素的特点为:

  List中存储的元素实现类排序,而且可以重复的存储相关元素。

 

  同时List接口又有两个常用的实现类ArrayList和LinkedList

    1)ArrayList:

      ArrayList数组线性表的特点为:类似数组的形式进行存储,因此它的随机访问速度极快。

      ArrayList数组线性表的缺点为:不适合于在线性表中间需要频繁进行插入和删除操作。因为每次插入和删除都需要移动数组中的元素。

 

      可以这样理解ArrayList就是基于数组的一个线性表,只不过数组的长度可以动态改变而已。

      对于ArrayList的详细使用信息以及创建的过程可以查看jdk中ArrayList的源码,这里不做过多的讲解。 

      对于使用ArrayList的开发者而言,下面几点内容一定要注意啦,尤其找工作面试的时候经常会被问到。

      注意啦!!!!!!!!

      a.如果在初始化ArrayList的时候没有指定初始化长度的话,默认的长度为10.    

      

1
2
3
4
5
6
/**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
    this(10);
    }

 

 

      b.ArrayList在增加新元素的时候如果超过了原始的容量的话,ArrayList扩容ensureCapacity的方案为“原始容量*3/2+1"哦。

     

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    /**
     * Increases the capacity of this <tt>ArrayList</tt> instance, if
     * necessary, to ensure that it can hold at least the number of elements
     * specified by the minimum capacity argument.
     *
     * @param   minCapacity   the desired minimum capacity
     */
    public void ensureCapacity(int minCapacity) {
    modCount++;
    int oldCapacity = elementData.length;
    if (minCapacity > oldCapacity) {
        Object oldData[] = elementData;
       <span style="color: #000000;"int newCapacity = (oldCapacity * 3)/2 1;
</span>         if (newCapacity < minCapacity)
        newCapacity = minCapacity;
            // minCapacity is usually close to size, so this is a win:
            elementData = Arrays.copyOf(elementData, newCapacity);
    }
    }

 

    c.ArrayList是线程不安全的,在多线程的情况下不要使用。

          如果一定在多线程使用List的,您可以使用Vector,因为Vector和ArrayList基本一致,区别在于Vector中的绝大部分方法都

          使用了同步关键字修饰,这样在多线程的情况下不会出现并发错误哦,还有就是它们的扩容方案不同,ArrayList是通过原始

         容量*3/2+1,而Vector是允许设置默认的增长长度,Vector的默认扩容方式为原来的2倍。

         切记Vector是ArrayList的多线程的一个替代品。

       d.ArrayList实现遍历的几种方法

         

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
30
31
32
33
34
35
36
37
38
39
40
41
package com.yonyou.test;
 
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
 
 
/**
 * 测试类
 * @author 小浩
 * @创建日期 2015-3-2
 */
 
public class Test{
public static void main(String[] args) {
     List<String> list=new ArrayList<String>();
     list.add("Hello");
     list.add("World");
     list.add("HAHAHAHA");
     //第一种遍历方法使用foreach遍历List
     for (String str : list) {            //也可以改写for(int i=0;i<list.size();i++)这种形式
        System.out.println(str);
    }
 
     //第二种遍历,把链表变为数组相关的内容进行遍历
    String[] strArray=new String[list.size()];
    list.toArray(strArray);
    for(int i=0;i<strArray.length;i++) //这里也可以改写为foreach(String str:strArray)这种形式
    {
        System.out.println(strArray[i]);
    }
     
    //第三种遍历 使用迭代器进行相关遍历
     
     Iterator<String> ite=list.iterator();
     while(ite.hasNext())
     {
         System.out.println(ite.next());
     }
 }
}

 

  

 

          尼玛,以上四点面试经常会被问到的。到时候死翘翘别说哥没告诉你。

 

    2)LinkedList

       LinkedList的链式线性表的特点为: 适合于在链表中间需要频繁进行插入和删除操作。

       LinkedList的链式线性表的缺点为: 随机访问速度较慢。查找一个元素需要从头开始一个一个的找。速度你懂的。

 

      可以这样理解LinkedList就是一种双向循环链表的链式线性表,只不过存储的结构使用的是链式表而已。

      对于LinkedList的详细使用信息以及创建的过程可以查看jdk中LinkedList的源码,这里不做过多的讲解。

      对于使用LinkedList的开发者而言,下面几点内容一定要注意啦,尤其找工作面试的过程时候经常会被问到。

      注意啦!!!!!!!!

      

      a.LinkedList和ArrayList的区别和联系

      ArrayList数组线性表的特点为:类似数组的形式进行存储,因此它的随机访问速度极快。

      ArrayList数组线性表的缺点为:不适合于在线性表中间需要频繁进行插入和删除操作。因为每次插入和删除都需要移动数组中的元素。

 

       LinkedList的链式线性表的特点为: 适合于在链表中间需要频繁进行插入和删除操作。

       LinkedList的链式线性表的缺点为: 随机访问速度较慢。查找一个元素需要从头开始一个一个的找。速度你懂的。

    

      b.LinkedList的内部实现

         对于这个问题,你最好看一下jdk中LinkedList的源码。这样你会醍醐灌顶的。

         这里我大致说一下:

         LinkedList的内部是基于双向循环链表的结构来实现的。在LinkedList中有一个类似于c语言中结构体的Entry内部类。

         在Entry的内部类中包含了前一个元素的地址引用和后一个元素的地址引用类似于c语言中指针。

     c.LinkedList不是线程安全的

         注意LinkedList和ArrayList一样也不是线程安全的,如果在对线程下面访问可以自己重写LinkedList

         然后在需要同步的方法上面加上同步关键字synchronized

     d.LinkedList的遍历方法

       

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
30
31
32
33
34
35
36
package com.yonyou.test;
 
import java.util.LinkedList;
import java.util.List;
 
 
/**
 * 测试类
 * @author 小浩
 * @创建日期 2015-3-2
 */
 
public class Test{
public static void main(String[] args) {
     
    List<String> list=new LinkedList<String>();
    list.add("Hello");
    list.add("World");
    list.add("龙不吟,虎不啸");
    //LinkedList遍历的第一种方式使用数组的方式
    String[] strArray=new String[list.size()];
    list.toArray(strArray);
    for(String str:strArray)
    {
        System.out.println(str);
    }
    //LinkedList遍历的第二种方式
    for(String str:list)
    {
        System.out.println(str);   
    }
    //至于还是否有其它遍历方式,我没查,感兴趣自己研究研究
             
 
  }
}

      e.LinkedList可以被当做堆栈来使用

        由于LinkedList实现了接口Dueue,所以LinkedList可以被当做堆栈来使用,这个你自己研究吧。

 

 

  2)Set接口

     Set接口也是Collection接口的一个常用子接口。

     查看Set接口的源码你会发现:

   

1
2
3
4
5
6
7
8
9
10
@see Collections#EMPTY_SET
 @since 1.2
 */
 
public interface Set<E> extends Collection<E> {
    // Query Operations
 
    /**
     * Returns the number of elements in this set (its cardinality).  If this
     * set contains more than <tt>Integer.MAX_VALUE</tt> elements, returns

   这里就自然而然的知道Set接口是Collection接口的子接口了吧。

   Set接口区别于List接口的特点在于:

   Set中的元素实现了不重复,有点象集合的概念,无序,不允许有重复的元素,最多允许有一个null元素对象。

   需要注意的是:虽然Set中元素没有顺序,但是元素在set中的位置是有由该元素的HashCode决定的,其具体位置其实是固定的。

   查看jdk的源码会发现下面的内容

  

1
2
3
4
5
6
7
8
9
10
11
...<br> * @see Collections#EMPTY_SET
 @since 1.2
 */
 
public interface Set<E> extends Collection<E> {
    // Query Operations
 
    /**
     * Returns the number of elements in this set (its cardinality).  If this
     * set contains more than <tt>Integer.MAX_VALUE</tt> elements, returns
<br> ...

    在这里也会看到set接口时Collection接口的子接口吧~哈

   此外需要说明一点,在set接口中的不重复是由特殊要求的。

    举一个例子:对象A和对象B,本来是不同的两个对象,正常情况下它们是能够放入到Set里面的,但是

    如果对象A和B的都重写了equals方法,并且重写后的equals方法是相同的话。那么A和B是不能同时放入到

    Set集合中去的,也就是Set集合中的去重和equals方法直接相关。

    为了更好的理解,请看下面的例子:

   

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.yonyou.test;
 
import java.util.HashSet;
import java.util.Set;
 
 
/**
 * 测试类
 * @author 小浩
 * @创建日期 2015-3-2
 */
 
public class Test{
public static void main(String[] args) {
     Set<String> set=new HashSet<String>();
     set.add("Hello");
     set.add("world");
     set.add("Hello");
     System.out.println("集合的尺寸为:"+set.size());
     System.out.println("集合中的元素为:"+set.toString());
  }
}

  由于String类中重写了equals方法,所以这里的第二个Hello是加不进去的哦。

  

 

    Set接口的常见实现类有HashSet,LinedHashSet和TreeSet这三个。下面我们将分别讲解这三个类。

      1)HashSet

         HashSet是Set接口的最常见的实现类了。其底层是基于Hash算法进行存储相关元素的。

         下面是HashSet的部分源码:

        

1
2
3
4
5
6
7
/**
    * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
    * default initial capacity (16) and load factor (0.75).
    */
   public HashSet() {
   map = new HashMap<E,Object>();
   }

   你看到了什么,没错,对于HashSet的底层就是基于HashMap来实现的哦。

   我们都知道在HashMap中的key是不允许重复的,你换个角度看看,那不就是说Set集合吗?

   这里唯一一个需要处理的就是那个Map的value弄成一个固定值即可。

   看来一切水到渠成啊~哈哈~这里的就是Map中的Key。

   对于HashMap和Hash算法的讲解会在下面出现,先别着急,下面继续讲解HashSet。

  

    下面讲解一下HashSet使用和理解中容易出现的误区:

     a.HashSet中存放null值

        HashSet中时允许出入null值的,但是在HashSet中仅仅能够存入一个null值哦。

     b.HashSet中存储元素的位置是固定的

        HashSet中存储的元素的是无序的,这个没什么好说的,但是由于HashSet底层是基于Hash算法实现的,使用了hashcode,

        所以HashSet中相应的元素的位置是固定的哦。

     c.遍历HashSet的几种方法

         具体的方法不说了,请看下面的代码:

      

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
30
31
32
33
34
35
36
37
38
39
40
package com.yonyou.test;
 
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
 
 
/**
 * 测试类
 * @author 小浩
 * @创建日期 2015-3-2
 */
 
public class Test{
public static void main(String[] args) {
     Set<String> set=new HashSet<String>();
     set.add("Hello");
     set.add("world");
     set.add("Hello");
     //遍历集合的第一种方法,使用数组的方法
     String[] strArray=new String[set.size()];
     strArray=set.toArray(strArray);
     for(String str:strArray)//此处也可以使用for(int i=0;i<strArray.length;i++)
     {
         System.out.println(str);
     }
     //遍历集合的第二中方法,使用set集合直接遍历
     for(String str:set)
     {
         System.out.println(str);
     }
      
     //遍历集合的第三种方法,使用iterator迭代器的方法
     Iterator<String> iterator=set.iterator();
     while(iterator.hasNext())
     {
         System.out.println(iterator.next());
     }
}
}

     2)LinkHashSet

       LinkHashSet不仅是Set接口的子接口而且还是上面HashSet接口的子接口。

       查看LinkedHashSet的部分源码如下:

      

1
2
3
4
5
6
7
8
9
10
11
12
13
...<br> * @see     Hashtable
 @since   1.4
 */
 
public class LinkedHashSet<E>
    extends HashSet<E>
    implements Set<E>, Cloneable, java.io.Serializable {
 
    private static final long serialVersionUID = -2851667679971038690L;
 
    /**
     * Constructs a new, empty linked hash set with the specified initial
<br>  ...

     这里就可以发现Set接口时HashSet接口的一个子接口了吧~

     通过查看LinkedHashSet的源码可以发现,其底层是基于LinkedHashMap来实现的哦。

     对于LinkedHashSet而言,它和HashSet主要区别在于LinkedHashSet中存储的元素是在哈希算法的基础上增加了

     链式表的结构。

    

    3)TreeSet

       TreeSet是一种排序二叉树。存入Set集合中的值,会按照值的大小进行相关的排序操作。底层算法是基于红黑树来实现的。

       TreeSet和HashSet的主要区别在于TreeSet中的元素会按照相关的值进行排序~

       TreeSet和HashSet的区别和联系

       1. HashSet是通过HashMap实现的,TreeSet是通过TreeMap实现的,只不过Set用的只是Map的key
       2. Map的key和Set都有一个共同的特性就是集合的唯一性.TreeMap更是多了一个排序的功能.
       3. hashCode和equal()是HashMap用的, 因为无需排序所以只需要关注定位和唯一性即可.
           a. hashCode是用来计算hash值的,hash值是用来确定hash表索引的.
           b. hash表中的一个索引处存放的是一张链表, 所以还要通过equal方法循环比较链上的每一个对象
              才可以真正定位到键值对应的Entry.
           c. put时,如果hash表中没定位到,就在链表前加一个Entry,如果定位到了,则更换Entry中的value,并返回旧value
       4. 由于TreeMap需要排序,所以需要一个Comparator为键值进行大小比较.当然也是用Comparator定位的.
           a. Comparator可以在创建TreeMap时指定
           b. 如果创建时没有确定,那么就会使用key.compareTo()方法,这就要求key必须实现Comparable接口.
           c. TreeMap是使用Tree数据结构实现的,所以使用compare接口就可以完成定位了.

         下面是一个使用TreeSet的实例:

 

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
package com.yonyou.test;
 
import java.util.Iterator;
import java.util.TreeSet;
 
/**
 * TreeSet测试类
 * @author 小浩
 * @创建日期 2015-4-3
 */
public class Test{
    public static void main(String[] args) {
        //String实体类中实现Comparable接口,所以在初始化TreeSet的时候,
        //无需传入比较器
        TreeSet<String> treeSet=new TreeSet<String>();
        treeSet.add("d");
        treeSet.add("c");
        treeSet.add("b");
        treeSet.add("a");
        Iterator<String> iterator=treeSet.iterator();
        while(iterator.hasNext())
        {
            System.out.println(iterator.next());
        }
         
     }
}

 

 3)Map接口

      说到Map接口的话大家也许在熟悉不过了。Map接口实现的是一组Key-Value的键值对的组合。 Map中的每个成员方法由一个关键字(key)和一个值(value)构成。Map接口不直接继承于Collection接口(需要注意啦),因为它包装的是一组成对的“键-值”对象的集合,而且在Map接口的集合中也不能有重复的key出现,因为每个键只能与一个成员元素相对应。在我们的日常的开发项目中,我们无时无刻不在使用者Map接口及其实现类。Map有两种比较常用的实现:HashMap和TreeMap等。HashMap也用到了哈希码的算法,以便快速查找一个键,TreeMap则是对键按序存放,因此它便有一些扩展的方法,比如firstKey(),lastKey()等,你还可以从TreeMap中指定一个范围以取得其子Map。键和值的关联很简单,用pub(Object key,Object value)方法即可将一个键与一个值对象相关联。用get(Object key)可得到与此key对象所对应的值对象。

       另外前边已经说明了,Set接口的底层是基于Map接口实现的。Set中存储的值,其实就是Map中的key,它们都是不允许重复的。

      Map接口的部分源码如下:

     

1
2
3
4
5
6
7
8
9
@see Collection
 @see Set
 @since 1.2
 */
public interface Map<K,V> {
    // Query Operations
 
    /**
     * Returns the number of key-value mappings in this map.  If the

  

      为了更好的理解上面的内容,这里我们有必要简单了解一下Hash算法的内容,由于篇幅限制,这里就不具体的讲解Hash算法的实现过程了。如果感兴趣的可以参考:http://www.cnblogs.com/xiohao/p/4389672.html

      接下来我们讲解Map接口的常见实现类HashMap、TreeMap、LinkedHashMap、Properties(继承HashTable)以及老版本的HashTable等。

      3)HashMap

       HashMap实现了Map、CloneMap、Serializable三个接口,并且继承自AbstractMap类

         HashMap基于hash数组实现,若key的hash值相同则使用链表方式进行保存

   

     

    

      新建一个HashMap时,默认的话会初始化一个大小为16,负载因子为0.75的空的HashMap

1
2
3
4
5
6
7
8
9
10
/**
     * Constructs an empty <tt>HashMap</tt> with the default initial capacity
     * (16) and the default load factor (0.75).
     */
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
        table = new Entry[DEFAULT_INITIAL_CAPACITY];
        init();
    }

   下面是一个HashMap中还存在一个内部类Entry,用于链表的存储,如

  

1
static class Entry<K,V> implements Map.Entry<K,V> {<br>      final K key;<br>      V value;<br>      Entry<K,V> next;<br>      final int hash;<br>      ......

 

  面代码其实告诉我们Entry是一个结点,它持有下一个元素的引用,这样就构成了一个链表。

那么,整体上来说HashMap底层就是使用这样一个数据结构来实现的。

我们提到使用Hash,但是Hash值如何与元素的存储建立关系呢?(Hash算法)

在数据结构课中我们学习过Hash的简单算法,就是给你一个Hash因子,通过对该元素的hashCode简单的求余,来实现对其快速的定位和索引。

在HashMap中有这样的代码:

  1. /**
  2.   * Returns index for hash code h.
  3.   */ 
  4. static int indexFor(int h, int length) { 
  5.      return h & (length-1); 
/**
  * Returns index for hash code h.
  */
static int indexFor(int h, int length) {
     return h & (length-1);
}

这个方法在HashMap中非常重要,凡是与查询、添加、删除有关的方法中都有调用该方法,为什么这么短的一个代码使用率这么高?根据代码注释我们知道,这个方法是根据hashCode及当前table的长度(数组的长度,不是map的size)得到该元素应该存放的位置,或者在table中的索引。

现在我们需要看一下当数据量已经超过初始定义的负载因子时,HashMap如何处理?

在HashMap中当数据量很多时,并且已经达到了负载限度时,会重新做一次哈希,也就是说会再散列。调用的方法为resize(),并且java默认传入的参数为2*table.length。先看一下JDK源码:

  1. /**
  2.      * Rehashes the contents of this map into a new array with a
  3.      * larger capacity.  This method is called automatically when the
  4.      * number of keys in this map reaches its threshold.
  5.      *
  6.      * If current capacity is MAXIMUM_CAPACITY, this method does not
  7.      * resize the map, but sets threshold to Integer.MAX_VALUE.
  8.      * This has the effect of preventing future calls.
  9.      *
  10.      * @param newCapacity the new capacity, MUST be a power of two;
  11.      *        must be greater than current capacity unless current
  12.      *        capacity is MAXIMUM_CAPACITY (in which case value
  13.      *        is irrelevant).
  14.      */ 
  15.     void resize(int newCapacity) { 
  16.         Entry[] oldTable = table; 
  17.         int oldCapacity = oldTable.length; 
  18.         if (oldCapacity == MAXIMUM_CAPACITY) { 
  19.             threshold = Integer.MAX_VALUE; 
  20.             return; 
  21.         } 
  22.  
  23.         Entry[] newTable = new Entry[newCapacity]; 
  24.         transfer(newTable); 
  25.         table = newTable; 
  26.         threshold = (int)(newCapacity * loadFactor); 
  27.     } 
  28.  
  29.     /**
  30.      * Transfers all entries from current table to newTable.
  31.      */ 
  32.     void transfer(Entry[] newTable) { 
  33.         Entry[] src = table; 
  34.         int newCapacity = newTable.length; 
  35.         for (int j = 0; j < src.length; j++) { 
  36.             Entry<K,V> e = src[j]; 
  37.             if (e != null) { 
  38.                 src[j] = null; 
  39.                 do { 
  40.                     Entry<K,V> next = e.next; 
  41.                     int i = indexFor(e.hash, newCapacity); 
  42.                     e.next = newTable[i]; 
  43.                     newTable[i] = e; 
  44.                     e = next; 
  45.                 } while (e != null); 
  46.             } 
  47.         } 
  48.     } 
/**
     * Rehashes the contents of this map into a new array with a
     * larger capacity.  This method is called automatically when the
     * number of keys in this map reaches its threshold.
     *
     * If current capacity is MAXIMUM_CAPACITY, this method does not
     * resize the map, but sets threshold to Integer.MAX_VALUE.
     * This has the effect of preventing future calls.
     *
     * @param newCapacity the new capacity, MUST be a power of two;
     *        must be greater than current capacity unless current
     *        capacity is MAXIMUM_CAPACITY (in which case value
     *        is irrelevant).
     */
    void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }

        Entry[] newTable = new Entry[newCapacity];
        transfer(newTable);
        table = newTable;
        threshold = (int)(newCapacity * loadFactor);
    }

    /**
     * Transfers all entries from current table to newTable.
     */
    void transfer(Entry[] newTable) {
        Entry[] src = table;
        int newCapacity = newTable.length;
        for (int j = 0; j < src.length; j++) {
            Entry<K,V> e = src[j];
            if (e != null) {
                src[j] = null;
                do {
                    Entry<K,V> next = e.next;
                    int i = indexFor(e.hash, newCapacity);
                    e.next = newTable[i];
                    newTable[i] = e;
                    e = next;
                } while (e != null);
            }
        }
    }

看到这里我们会发现resize(再哈希)的工作量是不是很大啊。再哈希是重新建一个指定容量的数组,然后将每个元素重新计算它要放的位置,这个工作量确实是很大的。

这里就产生了一个很重要的问题,那就是怎么让哈希表的分布比较均匀,也就是说怎么让它即不会成为一个单链表(极限情况,每个key的hash值都集中到了一起),又不会使hash空间过大(导致内存浪费)?

上面两个问题一个是解决了怎么计算hash值快速存取,一个是怎么实现再哈希,何时需要再哈希。快速存取的前提是元素分布均匀,不至于集中到一点,再哈希是元素过于零散,导致不断的重新构建表。

那么在第一个问题中我们看到了这样一个代码return h & (length-1);在第二个问题中我们说过内部调用传入的值为2*table.length;并且默认情况下HashMap的大小为一个16的数字,除了默认构造提供大小为16的空间外,如果我们使用

public HashMap(int initialCapacity, float loadFactor)

上面的构造方法,我们会发现这样的代码:

  1. // Find a power of 2 >= initialCapacity 
  2. int capacity = 1; 
  3. while (capacity < initialCapacity) 
  4.       capacity <<= 1; 
  5. …… 
  6. table = new Entry[capacity]; 
// Find a power of 2 >= initialCapacity
int capacity = 1;
while (capacity < initialCapacity)
      capacity <<= 1;
……
table = new Entry[capacity];

也就是说当我们传入1000时,它并没有给我们构造一个容量为1000的哈希表,而是构建了一个容量为1024大小的哈希表。

从整体上我们发现一个问题,那就是无论什么情况HashMap中哈希表的容量总是2的n次方的一个数。并且有这样一个公式:

       当length=2^n时,hashcode & (length-1) == hashcode % length

也就是这一点验证了第一个问题,hash索引值的计算方法其实就是对哈希因子求余。只有大小为2的n次方时,那样的计算才成立,所以HashMap为我们维护了一个这样大小的一个哈希表。(位运算速度比取模运算快的多)

c)      HashMap的使用方法:

我在很多代码中都用到了HashMap,原因是首先它符合存储关联数据的要求,其次它的存取速度快,这是一个选择的问题。

比较重要的是HashMap的遍历方法,在我的博客中有专门写到HashMap的遍历方法:http://blog.csdn.net/tsyj810883979/article/details/6746274

Ø  LinkedHashMap的特点、实现机制及使用方法

a)      LinkedHashMap的特点:

LinkedHashMap继承自HashMap并且实现了Map接口。和HashMap一样,LinkedHashMap允许key和value均为null。

于该数据结构和HashMap一样使用到hash算法,因此它不能保证映射的顺序,尤其是不能保证顺序持久不变(再哈希)。

如果你想在多线程中使用,那么需要使用Collections.synchronizedMap方法进行外部同步。

LinkedHashMap与HashMap的不同之处在于,LinkedHashMap维护者运行于所有条目的双重链接列表,此链接列表可以是插入顺序或者访问顺序。

b)      LinkedHashMap的实现机制:

无法总结下去,在网上看到这样一篇文章:http://zhangshixi.iteye.com/blog/673789

感觉真的没办法总结下去了。

Ø  HashMap与Hashtable的区别:

Hashtable实现Map接口,继承自古老的Dictionary类,实现一个key-value的键值映射表。任何非空的(key-value)均可以放入其中。

区别主要有三点:

1.      Hashtable是基于陈旧的Dictionary实现的,而HashMap是基于Java1.2引进的Map接口实现的;

2.      Hashtable是线程安全的,而HashMap是非线程安全的,我们可以使用外部同步的方法解决这个问题。

3.      HashMap可以允许你在列表中放一个key值为null的元素,并且可以有任意多value为null,而Hashtable不允许键或者值为null。

Ø  WeakHashMap的特点:

我没有使用过这个类。网摘:WeakHashMap是一种改进的HashMap,它对key实行“弱引用”,如果一个key不再被外部所引用,那么该key可以被GC回收。(后续使用后进行总结)

Ø  Properties及TreeMap在后续内容里进行总结。

 

就先这样吧,Java集合框架的讲解就先到这里了啊~

分享到:
评论

相关推荐

    java collection framework

    ### Java Collection Framework 相关知识点 #### 一、引言 在 Java 领域,《Java Collection Framework》这本书被广泛认为是一本优秀的教程,尤其适合初学者了解集合框架的前世今生。通过本书的学习,读者不仅能...

    8.javaCollection接口.zip

    8.javaCollection接口.zip8.javaCollection接口.zip8.javaCollection接口.zip8.javaCollection接口.zip8.javaCollection接口.zip8.javaCollection接口.zip8.javaCollection接口.zip8.javaCollection接口.zip8.java...

    java collection

    Java集合框架是Java编程语言中一个非常核心的部分,它提供了数据结构和算法的实现,使得开发者可以方便地存储和管理对象。在这个学习笔记中,我们将深入探讨ArrayList、HashMap、LinkedList和HashSet这四个重要的...

    JAVA COLLECTION (APress)

    《JAVA COLLECTION》是一本专注于Java集合框架的书籍,由APress出版社出版。这本书深入浅出地探讨了Java API中的各种数据结构,是学习和理解Java集合框架的理想资源。作者通过简洁明了的语言,使得初学者也能轻松...

    java Collection类整理

    Java集合框架中的`Collection`接口是所有单值容器的基础接口,它定义了基本的增删查改元素的方法。`Collection`有两个主要的子接口:`List`和`Set`。`List`接口要求元素保持特定的顺序,并允许重复元素;而`Set`接口...

    Java collection_java_控制服务器_服务器_

    在“Java collection_java_控制服务器_服务器_”这个主题中,我们将深入探讨Java集合框架以及如何利用它们来构建服务器控制程序。 首先,Java集合框架包括接口(如List、Set、Queue)和实现这些接口的具体类(如...

    关于Java_Collection_API_

    ### Java Collection API 关键知识点详解 #### 一、线程安全集合类 在Java的Collection框架中,集合类被划分为两大类:线程安全集合类与非线程安全集合类。早期版本的集合类(如`Vector`和`Hashtable`)通过`...

    java Collection 详细介绍

    ### Java Collection 概述 Java Collection 是 Java 编程语言中的一个重要组成部分,它提供了一种组织和管理数据的方式。在 Java 中,集合框架是围绕着一组接口构建的,这些接口规定了不同类型的容器如何存储、检索...

    java泛型集合 java集合 集合 java Collection

    本文将深入探讨这两个主题,并着重讲解`Collection`接口及其在Java中的应用。 首先,Java泛型是一种在编译时提供类型安全性的机制,它允许我们在创建集合时指定元素的类型。这样可以防止在运行时出现...

    Java Collection 移除元素方法及注意事项

    Java Collection 移除元素方法及注意事项 Java Collection 中的元素移除操作是 Java 编程人员每天都在重复的事情。本文主要介绍了 Java Collection 移除元素方法及注意事项,通过简单实例讲解了从 Java Collection ...

    Java Collection集合遍历运行代码实例

    Java Collection 集合遍历运行代码实例 Java Collection 集合遍历是 Java 语言中最常用的数据结构之一。Collection 是 Java 集合框架中的一个接口,提供了基本的集合操作,例如添加、删除、遍历元素等。Collection ...

    浅谈java Collection中的排序问题

    在Java编程语言中,Collection框架是处理集合数据的重要部分,其中包含了List、Set和Map三种主要的数据结构。本文将深入探讨这些数据结构在排序方面的实现和应用。 首先,我们来看List的排序。List接口提供了直接...

    java Collection&Map

    Java集合框架是Java编程语言中一个非常重要的组成部分,它提供了数据结构和算法的抽象,使得开发者可以方便地存储和管理对象。在这个框架中,Collection和Map接口及其实现类扮演着核心角色。 1. **Collection接口**...

    java_ms.rar_Math Class_java collection

     第六,Collection 和 Collections的区别。  你千万别说一个是单数一个是复数。   第七,什么时候用assert。  API级的技术人员有可能会问这个。   第八,GC是什么? 为什么要有GC?  基础。   第九,...

    Java集合Collection、List、Set、Map使用详解

    本文将深入解析Java集合中的Collection、List、Set和Map,包括它们的使用方法、实现原理以及如何进行排序。 ### 集合框架概述 1.1.1 容器简介 容器是Java集合框架的基础,它是一个可以存储多个对象的容器,提供了...

    Java Collection集合iterator方法解析

    Java Collection 集合 iterator 方法解析 Java Collection 集合 iterator 方法是一种非常重要的方法,通过该方法我们可以对集合进行遍历和操作。下面我们将详细介绍 Java Collection 集合 iterator 方法的实现原理...

    JAVA_Collection框架

    ### JAVA Collection框架详解 #### 一、概述 Java Collection框架 是 Java 核心库中一个重要的组成部分,它为集合类提供了一种通用的接口、实现以及算法。在 Java 开发过程中,我们经常会遇到需要处理一组对象的...

    Java.util.Collection类的学习.pdf

    Java.util.Collection类的学习 Java.util.Collection类是Java编程语言中的一个基础类库,提供了许多有用的方法来操作集合对象。Collection类包含了许多静态方法,可以对集合进行排序、混排、反转、替换等操作。 1....

    Java Collection集合的简单介绍与运用

    集合按照其存储结构可以分为两大类,分别是单列集合java.util.Collection和双列集合java.util.Map,这里主要记录一下Collection集合。 Collection:单列集合类的根接口,用于存储一系列符合某种规则的元素,它有两个...

Global site tag (gtag.js) - Google Analytics