JAVA 基础之 集合框架

另 集合框架实际使用的思考:

http://www.tot.name/show/3/7/20060530161152.htm

原文出处 : http://www.cnblogs.com/zhxxcq/archive/2012/03/11/2389611.html

另: 源码及原理分析 : http://wiki.jikexueyuan.com/project/java-collection/linkedlist.html

要点1:

Java的集合类主要由两个接口派生而出:CollectionMap,Collection和Map是Java集合框架的根接口,这两个接口又包含了一些接口或实现类。

 

image

Set和List接口是Collection接口派生的两个子接口,Queue是Java提供的队列实现,类似于List。

image

Map实现类用于保存具有映射关系的数据(key-value)。

Set、List和Map可以看做集合的三大类。

     List集合是有序集合,集合中的元素可以重复,访问集合中的元素可以根据元素的索引来访问。

     Set集合是无序集合,集合中的元素不可以重复,访问集合中的元素只能根据元素本身来访问(也是不能集合里元素不允许重复的原因)。

     Map集合中保存Key-value对形式的元素,访问时只能根据每项元素的key来访问其value。

对于Set、List和Map三种集合,最常用的实现类分别是HashSet、ArrayListHashMap三个实现类


要点2:

两种遍历集合的方法Iterator接口和foreach循环

Iterator it = books.iterator(); 

        while(it.hasNext()) 
        { 
       //未使用泛型,需要强制转换
            String book = (String)it.next(); 

            System.out.println(book); 

            if (book.equals("Struts2权威指南")) 
            { 
                it.remove();

            //使用Iterator迭代过程中,不可修改集合元素,下面代码引发异常
            //books.remove(book); 

            } 

            //对book变量赋值,不会改变集合元素本身 
             book = "测试字符串"; 

        } 
 /**
     * 集合转换为一维数组
     */
    public void listToArray() {
        // 创建List并添加元素
        List<String> list = new ArrayList<String>();
        list.add("1");
        list.add("3");
        list.add("4");

        // 利用froeach语句输出集合元素
        System.out.println("----2----froeach语句输出集合元素");

        for (String x : list) {
            System.out.println(x);
        }

        // 将ArrayList转换为数组
        Object s[] = list.toArray();

        // 利用froeach语句输出集合元素
        System.out.println("----2----froeach语句输出集合转换而来的数组元素");

        for (Object x : s) {
            System.out.println(x.toString()); // 逐个输出数组元素的值
        }
    }


要点3: Set

Set集合不允许重复元素,是因为Set判断两个对象相同不是使用==运算符,而是根据equals方法。(不同子类还有新的扩展,例如HashSet比较元素还需要比较Hashcode)即两个对象用equals方法比较返回true,Set就不能接受两个对象。

public class TestSet
{
    public static void main(String[] args) 
    {
        Set<String> books = new HashSet<String>();
        
        //添加一个字符串对象
        books.add(new String("Struts2权威指南"));
        
        //再次添加一个字符串对象,
        //因为两个字符串对象通过equals方法比较相等,所以添加失败,返回false
        boolean result = books.add(new String("Struts2权威指南"));
        
        System.out.println(result);
        
        //下面输出看到集合只有一个元素
        System.out.println(books);    
    }
}

结果:

false 
[Struts2权威指南]

序中,book集合两次添加的字符串对象明显不是一个对象(程序通过new关键字来创建字符串对象),当使用==运算符判断返回false,使用equals方法比较返回true,所以不能添加到Set集合中,最后只能输出一个元素。


要点4: HashSet :

HashSet按Hash算法来存储集合的元素,因此具有很好的存取和查找性能。

          HashSet的特点:

(1)HashSet不是同步的,多个线程访问是需要通过代码保证同步 

(2)集合元素值可以使null。

       HashSet集合判断两个元素相等的标准是两个对象通过equals方法比较相等,并且两个对象的hashCode()方法返回值也相等。所以有必要对两个方法都重写,而且注意规则,equals方法比较的属性一定要是用常量属性,防止出现对象属性修改后,出现set集合中出现相同元素。

//类A的equals方法总是返回true,但没有重写其hashCode()方法
class A
{
    public boolean equals(Object obj)
    {
        return true;
    }
}
//类B的hashCode()方法总是返回1,但没有重写其equals()方法
class B
{
    public int hashCode()
    {
        return 1;
    }
}
//类C的hashCode()方法总是返回2,并重写其equals()方法
class C
{
    public int hashCode()
    {
        return 2;
    }
    public boolean equals(Object obj)
    {
        return true;
    }
}
public class TestHashSet
{
    public static void main(String[] args) 
    {
        HashSet<Object> books = new HashSet<Object>();
        //分别向books集合中添加2个A对象,2个B对象,2个C对象
        books.add(new A());
        books.add(new A());
        books.add(new B());
        books.add(new B());
        books.add(new C());
        books.add(new C());
        System.out.println(books);
    }
}

程序运行结果:

[B@1, B@1C@2A@b5dac4A@9945ce]

           

说明:

(1)Object类提供的toString方法总是返回该对象实现类的类名+@+hashCode(16进制数)值,所以可以看到上面程序输出的结果。可以通过重写toString方法来输出自己希望的形式。

(2)即使2个A对象通过equals比较返回true,但HashSet依然把它们当成2个对象;即使2个B对象的hashCode()返回相同值,但HashSet依然把它们当成2个对象。由于C即equals和hashCode都重写了,才能准确判断元素相同。如果把一个对象放入HashSet中时,如果重写该对象equals()方法,也应该重写其hashCode()方法。要不就都不重写 。

关于equals 和hashcode :http://blog.csdn.net/chinayuan/article/details/3345559

hash算法的功能:

它能保证通过一个对象快速查找到另一个对象。hash算法的价值在于速度,它可以保证查询得到快速执行。

当需要查询集合中某个元素时,hash算法可以直接根据该元素的值得到该元素保存位置,从而可以让程序快速找到该元素。

当从HashSet中访问元素时,HashSet先计算该元素的hashCode值(也就是调用该对象的hashCode())方法的返回值),然后直接到该hashCode对应的位置去取出该元素。

即也是快速的原因。HashSet中每个能存储元素的“曹位(slot)”通常称为“桶(bucket)”,如果多个元素的hashCode相同,但它们通过equals()方法比较返回false,就需要一个“桶”里放多个元素,从而导致性能下降。

 注:当向HashSet中添加一个可变对象后,并且后面程序修改了该可变对象的属性,可能导致它与集合中其他元素相同,这就可能导致HashSet中包含两个相同的对象。

所以要注意规则,equals方法比较的属性一定要是用常量属性,防止出现对象属性修改后,出现set集合中出现相同元素。


要点5: TreeSet:

TreeSet 的排序 :

1.TreeSet是SortedSet接口的唯一实现,TreeSet可以确保集合元素处于排序状态(元素是有序的,采用自然排序)。

2.TreeSet会调用集合元素的compareTo(Object obj)方法来比较元素之间大小关系,然后将集合元素按升序排列,这种方式就是自然排序。

例如obj1.comparTo(obj2),如果该方法返回0,则表明这两个对象相等;如果返回一个正整数,则表明obj1大于obj2;如果该方法返回一个负整数,则表明obj1小于obj2.

TreeSet 的比较重复元素 :

1. 两个对象通过equals方法比较返回false,或通过compareTo(Object obj)比较没有返回0   ,则TreeSet 会把两个元素看作不同的元素。  

2. 总结:当需要把一个对象放入TreeSet中时,重写该对象对应类的equals()方法时,应保证该方法与compareTo(Object obj)方法有一致结果,其规则是:如果两个对象通过equals方法比较返回true时,这两个对象通过compareTo(Object obj)方法比较应返回0.

而且注意规则,equals方法比较的属性一定要是用常量属性,防止出现对象属性修改后,出现set集合中出现相同元素。

  TreeSet提供的几个额外方法:

public class TestTreeSetCommon
{
    public static void main(String[] args) 
    {
        TreeSet<Integer> nums = new TreeSet<Integer>();
        //向TreeSet中添加四个Integer对象
        nums.add(5);
        nums.add(2);
        nums.add(10);
        nums.add(-9);
        //输出集合元素,看到集合元素已经处于排序状态
        System.out.println(nums);
        //输出集合里的第一个元素
        System.out.println(nums.first());
        //输出集合里的最后一个元素
        System.out.println(nums.last());
        //返回小于4的子集,不包含4
        System.out.println(nums.headSet(4));
        //返回大于5的子集,如果Set中包含5,子集中还包含5
        System.out.println(nums.tailSet(5));
        //返回大于等于-3,小于4的子集。
        System.out.println(nums.subSet(-3 , 4));
    }
}

程序运行结果:

[-9, 2, 5, 10] 
-9 
10 
[-9, 2] 
[5, 10] 
[2]

TreeSet 添加元素过程:

1.  添加第一个元素,无须判断,直接加入。

2 . 添加第二个元素,先判断是否是于前一元素相同,相同则不插入。

3. 根据compareTo()判断插入位置,此时不应该出现等于0的情况,应该按照规则让compareTo和equals方法保持一直。

 HashSet 和 TreeSet 使用选择:

哈希表在进行插入、删除、查找这样的操作是很快的,其时间复杂度是常数级O(1);平衡二叉树虽然插入、删除操作比较麻烦(需要O(log n)的代价),但进行遍历和排序却很快。选择完全在于用户的侧重点,但由于类型转换的方便性,通常我们用哈希表构造一个集合以后,再把它转换成相应的树集进行遍历,以获得较好的效果。

Set set1 = new HashSet();
set1.add(elem1);// 通过插入元素构造集合
set1.add(elem2);
set1.add(elem3);
Set set2 = new TreeSet(set);
Iterator all = set2.iterator();
while (all.hasNext())
{// 遍历集合
all.next();
...
}

要点6: List :

1、List接口和ListIterator接口

    List作为Collection接口的子接口,可以使用Collection接口里的全部方法。List是有序集合,所以List集合里增加了一些根据索引来操作集合元素的方法:

  • void add(int index, Object element):将元素element插入在List集合的index处。

  • boolean addAll(int index, Collection c):将集合c所包含的所有元素都插入在List集合的index处。

  • Object get(int index):返回集合index索引处的元素。

  • int lastIndexOf(Object o):返回对象o在List集合中最后一次出现的位置索引。

  • Object remove(int index):删除并返回index索引处的元素。

  • Object set(int index, Object element):将index索引处的元素替换成element对象,返回新元素。

  • List subList(int fromIndex, int toIndex):返回从索引fromIndex(包含)到索引toIndex(不包含)处所有集合元素组成的子集合。

    List集合可以根据索引来插入、替换和删除集合元素。

关于使用List集合的几点建议:

  • 如果需要遍历List集合元素,对应ArrayList、Vector集合,则应该使用随机访问方法(get)来遍历集合元素,这样性能更好。对应LinkedList集合,则应采用迭代器(Iterator)来遍历集合元素。
  • 如果需要经常执行插入、删除操作来改变Lst集合大小,则应该使用LinkedList集合,而不是ArrayList。
  • 如果多条线程需要同时访问List集合中的元素,可以考虑使用Vector这个同步实现。

 

要点7:ArrayList和Vector实现类

    ArrayList(线程不安全)和Vector(线程安全)作为List类的两个典型实现,完全支持前面介绍的List接口全部功能。

    ArrayList和Vector类都是基于数组实现的List类,他们封装了一个动态再分配的Object[]数组。每个ArrayList或Vector对象有一个capacity属性,表示它们所封装的Object[]数组的长度。capacity会添加元素的个数而自动增加。当向集合中添加大量元素时,可以使用ensureCapacity方法一次性地增加capacity。这可以减少增加重分配次数,从而提供性能。capacity大小也可以在创建时就指定,该属性默认为10.

ArrayListVector提供如下两个方法来操作capacity属性:

  • void ensureCapacity(int minCapacity):将ArrayList或Vector集合的capacity增加minCapacity。
  • void trimToSize():调整ArrayList或Vector集合的capacity为列表当前大小。程序可调用该方法来减少ArrayList或Vector集合对象存储空间。

 Vector还提供了一个Stack子类,它用于模拟了”栈“这种数据结构,

  • Object peek():返回”栈“的第一个元素,但并不将该元素”pop“出栈。
  • Object pop():返回”栈“的第一个元素,并将该元素”pop“出栈。
  • void push(Object item):将一个元素”push“进栈,最后一个进”栈“的元素总是位于”栈“顶。

Arraylist 线程不安全解释:

ArrayList 类,在添加一个元素的时候,它可能会有两步来完成:

1. 在 Items[Size] 的位置存放此元素;2. 增大 Size 的值。


 

要点8:LinkedList实现类

List还有一个LinkedList的实现,它是一个基于链表实现的List类,对于顺序访问集合中的元素进行了优化,特别是当插入、删除元素时速度非常快。因为LinkedList即实现了List接口,也实现了Deque接口(双向队列),Deque接口是Queue接口的子接口,它代表一个双向列表,Deque接口里定义了一些可以双向操作队列的方法:

  • void addFirst(Object e):将制定元素插入该双向队列的开头。
  • void addLast(Object e):将制定元素插入该双向队列的末尾。
  • Iterator descendingIterator():返回以该双向队列对应的迭代器,该迭代器将以逆向顺序来迭代队列中的元素。
  • Object getFirst():获取、但不删除双向队列的第一个元素。
  • Object getLast(): 获取、但不删除双向队列的最后一个元素。
  • boolean offerFirst(Object e): 将指定的元素插入该双向队列的开头。
  • boolean offerLast(Object e): 将指定的元素插入该双向队列的末尾。
  • Object peekFirst(): 获取、但不删除该双向队列的第一个元素:如果此双端队列为空,则返回null。
  • Object peekLast():获取、但不删除该双向队列的最后一个元素:如果此双端队列为空,则返回null。
  • Object pollFirst():获取、并删除该双向队列的第一个元素:如果此双端队列为空,则返回null。
  • Object pollLast():获取、并删除该双向队列的最后一个元素:如果此双端队列为空,则返回null。
  • Object pop():pop出该双向队列所表示的栈中第一个元素。
  • void push(Object e):将一个元素push进该双向队列所表示的栈中(即该双向队列的头部)。
  • Object removerFirst():获取、并删除该双向队列的最后一个元素。
  • Object removeFirstOccurrence(Object o):删除该双向队列的第一次的出现元素o。
  • Object removeLast():获取、并删除该双向队列的最后一个元素。
  • Object removeLastOccurrence(Object o):删除该双向队列的最后一次出现的元素o。

从上面方法中可以看出,LinkedList不仅可以当成双向队列使用,也可以当成“栈”使用。同时,LinkedList实现了List接口,所以还被当成List使用。


 

要点9:MAP接口的使用

Map用于保存具有映射关系的数据(key-vlaue)。Map的key不允许重复,即同一个Map对象的任何两个key通过equals方法比较总是返回false

put(Object key, Object value):添  加一个key-value对,如果当前Map中已有一个与该key相等的key-value对,则新的key-value对会覆盖原来的key-value对。

Map中包含了一个keySet()方法,用于返回Map所以key组成的Set集合。

 Map中包括一个内部类:Entry。该类封装了一个key-value对,Entry包含三个方法:

  • Object getkey():返回该Entry里包含的key值。
  • Object getValue():返回该Entry里包含的value值。
  • Object setValue():设置该Entry里包含的value值,并返回新设置的value值。

    可以把Map理解成一个特殊的Set,只是该Set里包含的集合元素是Entry对象,而不是普通对象。


 

 

要点10:HashMap和Hashtable实现类

  • Hashtable是一个线程安全的Map实现,但HashMap是线程不安全的实现,所以HashMap比Hashtable的性能高些;但如果多线程访问同一个Map对象,使用Hashtable实现类更好。
  • Hashtable不允许使用null作为key和value,如果为null,则引发NullPointerException异常;但HashMap可以使用null作为key或value。

  由于HashMap里的可以不能重复,所以HashMap里最多只有一对key-value值为null,但可以有无数多项key-value对的value为null。

  HashMap、Hashtable判断两个key相等的标准是:两个key通过equasl方法比较返回ture,两个key的hashCode值相等。

HashMap 子类 --LinkedHashMap类

  HashMap有一个子类:LinkedHashMap,它也是双向链表来维护key-value对的次序,该链表定义了迭代顺序,该迭代顺序与key-value对的插入顺序保持一致。

LinkedHashMap可以避免对HashMap、Hashtable里的key-value对进行排序(只要插入key-value对时保持顺序即可)。

LinkedHashMap需要维护元素的插入顺序,因此性能略低于HashMap的性能,但在迭代访问Map里的全部元素时将有很好的性能,因为它以链表来维护内部顺序。

HashTble 子类--Properties类

  Properties类是Hashtable类的子类,用于处理属性文件(例如Windows操作平台上的ini文件)。Properties类可以把Map对象和属性文件关联起来,从而可以把Map对象中的key-value对写入属性文件,也可以把属性文件中的属性名=属性值加载到Map对象中。由于属性文件里的属性名、属性值只能是字符串类型,所以Properties里的key、value都是字符串类型该类提供了如下三个方法来修改Properties里的key、value值。

  1. String getProperty(String key):获取Properties中指定属性名对应的属性值,类似于Map的get(Object key)方法。
  2. String getProperty(String key, String defaultValue):该方法与前一个方法基本类似。该方法多一个功能,如果Properties中不存在指定key时,该方法返回默认值。
  3. Object setProperty(String key、String value):设置属性值,类似Hashtable的put方法。

提供两个读、写属性文件的方法:

  1. void load(InputStream inStream):从属性文件(以输入流表示)中加载属性名=属性值,把加载到的属性名=属性值对追加到Properties里(由于Properties是Hashtable)的子类,它不保证key-value对之间的次序)。
  2. void Store(OutputStream out, String comment):将Properties中的key-valu对写入指定属性文件(以输出流表示)。

HashMap和Hashtable 区别

  1、 继承和实现区别

  Hashtable是基于陈旧的Dictionary类,完成了Map接口;HashMap是Java 1.2引进的Map接口的一个实现(HashMap继承于AbstractMap,AbstractMap完成了Map接口)。

  2、 线程安全不同

  HashTable的方法是同步的,HashMap是未同步,所以在多线程场合要手动同步HashMap。

HashMap 线程安全问题:

http://www.iteye.com/topic/656670

  3、 对null的处理不同

  HashTable不允许null值(key和value都不可以),HashMap允许null值(key和value都可以)。即 HashTable不允许null值其实在编译期不会有任何的不一样,会照样执行,只是在运行期的时候Hashtable中设置的话回出现空指针异常。 HashMap允许null值是指可以有一个或多个键所对应的值为null。当get()方法返回null值时,即可以表示 HashMap中没有该键,也可以表示该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键,而应该用containsKey()方法来判断。

  4、 方法不同

  HashTable有一个contains(Object value),功能和containsValue(Object value)功能一样。

  5、HashTable使用Enumeration,HashMap使用Iterator。

  6、HashTable中hash数组默认大小是11,增加的方式是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。

  7、哈希值的使用不同,HashTable直接使用对象的hashCode,代码是这样的:

  int hash = key.hashCode();

  int index = (hash & 0x7FFFFFFF) % tab.length;

  而HashMap重新计算hash值,而且用与代替求模:

  int hash = hash(k);

  int i = indexFor(hash, table.length);

  static int hash(Object x) {

  int h = x.hashCode();

  h += ~(h << 9);

  h ^= (h >>> 14);

  h += (h << 4);

  h ^= (h >>> 10);

  return h;

  }

  static int indexFor(int h, int length) {

  return h & (length-1);

  }


 

 

要点11:SortedMap接口和TreeMap实现类

Map接口派生了一个SortedMap子接口,TreeMap为其实现类。类似TreeSet排序,TreeMap也是基于红黑树对TreeMap中所有key进行排序,从而保证TreeMap中所有key-value对处于有序状态。TreeMap两种排序方法:

  1. 自然排序:TreeMap的所有key必须实现Comparable接口,而且所有key应该是同一个类的对象,否则将会抛出ClassCastExcepiton异常。
  2. 定制排序:创建TreeMap时,传入一个Comparator对象,该对象负责对TreeMap中所有key进行排序。采用定制排序时不要求Map的key实现Comparable接口。

  TreeMap中判断两个key相等的标准也是两个key通过equals比较返回true,而通过compareTo方法返回0,TreeMap即认为这两个key是相等的。

  如果使用自定义的类作为TreeMap的key,应重新该类的equals方法和compareTo方法时应有一致的返回结果:即两个key通过equals方法比较返回true时,它们通过compareTo方法比较应该返回0。如果equals方法与compareTo方法的返回结果不一致,要么该TreeMap与Map接口的规则有出入(当equals比较返回true,但CompareTo比较不返回0时),要么TreeMap处理起来性能有所下降(当compareTo比较返回0,当equals比较不返回true时)

 
 

 

要点12: HashSet和HashMap关系

HashSet的实现只是封装了一个HashMap对象来存储所有的集合元素.
 
当在HashMap中添加key-value对,由其key的hashCode()返回值决定改key-value对的存储位置,当key-value对的key的hashCode()返回值相同时,key-value对的value将由key通过eqauls()比较值决定是采用覆盖行为(返回true)(key不做改变),还是产生key-value链(返回false)。
Java代码 
import java.util.*;  
class Name  
{  
    private String first;  
    private String last;  
    public Name(String first, String last)   
    {  
        this.first = first;  
        this.last = last;  
    }  
    //根据first判断两个Name是否相等  
    public boolean equals(Object o)   
    {  
        if (this == o)  
        {  
            return true;  
        }  
        if (o.getClass() == Name.class)  
        {  
            Name n = (Name)o;  
            return n.first.equals(first);  
        }  
        return false;  
    }  
    //根据first计算Name对象的hashCode()返回值  
    public int hashCode()  
    {  
        return first.hashCode();  
    }  
    public String toString()  
    {  
        return "Name[first=" + first + ", last=" + last + "]";  
    }  
}  
public class HashSetTest2  
{  
    public static void main(String[] args)   
    {  
        HashMap<Name,String> set = new         HashMap<Name,String>();   
        set.put(new Name("abc" , "123"),"1111111");  
        set.put(new Name("abc" , "456"),"2222222");  
        System.out.println(set);  
    }  
}  
 输出:
{Name[first=abc, last=123]=2222222}
即key没变,value变了。
 
HashSet的实现只是封装了一个HashMap对象来存储所有的集合元素。所有放入HashSet中的集合元素实际上由HashMap的key来保存,而HashMap的value则存储了一个PRESENT,它是一个静态的Object对象。
由于HashSet的add()方法添加集合元素时实际上转变为调用HashMap的put()方法添加key-value对,当新放入HashMap的key-value对中key与集合中原有key-value对中的key相同(hashCode()返回值相等,通过eqauls()比较值也相等)时,新添加的key-value对的value将覆盖原来key-value对的value,但key不会有任何改变。因此,如果向HashSet中添加一个已经存在的元素,新添加的集合元素(底层由HashMap的key保存)不会覆盖已有的集合元素。即如果向HashSet中添加重复元素,输出的仍然是之前的元素.
把上面代码main方法中改为:// 看原文, 不完整
HashSet<Name> set = new HashSet<Name>();
set.add(new Name("abc" , "123"));
set.add(new Name("abc" , "456"));
System.out.println(set);
则输出:
[Name[first=abc, last=123]]
即key没变
 
 

要点12: java泛型的使用

集合中存储的数据必须是对象,不能存基本数据类型,所以引入泛型,可以具体说明存的对象类型具体是什么?

泛型(Generic type 或者 generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类。可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数是运行时传递的值的占位符一样。

可以在集合框架(Collection framework)中看到泛型的动机。例如,Map 类允许您向一个 Map 添加任意类的对象,即使最常见的情况是在给定映射(map)中保存某个特定类型(比如 String)的对象。

因为 Map.get() 被定义为返回 Object,所以一般必须将 Map.get() 的结果强制类型转换为期望的类型,如下面的代码所示:

Map m = new HashMap();
m.put("key", "blarg");
String s = (String) m.get("key");

要让程序通过编译,必须将 get() 的结果强制类型转换为 String,并且希望结果真的是一个 String。但是有可能某人已经在该映射中保存了不是 String 的东西,这样的话,上面的代码将会抛出 ClassCastException。

理想情况下,您可能会得出这样一个观点,即 m 是一个 Map,它将 String 键映射到 String 值。这可以让您消除代码中的强制类型转换,同时获得一个附加的类型检查层,该检查层可以防止有人将错误类型的键或值保存在集合中。这就是泛型所做的工作。

泛型的好处

Java 语言中引入泛型是一个较大的功能增强。不仅语言、类型系统和编译器有了较大的变化,以支持泛型,而且类库也进行了大翻修,所以许多重要的类,比如集合框架,都已经成为泛型化的了。这带来了很多好处:

类型安全。 泛型的主要目标是提高 Java 程序的类型安全。通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的程度上验证类型假设。没有泛型,这些假设就只存在于程序员的头脑中(或者如果幸运的话,还存在于代码注释中)。

Java 程序中的一种流行技术是定义这样的集合,即它的元素或键是公共类型的,比如“String 列表”或者“String 到 String 的映射”。通过在变量声明中捕获这一附加的类型信息,泛型允许编译器实施这些附加的类型约束。类型错误现在就可以在编译时被捕获了,而不是在运行时当作 ClassCastException 展示出来。将类型检查从运行时挪到编译时有助于您更容易找到错误,并可提高程序的可靠性。

消除强制类型转换。 泛型的一个附带好处是,消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。

尽管减少强制类型转换可以降低使用泛型类的代码的罗嗦程度,但是声明泛型变量会带来相应的罗嗦。比较下面两个代码例子。

该代码不使用泛型

List li = new ArrayList();
li.put(new Integer(3));
Integer i = (Integer) li.get(0);


该代码使用泛型

List<Integer> li = new ArrayList<Integer>();
li.put(new Integer(3));
Integer i = li.get(0);

泛型类:

public class Test<T> {
 private T name;

 public T getName() {
  return name;
 }

 public void setName(T name) {
  this.name = name;
 }

 public static void main(String[] args) {
  Test<String> stringTest = new Test<String>();
  stringTest.setName("aaa");
  System.out.println(stringTest.getName());
  Test<Integer> integerTest = new Test<Integer>();
  integerTest.setName(1111);
  System.out.println(integerTest.getName());
 }
}

要点13: ArrayList , HashMap , HashSet 源代码及详解

http://zhangshixi.iteye.com/blog/674856

1. ArrayList概述:

   ArrayList是List接口的可变数组的实现。实现了所有可选列表操作,并允许包括 null 在内的所有元素。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。
   每个ArrayList实例都有一个容量,该容量是指用来存储列表元素的数组的大小。它总是至少等于列表的大小。随着向ArrayList中不断添加元素,其容量也自动增长。自动增长会带来数据向新数组的重新拷贝,因此,如果可预知数据量的多少,可在构造ArrayList时指定其容量。在添加大量元素前,应用程序也可以使用ensureCapacity操作来增加ArrayList实例的容量,这可以减少递增式再分配的数量。 
   注意,此实现不是同步的。如果多个线程同时访问一个ArrayList实例,而其中至少一个线程从结构上修改了列表,那么它必须保持外部同步。

2. ArrayList的实现:

   对于ArrayList而言,它实现List接口、底层使用数组保存所有元素。其操作基本上是对数组的操作。


 HashpMap 原理

参考:http://www.importnew.com/7099.html

  • hashing的概念
  • HashMap中解决碰撞的方法
  • equals()和hashCode()的应用,以及它们在HashMap中的重要性
  • 不可变对象的好处
  • HashMap多线程的条件竞争
  • 重新调整HashMap的大小

1.    HashMap概述:

   HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。

2.    HashMap的数据结构:

   在java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。

hashmap

源码如下:

Java代码  
/** 
 * The table, resized as necessary. Length MUST Always be a power of two. 
 */  
transient Entry[] table;  
  
static class Entry<K,V> implements Map.Entry<K,V> {  
    final K key;  
    V value;  
    Entry<K,V> next;  
    final int hash;  
    ……  
}  

   可以看出,Entry就是数组中的元素,每个 Map.Entry 其实就是一个key-value对,它持有一个指向下一个元素的引用,这就构成了链表。

3.    HashMap的存取实现:

   1) 存储:

Java代码  
public V put(K key, V value) {  
    // HashMap允许存放null键和null值。  
    // 当key为null时,调用putForNullKey方法,将value放置在数组第一个位置。  
    if (key == null)  
        return putForNullKey(value);  
    // 根据key的keyCode重新计算hash值。  
    int hash = hash(key.hashCode());  
    // 搜索指定hash值在对应table中的索引。  
    int i = indexFor(hash, table.length);  
    // 如果 i 索引处的 Entry 不为 null,通过循环不断遍历 e 元素的下一个元素。  
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {  
        Object k;  
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  
            V oldValue = e.value;  
            e.value = value;  
            e.recordAccess(this);  
            return oldValue;  
        }  
    }  
    // 如果i索引处的Entry为null,表明此处还没有Entry。  
    modCount++;  
    // 将key、value添加到i索引处。  
    addEntry(hash, key, value, i);  
    return null;  
}  

   从上面的源代码中可以看出:当我们往HashMap中put元素的时候,先根据key的hashCode重新计算hash值,根据hash值得到这个元素在数组中的位置(即下标),如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。

   addEntry(hash, key, value, i)方法根据计算出的hash值,将key-value对放在数组table的i索引处。addEntry 是HashMap 提供的一个包访问权限的方法,代码如下:

Java代码  
void addEntry(int hash, K key, V value, int bucketIndex) {  
    // 获取指定 bucketIndex 索引处的 Entry   
    Entry<K,V> e = table[bucketIndex];  
    // 将新创建的 Entry 放入 bucketIndex 索引处,并让新的 Entry 指向原来的 Entry  
    table[bucketIndex] = new Entry<K,V>(hash, key, value, e);  
    // 如果 Map 中的 key-value 对的数量超过了极限  
    if (size++ >= threshold)  
    // 把 table 对象的长度扩充到原来的2倍。  
        resize(2 * table.length);  
}  
 

   当系统决定存储HashMap中的key-value对时,完全没有考虑Entry中的value,仅仅只是根据key来计算并决定每个Entry的存储位置。我们完全可以把 Map 集合中的 value 当成 key 的附属,当系统决定了 key 的存储位置之后,value 随之保存在那里即可。

另一种解释:http://jingyan.baidu.com/article/90895e0f92777e64ec6b0b92.html

(PUT)

现在我们一步一步来分析上面的代码。

对key做null检查。如果key是null,会被存储到table[0],因为null的hash值总是0。

key的hashcode()方法会被调用,然后计算hash值。hash值用来找到存储Entry对象的数组的索引。有时候hash函数可能写的很不好,所以JDK的设计者添加了另一个叫做hash()的方法,它接收刚才计算的hash值作为参数。如果你想了解更多关于hash()函数的东西,可以参考:hashmap中的hash和indexFor方法

indexFor(hash,table.length)用来计算在table数组中存储Entry对象的精确的索引。

在我们的例子中已经看到,如果两个key有相同的hash值(也叫冲突),他们会以链表的形式来存储。所以,这里我们就迭代链表。

· 如果在刚才计算出来的索引位置没有元素,直接把Entry对象放在那个索引上。

· 如果索引上有元素,然后会进行迭代,一直到Entry->next是null。当前的Entry对象变成链表的下一个节点。

· 如果我们再次放入同样的key会怎样呢?逻辑上,它应该替换老的value。事实上,它确实是这么做的。在迭代的过程中,会调用equals()方法来检查key的相等性(key.equals(k)),如果这个方法返回true,它就会用当前Entry的value来替换之前的value

(GET)

当你理解了hashmap的put的工作原理,理解get的工作原理就非常简单了。当你传递一个key从hashmap总获取value的时候:

对key进行null检查。如果key是null,table[0]这个位置的元素将被返回。

key的hashcode()方法被调用,然后计算hash值。

indexFor(hash,table.length)用来计算要获取的Entry对象在table数组中的精确的位置,使用刚才计算的hash值。

在获取了table数组的索引之后,会迭代链表,调用equals()方法检查key的相等性,如果equals()方法返回true,get方法返回Entry对象的value,否则,返回null。

要牢记以下关键点:

· HashMap有一个叫做Entry的内部类,它用来存储key-value对。

· 上面的Entry对象是存储在一个叫做table的Entry数组中。

· table的索引在逻辑上叫做“桶”(bucket),它存储了链表的第一个元素。

· key的hashcode()方法用来找到Entry对象所在的桶。

· 如果两个key有相同的hash值,他们会被放在table数组的同一个桶里面。

· key的equals()方法用来确保key的唯一性。

· value对象的equals()和hashcode()方法根本一点用也没有。


1.    HashSet概述:

   HashSet实现Set接口,由哈希表(实际上是一个HashMap实例)支持。它不保证set 的迭代顺序;特别是它不保证该顺序恒久不变。此类允许使用null元素。

2.    HashSet的实现:

   对于HashSet而言,它是基于HashMap实现的,HashSet底层使用HashMap来保存所有元素,因此HashSet 的实现比较简单,相关HashSet的操作,基本上都是直接调用底层HashMap的相关方法来完成,

未完 : 集合框架的底层实现, 与array的关系。 效率比较, 实战使用情况。

原文地址:https://www.cnblogs.com/alexlo/p/2944831.html