对于ArrayList和LinkedList的存储方式相对简单,默认情况下就是顺序存储,先添加的元素在前面,后添加的元素在后面,不同的是ArrayList底层是通过数组来维护,LinkedList底层是通过链表来维护。这两种方式都可以重复添加相同的元素,根据应用情况不同自行选择。
好了,重头戏在下面!
对于HashSet和TreeSet来说,里面的元素是不能重复的,若重复则会覆盖前面的那个元素。而对于HashMap和TreeMap来说,里面存储的是Key-Value对,在所有的键值对中,Key值是不能重复的,但Value值是可以重复的,若重复存储相同的Key值,则会把前面的那个Key-Value对中的Key和Value都覆盖掉。
OK,下面就HashSet、TreeSet和HashMap、TreeMap的存储方式作简单总结。
HashSet的底层是通过HashMap来实现存储的,而TreeSet的底层又是通过TreeMap来实现存储的,而且HashSet和TreeSet中的元素都是以Key值存储的,这也是HashSet和TreeSet的元素不能重复的原因。由此可见,我们只要理解HashMap和TreeMap的存储方式就可以了。
HashMap的存储相对于TreeMap来说要简单,在上一篇文章中已经讲到了,其底层实际上维护的是一个Entry类型的数组table,而具体怎么存储取决于添加元素hashcode值和table的长度决定,例如:String类中重写了hashCode方法,所以我们可以算出hash code值,但对于某些没有重写hashCode方法的类型,则直接调用Object类中的hashCode方法,然而这个方法对我们是不透明的,所以不能算出hashcode值,所以排序算法不得而知。这里我认为没必要深究,我们只需知道HashMap中的元素是乱序的即可,尽管这种乱序也是“有序”的。
而TreeMap的排序则透明了许多,与HashMap最大的不同点是:TreeMap底层维护的是一个Entry类型的双向链表,而不是数组,每个Entry类中都定义有key、value、parent、left、right等属性, key、value表示节点中的key-value对,left、right则指向链接到当前节点左右两边的那两个节点,parent表示最后加入链表的那个节点的父节点,只是起过渡的作用。
下面分两种情况来讨论添加元素的具体操作:
第一种是添加的Key值类型是Java已有的类型
我们知道TreeMap的底层代码是根据Key-Value对中的Key值来排序的,这种情况Coder可以自行定义比较器,当然也可以用TreeMap中默认的。若是自己定义的比较器,则在生成TreeSet或TreeMap对象的时候将其传入,如:
TreeSet<String> set = new TreeSet<String>(new MyComparator());
第二种是添加的Key值类型不是Java已有类型,而是Coder自定义的类型
对于Coder自己定义的类型(如用户定义一个Person类),若想将其对象添加到TreeSet或TreeMap中,或是将其对象作为Key值添加到TreeMap中。Coder就必须定义自己的比较器,至少有两种方式可以定义该比较器,但无论哪一种都必须确定比较器要比较的对象,Coder可以用Person类中的某个属性作为比较器比较的对象,即以该属性来排序。
第一种方式:第一种情况一样,单独定义一个MyComparator类,不再赘述;第二种方式:模仿Java底层默认比较器的方式,即让要添加的Person类实现Comparable接口,并在Person类中实现compareTo方法,这样TreeMap中的put方法就可以调用Person中的compareTo方法了,从而实现比较器。
可能有人会问,TreeMap不是以Key值来排序吗?你这里的Key值就是Person对象啊,那不是应该以Person对象来排序吗?怎么可以以Person对象中的属性来排序呢?问题的答案是:我们的比较器确实是以Person对象为参数传进去的,但比较器内部的比较算法我们可以用Person对象的属性来完成。
感觉说的有点乱,大家多多包涵!下面给出测试代码,以Person类中的score属性排序,希望大家提宝贵意见!