Java集合实现细节

来源:互联网 发布:oracle查询当天数据 编辑:程序博客网 时间:2024/05/22 01:48

Map和Set的关系:Map集合的所有key具有Set集合的特征,只要把Map的所以key集合起来,就是一个Set,实现了从Map到Set的转换;当Set的每个元素都是key-value组(Map.Entry<K,V>),则可以扩展为Map。


HashMap和HashSet:

    对于HashSet,系统采用Hash算法决定集合元素的存储位置,可以保证快速存取集合云素;对于HashMap,系统将value当成key的附属,系统根据Hash算法决定key的存储位置,可以保证快速存储集合key,而value总是紧随key存储。

    集合虽然号称存储Java对象,但实际保留的只是对象的引用,这些引用变量指向实际的Java对象。

    当系统决定存储HashMap中的key-value对时,没有考虑Entry中的value,仅仅只根据key来计算决定Entry的存储位置,完全把value当作key的附属,系统决定key存储时,value也随之保存。

    当程序试图将key-value放入HashMap,会根据key的hashCode返回值决定,如果两个Entry的hashCode返回值相同,那么存储位置相同,如果Entry的key的equal比较,如果true,新添加的将覆盖原来的Entry,如果false,将新添加的与集合中原有的Entry形成Entry链,而且,新添加的位于Entry链的头部。

    创建HashMap():

        HashMap():创建初始容量16,负载因子为0.75的HashMap

        HashMap(int initalCapacity):构建初始容量为initalCapacity,负载因子为0.75的HashMap

        HashMap(int initicalCapacity, float loadFactor):以执行初始化容量和负载因子创建HashMap。

        创建HashMap的实际容量为大于给定容量的、最小的2的n次方值(例如,给中initalCopacity为10,实际容量为16),所以通常指定的initialCapacity并不等于HashMap的实际长度,除非指定的初始容量为2的n次方.这样可以减少系统的计算开销。

        简单归纳,HashMap底层将key-value当成整体对象Entry处理,采用Entry[]数组保存所以key-value对,根据Hash算法决定其存储位置与找到位置直接取出。负载因子是时间和空间的折衷,增大减少占用内存但增加查询时间/减少提供查询性能但增加占用内存空间。

    创建HashSet:

        底层采用HashMap保存所以元素,只是封装了一个HashMap保存所以的集合元素,HashSet中的集合元素实际由HashMap的key来保存,HashMap的value则存储了一个final的Object(PRESENT),一个静态的Object对象。如果向HashSet中添加一个已存在的元素(底层由HashMap的key保存,不会改变),新添加元素不会覆盖已有元素。

    当试图把某个类的对象当成HashMap的key,或者试图放入HashSet中保存,重写该类的equal方法和hashCode方法很重要,而且必须保持一致,当equal返回true时,hashCode必须返回相同值。


TreeMap和TreeSet:

    TreeSet与HashSet类似,底层采用NavigableMap保存TreeSet集合元素,但实际上NavigableMap是一个接口,所以底层是使用TreeMap包含Set的所有元素。TreeSet的大部分方法也是调用TreeMap的方法实现。

    对于TreeMap,采用了一种“红黑树”的排序二叉树保存Map中的每个Entry,每个Entry被当成“红黑树”接一个节点对待。意味着TreeMap的存取效率都比HashMap低,当TreeMap存取元素时,要循环找到合适位置,因此比较耗性能,但是优势在于Entry总是按key根据排序规则保持有序状态。

    红黑树是一种平衡二叉树,树中每个节点的值,大于或等于它的左子树的所以节点的值,并且小于或者等于它的右子树的所以节点的值,这确保红黑树运行时可以快速在树中查找和定位所需节点。


Map的values()方法:    

    Map集合是关联数组,包含key和value两组集合。key可以组成Set集合,value可以组成List集合。

    实际的values()方法返回Map的所有value集合,但它们并不是List集合。分别是HashMap$Values和TreeMap$Values对象。两个Map类对象返回values()方法的实现完全相同(return (vs!=null?vs:values=new Values())),区别在于内部实现不同。

    Values集合类集成了AbstractCollection抽象类,但并不是一个Collection集合,因为没有实现add(Object o)方法,也就是说这个Values集合对象没有真正盛装任何Java对象。values()方法表面上返回一个Values对象,但这个集合并不能添加元素,主要功能用于遍历Map里的所以value,而遍历主要依赖HashIterator的nextEntry()方法实现。

    HashMap和TreeMap中Values类区别不太大,不过TreeMap通过“红黑树”实现,还提供了两个简单的工具方法(getFristEntry返回最小和successor获取下一个节点)。

    归纳总结:HashMap和TreeMap的values()方法返回其value返回的Collection集合,按正常理解,这个Collection应该是List集合,但是values()方法实现更加巧妙,返回一个不存储元素的Collection集合,遍历Collection元素时,实际遍历Map的value,并未把value组合成一个包含元素的对象,可以降低系统的内存开销。


List集合:

    List集合主要实现类:ArrayList、Vector和LinkList,还有Vector的子类Stack(本质上Stack还是Vector类,只是多了5个方法,扩展成为Stack类,Java已经不推荐使用,而推荐Deque代替)。

    Deque接口代表双端列表这种数据结构,既有队列的先进先出(FIFO),也有栈的性质(FILO),常用的实现类是ArrayDeque。

    Vector和ArrayList本质没有太大不同,都是基于Java数组实现的:

        序列化机制:ArrayList提供了writeObject和readObject实现定制序列化,Vector只实现了writeObject,未完全实现定制序列化;

        线程机制:Vector其实就是ArrayList的线程安全版本,但是Java推荐使用Collections工具类,通过工具类synchronizedList方法将ArrayList包装成线程安全。

        底层扩展方面:ArrayList在容量不足情况下总是扩充为原理1.5倍;Vector多了一个选择,可以传入capacityIncrement,如果大于0,扩充容量为原来容量加上capacityIncrement,否则扩充为原来2倍。


ArrayList和LinkList:

    ArrayList是顺序存储的线性表,底层采用数字保存每个集合元素,LinkList则是一种链式存储的线性表。

    ArrayList是数组实现,所以增减元素,需要对数组进行“整体搬家”,容量不足还需创建容量为1.5倍的数组,再由垃圾回收原来的数组,因此开销比较大;但在查找方面直接调用数组获取索引处元素,性能非常好;对应LinkList,开销主要集中于entry(int index)方法上,该方法需要一个个的搜索知道index处的元素,再对之进行处理,但在增减方面,只需要更改插入元素,性能比ArrayList好。

    总体来说,ArrayList调用add方法在尾端添加元素,无需搬家,查找性能非常好,总体上性能优于LinkList。


Iterator:

    对应Iteartor迭代器,只是一个接口,java各种集合都提供一个iterator方法用于迭代该集合元素,不需关心返回的是哪种实现类,这是典型的“迭代器模式”。

    使用Itarator进行迭代,通常不应该删除集合元素,否则会引发ConcurrentModificationExceotion异常。