Java集合类解析

Java中的集合类包含的内容很多而且很重要,很多数据的存储和处理(排序,去重,筛选等)都需要通过集合类来完成。


首先java中集合类主要有两大分支:

(1)Collection (2)Map

先看它们的类图:

(1)Collection

(2)Map 

可以看到它们之间的关系纷繁复杂,如果不系统的学习一下,还真是不知道有什么区别,该怎么选择。由于HashSet的内部实现原理是使用了HashMap,所以我们的学习路线为先学习Map集合类,然后再来学习Collection集合类。


(1)HashMap和Hashtable ( 注意table是小写的t,搞不懂为什么要这样,老是会写错。。。)

首先来看HashMap和HashTable,这两兄弟经常被放到一起来比较,那么它们有什么不一样呢?

a.HashMap不是线程安全的;HashTable是线程安全的,其线程安全是通过Sychronize实现。

b.由于上述原因,HashMap效率高于HashTable。

c.HashMap的键可以为null,HashTable不可以。

d.多线程环境下,通常也不是用HashTable,因为效率低。HashMap配合Collections工具类使用实现线程安全。同时还有ConcurrentHashMap可以选择,该类的线程安全是通过Lock的方式实现的,所以效率高于Hashtable。

好,比较了他们的不一样后,来讲讲它们的原理。

数组,链表,哈希表。各有优劣,顺便提一下,数组连续内存空间,查找速度快,增删慢;链表充分利用了内存,存储空间是不连续的,首尾存储上下一个节点的信息,所以寻址麻烦,查找速度慢,但是增删快;哈希表呢,综合了它们两个的有点,一个哈希表,由数组和链表组成。假设一条链表有1000个节点,现在查找最后一个节点,就得从第一个遍历到最后一个;如果用哈希表,将这条链表分为10组,用一个容量为10数组来存储这10组链表的头结点(a[0] = 0 , a[1] = 100 , a[2] = 200 …)。这样寻址就快了。

HashMap实现原理就是上述原理了,当然其具体实现还有很多其他的东西。Hashtable同理,只不过做了同步处理。

Hash碰撞,不同的key根据hash算法算出的值可能一样,如果一样就是所谓的碰撞。

优化措施:

(1) HashMap的扩容代价非常大,要生成一个新的桶数组,然后要把所有元素都重新Hash落桶一次,几乎等于重新执行了一次所有元素的put。所以如果我们对Map的大小有一个范围的话,可以在构造时给定大小,一般大小设置为:(int) ((float) expectedSize / 0.75F + 1.0F)。

(2) key的设计尽量简洁。


HashMap一些功能实现:

a.按值排序

HashMap按值排序通过Collections的sort方法,在实现排序之前,我们先看看HashMap的几种遍历方式:

    //Collection And Map
    public static void testCM(){
        //Collection
        Map<Integer , String> hs = new HashMap<Integer , String>();
        int i = 0;
        hs.put(199, "序号:"+201);
        while(i<50){
            hs.put(i, "序号:"+i);
            i++;
        }
        hs.put(-1, "序号:"+200);
        hs.put(200, "序号:"+200);

        //遍历方式一:for each遍历HashMap的entryset,注意这种方式在定义的时候就必须写成
        //Map<Integer , String> hs,不能写成Map hs;
        for(Entry<Integer , String> entry : hs.entrySet()){
            System.out.println("key:"+entry.getKey()+"  value:"+entry.getValue());
        }
        //遍历方式二:使用EntrySet的Iterator
        Iterator<Map.Entry<Integer , String>> iterator = hs.entrySet().iterator();
        while(iterator.hasNext()){
            Entry<Integer , String> entry =  iterator.next();
            System.out.println("key:"+entry.getKey()+"  value:"+entry.getValue());
        };
        //遍历方式三:for each直接使用HashMap的keyset
        for(Integer key : hs.keySet()){
            System.out.println("key:"+key+"  value:"+hs.get(key));
        };
        //遍历方式四:使用keyset的Iterator
        Iterator keyIterator = hs.keySet().iterator();
        while(keyIterator.hasNext()){
            Integer key = (Integer)keyIterator.next();
            System.out.println("key:"+key+"  value:"+hs.get(key));
        }
    }
(1)使用keyset的两种方式都会遍历两次,所以效率没有使用EntrySet高。
(2)HashMap输出是无序的,这个无序不是说每次遍历的结果顺序不一样,而是说与插入顺序不一样。

接下来我们看按值排序,注释比较详细就不赘述过程了。

    //对HashMap排序
    public static void sortHashMap(Map<Integer , String> hashmap){

        System.out.println("排序后");

        //第一步,用HashMap构造一个LinkedList
        Set<Entry<Integer , String>> sets = hashmap.entrySet();
        LinkedList<Entry<Integer , String>> linkedList = new LinkedList<Entry<Integer , String>>(sets);

        //用Collections的sort方法排序
        Collections.sort(linkedList , new Comparator<Entry<Integer , String>>(){

            @Override
            public int compare(Entry<Integer , String> o1, Entry<Integer , String> o2) {
                // TODO Auto-generated method stub
                /*String object1 = (String) o1.getValue();
                String object2 = (String) o2.getValue();
                return object1.compareTo(object2);*/
                return o1.getValue().compareTo(o2.getValue());
            }

        });

        //第三步,将排序后的list赋值给LinkedHashMap
        Map<Integer , String> map = new LinkedHashMap();
        for(Entry<Integer , String> entry : linkedList){
            map.put(entry.getKey(), entry.getValue());
        }
        for(Entry<Integer , String> entry : map.entrySet()){
            System.out.println("key:"+entry.getKey()+"  value:"+entry.getValue());
        }
    }

b.按键排序

HashMap按键排序要比按值排序方法容易实现,而且方法很多,下面一一介绍。

第一种:还是熟悉的配方还是熟悉的味道,用Collections的sort方法,只是更改一下比较规则。

第二种:TreeMap是按键排序的,默认升序,所以可以通过TreeMap来实现。

    public static void sortHashMapByKey(Map hashmap){

        System.out.println("按键排序后");

        //第一步:先创建一个TreeMap实例,构造函数传入一个Comparator对象。
        TreeMap<Integer , String> treemap = new TreeMap<Integer , String>(new Comparator<Integer>(){

            @Override
            public int compare(Integer o1,Integer o2) {
                // TODO Auto-generated method stub
                return Integer.compare(o1, o2);
            }

        });
        //第二步:将要排序的HashMap添加到我们构造的TreeMap中。
        treemap.putAll(hashmap);
        for(Entry<Integer , String> entry : treemap.entrySet()){
            System.out.println("key:"+entry.getKey()+"  value:"+entry.getValue());
        }
    }

第三种:可以通过keyset取出所有的key,然后将key排序,再有序的将key-value键值对存到LinkedHashMap中,这个就不贴代码了,有兴趣的可以自己去尝试一下。

c.value去重

对于HashMap而言,它的key是不能重复的,但是它的value是可以重复的,有的时候我们要将重复的部分剔除掉。

方法一:将HashMap的key-value对调,然后赋值给一个新的HashMap,由于key的不可重复性,此时就将重复值去掉了。最后将新得到的HashMap的key-value再对调一次即可。

d.HashMap线程同步

第一种:

Map<Integer , String> hs = new HashMap<Integer , String>();
        hs = Collections.synchronizedMap(hs);

第二种:

ConcurrentHashMap<Integer , String> hs = new ConcurrentHashMap<Integer , String>();

(2)IdentifyHashMap

IdentityHashMap与HashMap基本相似,只是当两个key严格相等时,即key1==key2时,它才认为两个key是相等的 。IdentityHashMap也允许使用null,但不保证键值对之间的顺序。


(3)WeakHashMap

WeakHashMap与HashMap的用法基本相同,区别在于:后者的key保留对象的强引用,即只要HashMap对象不被销毁,其对象所有key所引用的对象不会被垃圾回收,HashMap也不会自动删除这些key所对应的键值对对象。但WeakHashMap的key所引用的对象没有被其他强引用变量所引用,则这些key所引用的对象可能被回收。WeakHashMap中的每个key对象保存了实际对象的弱引用,当回收了该key所对应的实际对象后,WeakHashMap会自动删除该key所对应的键值对。



接下来是Collection接口及其子类:

(4)ArrayList , LinkedList , Vector

(1)首先,说说它们的关系和区别。ArrayList和Vector本质都是用数组实现的,而LinkList是用双链表实现的;所以,Arraylist和Vector在查找效率上比较高,增删效率比较低;LinkedList则正好相反。ArrayList是线程不安全的,Vector是线程安全的,效率肯定没有ArrayList高了。实际中一般也不怎么用Vector,可以自己做线程同步,也可以用Collections配合ArrayList实现线程同步。

(2)Tips

前面多次提到扩容的代价很高,所以如果能确定容量的大致范围就可以在创建实例的时候指定,注意,这个仅限于ArrayList和Vector哟:

ArrayList arrayList = new ArrayList(100);
arrayList.ensureCapacity(200);
Vector vector = new Vector(100);
vector.ensureCapacity(200);

(3)其他功能实现

a.排序

List的排序的话就是使用Collections的sort方法,构造Comparator或者让List中的对象实现Comparaable都可以,这里就不贴代码了。

b.去重

第一种:用Iterator遍历,遍历出来的放到一个临时List中,放之前用contains判断一下。

第二种:利用set的不可重复性,只需三步走。

//第一步:用HashSet的特性去重
    HashSet tempSet = new HashSet(arrayList);
    //第二步:将arrayList清除
    tempSet.clear();
    //第三步:将去重后的重新赋给List
    arrayList.addAll(tempSet);

(5)Stack

Stack呢,是继承自Vector的,所以用法啊,线程安全什么的跟Vector都差不多,只是有几个地方需要注意:

第一:add()和push(),stack是将最后一个element作为栈顶的,所以这两个方法对stack而言是没什么区别的,但是,它们的返回值不一样,add()返回boolean,就是添加成功了没有;push()返回的是你添加的元素。为了可读性以及将它跟栈有一丢丢联系,推荐使用push。

第二:peek()和pop(),这两个方法都能得到栈顶元素,区别是peek()只是读取,对原栈没有什么影响;pop(),从字面上就能理解,出栈,所以原栈的栈顶元素就没了。


(6)HashSet和TreeSet

Set集合类的特点就是可以去重,它们的内部实现都是基于Map的,用的是Map的key,所以知道为什么可以去重复了吧。 
既然要去重,那么久需要比较,既然要比较,那么久需要了解怎么比较的,不然它将1等于2了,你怎么办?

比较是基于hascode()方法和equals()方法的,所以必要情况下需要重新这两个方法。


好了,到了总结的时候了,其实你会发现集合类虽然看起来多,但是都是很有规律的。ArrayList,LinkedList一个无序,一个有序;HashSet,TreeSet一个无序,一个有序;HashMap,LinkedHasmMap,一个无序,一个有序;Vector和HashTable,Stack是线程安全的,但是效率低;线程不安全的类都可以配合Collections得到线程安全的类。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-18 21:53:13

Java集合类解析的相关文章

Java集合类源码解析:AbstractList

今天学习Java集合类中的一个抽象类,AbstractList. 初识AbstractList AbstractList 是一个抽象类,实现了List<E>接口,是隶属于Java集合框架中的 根接口 Collection 的分支,由其衍生的很多子类因为拥有强大的容器性能而被广泛应用,例如我们最为熟悉的ArrayList,这是它的类继承结构图: 特殊方法 AbstractList 虽然是抽象类,但其内部只有一个抽象方法 get(): abstract public E get(int index

Java集合类源码解析:AbstractMap

目录 引言 源码解析 抽象函数entrySet() 两个集合视图 操作方法 两个子类 参考: 引言 今天学习一个Java集合的一个抽象类 AbstractMap ,AbstractMap 是Map接口的 实现类之一,也是HashMap.TreeMap.ConcurrentHashMap 等的父类,它提供了Map 接口中方法的基本实现(关于Map接口有疑惑的同学可参考 Java集合类根接口:Collection 和 Map) 源码解析 因为 AbstractMap 类是实现Map接口的抽象类,所以

Java泛型解析(01):认识泛型

What Java从1.0版本到现在的8,中间Java5中发生了一个很重要的变化,那就是泛型机制的引入.Java5引入了泛型,主要还是为了满足在1999年指定的最早Java规范之一.经过了5年左右的时间,专家组定义了一套泛型规范,实现后通过测试投入到使用.所以说泛型是Java5以后才有的,欲知详情,继续往下看. Why      换个角度想,Java5引入泛型,必定是它能带来好处,否则牛气的Java专家工程师就要遭到吐槽了.我们来吐槽一下没有泛型的程序是怎么写的. [code01] ArrayL

java 集合类源码分析--linkedlist

一.源码解析 1. LinkedList类定义2.LinkedList数据结构原理3.私有属性4.构造方法5.元素添加add()及原理6.删除数据remove()7.数据获取get()8.数据复制clone()与toArray()9.遍历数据:Iterator()二.ListItr 一.源码解析 1. LinkedList类定义. public class LinkedList<E> extends AbstractSequentialList<E> implements List

Java集合类之List,Set探究

Collection是一个顶层的接口,在集合类中还有Map也属于顶层接口(参考Java集合类之Map探究),本文通过一张类图描述List和Set的基本结构. 类图解析: Collection是一个顶层的接口,定义了一系列方法,List和Set是继承自Collection的接口. 每个Collection都可以产生一个Iterator(参考Java iterator与iterator模式),从而可以利用Iterator进行遍历. AbstractCollection是Collection的抽象实现

Java集合类: Set、List、Map、Queue使用

目录 1. Java集合类基本概念 2. Java集合类架构层次关系 3. Java集合类的应用场景代码 1. Java集合类基本概念 在编程中,常常需要集中存放多个数据.从传统意义上讲,数组是我们的一个很好的选择,前提是我们事先已经明确知道我们将要保存的对象的数量.一旦在数组初始化时指定了这个数组长度,这个数组长度就是不可变的,如果我们需要保存一个可以动态增长的数据(在编译时无法确定具体的数量),java的集合类就是一个很好的设计方案了. 集合类主要负责保存.盛装其他数据,因此集合类也被称为容

Java 集合类详解

1.java集合类图 1.1 1.2 上述类图中,实线边框的是实现类,比如ArrayList,LinkedList,HashMap等,折线边框的是抽象类,比如AbstractCollection,AbstractList,AbstractMap等,而点线边框的是接口,比如Collection,Iterator,List等. 发现一个特点,上述所有的集合类,都实现了Iterator接口,这是一个用于遍历集合中元素的接口,主要包含hashNext(),next(),remove()三种方法.它的一个

摘抄转载前辈们的Java集合类总结

JAVA 集合类介绍和使用    类关系示意图  Iterable(接口)      │      └--Collection (接口)          ├-List(接口)          │ ├-LinkedList  不同步  (构造同步:List list = Collections.synchronizedList(new LinkedList(...));)          │ ├-ArrayList   不同步           │ └-Vector      同步    

针对Java集合类的小总结

Java集合类包位于java.util下,有很多常用的数据结构:数组.链表.队列.栈.哈希表等等.了解不同的集合类的特性在开发过程中是比较重要的,感谢@兰亭风雨的专栏分析,这里我也根据自己的理解做轻度的总结,不再进行分析,因为上面的博客分析已经相当优秀. List接口实现,一般常用的为ArrayList和LinkedList,还有不太常用的Vector,Stack.比如JDK文档声明 Deque 接口及其实现提供了 LIFO 堆栈操作的更完整和更一致的 set,应该优先使用此 set,而非Sta