• 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的关系。 效率比较, 实战使用情况。

  • 相关阅读:
    php随笔3-thinkphp 学习-ThinkPHP3.1快速入门(1)基础
    自己动手写PHP MVC框架
    Aptana Studio 3 官方汉化包汉化
    PHP使用手册
    关系型数据库:关系模式设计原则
    Git使用教程
    高并发与负载均衡-nginx-session一致性
    高并发与负载均衡-nginx-安装-配置-location-负载均衡
    高并发与负载均衡-nginx-反向代理概念
    Java-笔记2
  • 原文地址:https://www.cnblogs.com/alexlo/p/2944831.html
Copyright © 2020-2023  润新知