java Map集合深入解析

java集合类梳理
要理解Java技术强大特性就有必要掌握集合框架

1.集合类的继承层次结构 简介对比 总结如下:

Collection<--List<--Vector
Collection<--List<--ArrayList
Collection<--List<--LinkedList
Collection<--Set<--HashSet
Collection<--Set<--HashSet<--LinkedHashSet
Collection<--Set<--SortedSet<--TreeSet

继承自Collection的集合除了Set和List还有Queue队列。

  Vector和ArrayList都是基于Array数组的实现,不可能走出Array的限制,所以性能上也不会超过Array。Vector和ArrayList唯一的区别就是Vector是线程安全的,所以在性能上ArrayList比较优越一些。LinkedList与Vector和ArrayList不同,因为它不是基于数组实现的,所以不受Array性能的限制。它的每一个节点包含:(1)节点本身的数据data(2)下一个节点的信息nextNode。所以当对LinkedList做添加或者删除操作的时候,只需要更改nextNode的相关信息就可以实现,和上面两种基于Array的List不一样在进行插入和删除操作时候必须进行大量的数据移动。列表呢主要是以线性方式存储,所以没有特定的元素顺序,只有一个开头一个结尾。
  这里还要说一下两个基于数组实现的列表Vector和ArrayList,【数据增长方面】当存储的元素数量超过列表定义的数量时,Vector扩展增加一倍容量,而ArrayList增加50%;【同步性方面】Vector是同步的,其中的对象是安全的,则性能相对较低;【使用模式】在ArrayList和Vector中,从一个指定的位置通过索引查找数据或者在集合的末尾增加、移除一个元素所花费的时间是一样的O(1),但是如果是在其他的位置增加或者移除元素则涉及到第i和第i个元素之后的元素都要执行位移的操作。而LinkedList不同,在集合中任意一个位置增加或者移除元素的时间都是O(1),但是索引就比较慢 为O(i)。
  set和list的根本区别是:set的实现是基于HashMap的,而List是以Array为基础的。
  HashSet:因为它的存储方式时把HashMap中的Kry作为Set的对应存储项,因为HashMap中的Key是不允许重复的,所以Set中不允许有重复值存在。如果后期存入了一个重复的值,则将会覆盖之前对象。LinkedHashSet作为HashSet的一个子类,是一个链表;TreeSet作为SortedSet的子类,不同于HashSet的就是:它是通过SortedMap实现的,是有序的。HashMap中允许有一个键的值为null,非同步(和HashTable很类似)。(HashTable中所有的key和value都不能为空,是同步的,现在很少用到。)

2.介绍一下Set、Map和List三个接口

(1)Set:Set中的元素无序且不能重复。Set有两个实现类:HashSet和TreeSet,其中TreeSet实现了SortedSet接口,因此TreeSet中的元素是有序的;
(2)List: List是有序可重复的集合,按照元素存入的顺序存放对象,所以它能够对每个元素的插入和删除位置进行精确的控制。按序存储的元素List允许重复。LinkedList、ArrayList和Vector都实现了List接口。
(3)Map:Map是一种从键映射到值的结构,存储键值对,其中key都是唯一的不允许重复,而值可以重复。实现Map的子类有很多:HashMap、TreeMap、LinkedMap等。虽然实现了相同的接口,但是执行效率并不完全相同。HashMap基于散列表实现,采用对象的hashCode进行快速查询;LinkedMap采用列表来维护内部顺序;TreeMap通过红黑树的数据结构来实现的,内部元素按需排列。

3.ArrayList、Vector和LinkedList有什么区别?

  ArrayList和LinkedList在用法上没有区别,但是在功能上还是有区别的。LinkedList经常用在增删操作较多而查询操作很少的情况下,ArrayList则相反。
  这三个类都继承自List接口,内部元素可以重复但有序。都在java.util包中,都是可伸缩的数组,也就是说可以动态改变数组的长度。
简述:首先来说ArrayList和Vector:都是基于Object[] 对象数组实现的。它们在创建的时候会申请连续的存储空间来存储数据。所以:1)他们支持使用下标来访问元素,所以索引数据的速度回比较快。2)因为是顺序存储,插入数据的时候需要移动容器中的数据,所以对数据的插入操作效率会比较慢。 ArrayList和Vector都有一个初始化的容量值,当添加进入容易中的数据长度大于容器的长度时,它们会自动动态扩充他们的存储单元。Vector默认扩充为原来的2倍,而ArrayList扩充为原来的1.5倍。
  区别:它俩最大的区别就是同步的使用。ArrayList中的方法都是非同步的,所以ArrayList不是安全的,Vector中的大多数方法都是直接或者间接同步的,所以Vector是线程安全的。相对而言,因为Vector是线程安全,而ArrayList线程不安全,所以ArrayList性能较Vector高。
  LinkedList是采用双向列表实现的,对数据的索引需要从列表头开始遍历,因此用于随机访问则效率比较低。但是因为插入元素是不需要对数据进行移动,因此插入操作的效率较高。同时LinkedList是非线程安全容器。
  List的选择: 1)当进行索引或在集合的末端增加和删除元素时,使用ArrayList和Vector的效率较高;2)当进行特定位置的插入和删除元素的操作时,LinkedList的效率较高;因为LinkedList不需要移动元素。当在多线程中使用容器时,选择Vector比较安全。因为Vector是线程安全。

4.HashMap、Hashtable、TreeMap和WeakHashMap有什么区别?

(1)HashMap和hashtable都使用hash法进行索引,所以它俩具有很多相似之处。
区别:1)HashMap是Hashtable的轻量级实现(线程不安全的实现),他们都完成了Map接口,而HashMap允许一个null值的key,Hashtable不允许。 2)Hashtable的线程是安全的,而HashMap不安全,不支持线程同步。所以如果使用HashMap开发人员必须提供额外的同步机制。
(2)WeakHashMap和HashMap很类似。区别在于WeakHashMap是“弱引用”。也就是说如果WeakHashMap中的某个key,不再被外部引用,它就会被垃圾回收器回收。而HashMap的key采用“强引用的方式”。只有当其中的key被删除后,才可以被垃圾回收器回收。
(3)Map的选择:HashMap中存入的键值对是随机的。 1)当在Map中插入、删除、定位元素,HashMap是最好的选择; 2)因为TreeMap实现了SortMap 接口,能够把它保存的记录按照key排序,因此取出来的值是排序后的键值对,如果需要自然排序或者自定义排序,则使用TreeMap; 3) 如果需要输出数据的顺序和输入相同,那么使用LinkedHashMap(是HashMap的一个子类)。
向HashMap中添加数据:如果key存在,则覆盖key对应的值。

5.Map集合

实现类:HashMap、Hashtable、LinkedHashMap和TreeMap
HashMap
  HashMap是最常用的Map,它根据键的HashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度,遍历时,取得数据的顺序是完全随机的。因为键对象不可以重复,所以HashMap最多只允许一条记录的键为Null,允许多条记录的值为Null,是非同步的
Hashtable
  Hashtable与HashMap类似,是HashMap的线程安全版,它支持线程的同步,即任一时刻只有一个线程能写Hashtable,因此也导致了Hashtale在写入时会比较慢,它继承自Dictionary类,不同的是它不允许记录的键或者值为null,同时效率较低。
ConcurrentHashMap(是怎么实现线程安全的?)
  线程安全,并且锁分离。ConcurrentHashMap内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的hash table,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。
LinkedHashMap
  LinkedHashMap保存了记录的插入顺序,在用Iteraor遍历LinkedHashMap时,先得到的记录肯定是先插入的,在遍历的时候会比HashMap慢,有HashMap的全部特性。
TreeMap
  TreeMap实现SortMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序(自然顺序),也可以指定排序的比较器,当用Iterator遍历TreeMap时,得到的记录是排过序的。不允许key值为空,非同步的;

6.主要实现类区别小结

Vector和ArrayList
  vector是线程同步的,所以它也是线程安全的,而arraylist是线程异步的,是不安全的。如果不考虑到线程的安全因素,一般用arraylist效率比较高。
  如果集合中的元素的数目大于目前集合数组的长度时,vector增长率为目前数组长度的100%,而arraylist增长率为目前数组长度的50%。如果在集合中使用数据量比较大的数据,用vector有一定的优势。
  如果查找一个指定位置的数据,vector和arraylist使用的时间是相同的,如果频繁的访问数据,这个时候使用vector和arraylist都可以。而如果移动一个指定位置会导致后面的元素都发生移动,这个时候就应该考虑到使用linklist,因为它移动一个指定位置的数据时其它元素不移动。
  ArrayList 和Vector是采用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,都允许直接序号索引元素,但是插入数据要涉及到数组元素移动等内存操作,所以索引数据快,插入数据慢,Vector由于使用了synchronized方法(线程安全)所以性能上比ArrayList要差,LinkedList使用双向链表实现存储,按序号索引数据需要进行向前或向后遍历,但是插入数据时只需要记录本项的前后项即可,所以插入数度较快。

arraylist和linkedlist
  ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
  对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。
  对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。 这一点要看实际情况的。若只对单条数据插入或删除,ArrayList的速度反而优于LinkedList。但若是批量随机的插入删除数据,LinkedList的速度大大优于ArrayList. 因为ArrayList每插入一条数据,要移动插入点及之后的所有数据。

HashMap与TreeMap
   HashMap通过hashcode对其内容进行快速查找,而TreeMap中所有的元素都保持着某种固定的顺序,如果你需要得到一个有序的结果你就应该使用TreeMap(HashMap中元素的排列顺序是不固定的)。
  在Map 中插入、删除和定位元素,HashMap是最好的选择。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。使用HashMap要求添加的键类明确定义了hashCode()和 equals()的实现。
两个map中的元素一样,但顺序不一样,导致hashCode()不一样。
同样做测试:
  在HashMap中,同样的值的map,顺序不同,equals时,false;
而在treeMap中,同样的值的map,顺序不同,equals时,true,说明,treeMap在equals()时是整理了顺序了的。

7.面试中常问到的问题

(1)Iterator与ListIterator有什么区别?
Iterator:只能正向遍历集合,适用于获取移除元素。ListIerator:继承Iterator,可以双向列表的遍历,同样支持元素的修改。
(2)什么是HaspMap和Map?
Map是接口,Java 集合框架中一部分,用于存储键值对,HashMap是用哈希算法实现Map的类。
(3)HashMap与HashTable有什么区别?对比Hashtable VS HashMap
两者都是用key-value方式获取数据。Hashtable是原始集合类之一(也称作遗留类)。HashMap作为新集合框架的一部分在Java2的1.2版本中加入。它们之间有一下区别:
  ● HashMap和Hashtable大致是等同的,除了非同步和空值(HashMap允许null值作为key和value,而Hashtable不可以)。
  ● HashMap没法保证映射的顺序一直不变,但是作为HashMap的子类LinkedHashMap,如果想要预知的顺序迭代(默认按照插入顺序),你可以很轻易的置换为HashMap,如果使用Hashtable就没那么容易了。
  ● HashMap不是同步的,而Hashtable是同步的。
  ● 迭代HashMap采用快速失败机制,而Hashtable不是,所以这是设计的考虑点。
(4)、在Hashtable上下文中同步是什么意思?
  同步意味着在一个时间点只能有一个线程可以修改哈希表,任何线程在执行hashtable的更新操作前需要获取对象锁,其他线程等待锁的释放。
(5)怎样使Hashmap同步?
  HashMap可以通过Map m = Collections.synchronizedMap(hashMap)来达到同步的效果。
(6)、什么时候使用Hashtable,什么时候使用HashMap
  基本的不同点是Hashtable同步HashMap不同步,所以无论什么时候有多个线程访问相同实例的可能时,就应该使用Hashtable,反之使用HashMap。非线程安全的数据结构能带来更好的性能。
  如果在将来有一种可能—你需要按顺序获得键值对的方案时,HashMap是一个很好的选择,因为有HashMap的一个子类 LinkedHashMap。所以如果你想可预测的按顺序迭代(默认按插入的顺序),你可以很方便用LinkedHashMap替换HashMap。反观要是使用的Hashtable就没那么简单了。同时如果有多个线程访问HashMap,Collections.synchronizedMap()可以代替,总的来说HashMap更灵活。
(7)、为什么Vector类认为是废弃的或者是非官方地不推荐使用?或者说为什么我们应该一直使用ArrayList而不是Vector
  你应该使用ArrayList而不是Vector是因为默认情况下你是非同步访问的,Vector同步了每个方法,你几乎从不要那样做,通常有想要同步的是整个操作序列。同步单个的操作也不安全(如果你迭代一个Vector,你还是要加锁,以避免其它线程在同一时刻改变集合).而且效率更慢。当然同样有锁的开销即使你不需要,这是个很糟糕的方法在默认情况下同步访问。你可以一直使用Collections.sychronizedList来装饰一个集合。
  事实上Vector结合了“可变数组”的集合和同步每个操作的实现。这是另外一个设计上的缺陷。Vector还有些遗留的方法在枚举和元素获取的方法,这些方法不同于List接口,如果这些方法在代码中程序员更趋向于想用它。尽管枚举速度更快,但是他们不能检查如果集合在迭代的时候修改了,这样将导致问题。尽管以上诸多原因,oracle也从没宣称过要废弃Vector。
(8)HashMap和ConcurrentHashMap的区别

扫描二维码关注公众号,回复: 46144 查看本文章

8.map的遍历方式介绍:

map的遍历有4种,我们依次来看。

第一种 这是最常见的用法,这种用法可以同时拿到key和value值。 缺点:如果map是空,将会出现空指针异常,那么每次在对map遍历以前,就要先进行判空

public static void forEachMap(Map<String,String> map) {         
    for ( Map.Entry<String,String> entry : map.entrySet()) {    
        System.out.println(entry.getKey()+entry.getValue());    
    }                                                           
}    

下来看看第二种遍历方法 这种方法是只遍历key或者value。这种方法比第一种方法的效率略微有提升,而且代码也能简洁一点。同样,这种方法也需要判断map是否为空。

 public static void forEachMap2(Map<String,String> map){     
     for (String str :map.keySet()){                         
         System.out.println(str);                            
     }                                                       
     for (String str :map.values()){                         
         System.out.println(str);                            
     }                                                       
 }     

第三种方法是使用迭代器的方式

  /**                                                                           
   * 使用迭代器                                                                   
   * @param map                                                                 
   */                                                                           
  public static void forEachMap3(Map<String, String> map) {                     
      Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator(); 
      while (iterator.hasNext()) {                                              
          Map.Entry<String, String> entry = iterator.next();                    
          System.out.println(entry.getKey() + entry.getValue());                
      }                                                                         
  }      

  /**                                                                           
   * 使用迭代器但是不适用泛型                                                               
   *                                                                            
   * @param map                                                                 
   */                                                                           
  public static void forEachMap4(Map<String, String> map) {                     
      Iterator iterator = map.entrySet().iterator();                            
      while (iterator.hasNext()){                                               
          Map.Entry entry= (Map.Entry) iterator.next();   
          //这里的类型转换的原因是,如果不加String,那么背默认为两个object,不能相加                      
          System.out.println((String)entry.getKey() + entry.getValue());        
      }                                                                         
  }     

这两种方式没有什么大的区别,只是在泛型上有个区别。
第四种是先拿到map的key值,再拿取value值。 这种方式效率比较低,一般不推荐使用

for (Integer key : map.keySet()) {  
    Integer value = map.get(key);  
    System.out.println("Key = " + key + ", Value = " + value);  
} 

猜你喜欢

转载自blog.csdn.net/Amethyst128/article/details/80046254
今日推荐