集合
一、collection接口
java实用类库提供了一套相当完整的容器类来解决存储生命周期和数量不确定的对象,其中最基本的类型是 List、Set、Queue和 Map。这些对象称为集合类,但由于java的类库中使用了 collection 这个名字来代指该类库的一个特殊子集,所以我们就用了范围更广的“集合”来称呼。
所有的 collection 都可以使用 foreach 语法遍历。也可以使用 iterator 迭代器进行遍历
1.List接口
List集合是collection接口的子接口,List必须按照插入的顺序来保存数据,此集合是一个有序的集合。
* 方法
list的所有方法all方法参数类型都是collection,这些方法返回的值大部分都是boolean类型。容器类对象的调用remove,contains等方法时需要比较对象是否相等,这会涉及到equals方法和hashcode方法。即,相等的对象应该有相等的hashcode,当然,如果是自定义的类型,还需要重写这两个方法。
List接口的实现类
* Arraylist
允许使用数字来查找值,因此在某种意义上讲,他将数字与对象关联在了一起。是实现了基于动态数组的数据结构,随机访问元素比较快,擅长于作数据查询,增删慢,线程不安全,效率高
* LinkedList
底层数据结构是链表,随机访问元素比较慢,增删快,线程不安全,效率
说了那么多明白了吗?
不明白!
我们还是来段代码实际演示一下吧!
linkedlist插入和arraylist插入比较但有时你是不是遇到这种情况
linkedlist插入和arraylist插入比较瞬间一脸懵逼,甚至开始怀疑人生。
事实并不是你看到的这样,List在插入一条数据时,它首先是要做的是查找到你要插入的位置,这个过程是Linkedlist所不擅长的,这也证明了Linkedlist不适合作为数据查找来使用。找到要插入的位置后进行插入是很快的也就是你上面看到的那样。
2.Set接口
set的所有方法存入Set的每个元素都必须是唯一的,因为set不保存重复元素。加入Set的元素必须定义equals()方法以确保对象的唯一性。Set与Collection有完全一样的接口。set接口不保证维护元素的次序。
* HashSet
为快速查找而设计的Set,底层底层使用的是散列。存放Hashset的元素必须定义hashcode()。查看源码可知Hashset是通过Hashmap方式存储的。
* linkedhashset
具有hashset的查找速度,且内部使用链表维护元素的顺序(插入的次序)。于是在使用迭代器遍历Set时,结果会按照元素插入的次序显示。元素也必须定义hashCode()方法。
* TreeSet
保持次序的Set,底层是红 - 黑树结构。使用它可以从Set中提取有序的序列。元素必须实现Comparator接口。
如果没有其他的限制,通常情况下再使用set时都会使用Hashset,因为它对速度进行了优化。在使用Set时,你必须为散列存储和树型存储都创建一个equals()方法,而hashCode()只有在这个类被存放在HashSet或者LinkedHashSet中时才是必须重写的。但是,对于良好的变成风格而言,你应该在覆盖equals()方法的同时,总是把hashCode()方法也覆盖了。
二、Map接口
map的所有方法Map存储的是一组成对的“键值对”对象,可以通过键来找到值。映射表可以使我们通过一个对象来查找另一个对象,它也被称为“关联数组”,因为他将一个对象与另一个对象关联在了一起。又被称为“字典”,Map是强大的编程工具。
1.HashMap是Map的实现类
Map基于散列表的实现(他取代了Hashtable)。插入和查询“键值对”的开销是固定的。可以通过构造器设置容量和负载因子,以调整容器的性能。
2.TreeMap是Map的实现类
基于红黑树数据结构的实现。查看“键”或“键值对”时,它们会被排序(次序由Comparable或Comparator决定),它是通过键来排序的。TreeMap的特性在于,所得到的结果是经历排序的。treemap是唯一带有submap()方法的map,它可以返回一个子树。
hashcode()与equals()的比较
1、equals()方法用于比较对象的内容是否相等(覆盖以后)
2、hashcode()方法只有在集合中用到
3、当覆盖了equals()方法时,比较对象是否相等将通过覆盖后的equals()方法进行比较(判断对象的内容是否相等)。
4、当我们向一个集合中添加某个元素,集合会首先调用hashCode方法,这样就可以直接定位它所存储的位置,若该处没有其他元素,则直接保存。若该处已经有元素存在,就调用equals方法来匹配这两个元素是否相同,相同则不存,不同则散列到其他位置。这样处理,当我们存入大量元素时就可以大大减少调用equals()方法的次数,极大地提高了效率。所以hashCode扮演的角色为寻域(寻找某个对象在集合中区域位置)。hashCode可以将集合分成若干个区域,每个对象都可以计算出他们的hash码,可以将hash码分组,每个分组对应着某个存储区域,根据一个对象的hash码就可以确定该对象所存储区域,这样就大大减少查询匹配元素的数量,提高了查询效率。
5、将元素放入集合的流程图:
hashCode()方法与equals()方法重写
第一步:定义一个初始值,一般来说取17
int result = 17;
第二步:分别解析自定义类中与equals方法相关的字段(假如hashCode中考虑的字段在equals方法中没有考虑,则两个equals的对象就很可能具有不同的hashCode)
情况一:字段a类型为boolean 则[hashCode] = a ? 1 : 0;
情况二:字段b类型为byte/short/int/char, 则[hashCode] = (int)b;
情况三:字段c类型为long, 则[hashCode] = (int) (c ^ c>>>32);
情况四:字段d类型为float, 则[hashCode] = d.hashCode()(内部调用的是Float.hashCode(d), 而该静态方法内部调用的另一个静态方法是Float.floatToIntBits(d))
情况五:字段e类型为double, 则[hashCode] =
e.hashCode()(内部调用的是Double.hashCode(e),
而该静态方法内部调用的另一个静态方法是Double.doubleToLongBits(e),得到一个long类型的值之后,跟情况三进行类似的操作,得到一个int类型的值)
情况六:引用类型,若为null则hashCode为0,否则递归调用该引用类型的hashCode方法。
情况七:数组类型。(要获取数组类型的hashCode,可采用如下方法:s[0]*31 ^ (n-1) + s[1] * 31 ^ (n-2) + ..... + s[n-1], 该方法正是String类的hashCode实现所采用的算法)
第三步:对于涉及到的各个字段,采用第二步中的方式,将其依次应用于下式:
result = result * 31 + [hashCode];
补充说明一点:如果初始值result不取17而取0的话,则对于hashCode为0的字段来说就没有区分度了,这样更容易产生冲突。比如两个自定义类中,一个类比另一个类多出来一个或者几个字段,其余字段全部一样,分别new出来2个对象,这2个对象共有的字段的值全是一样的,而对于多来的那些字段的值正好都是0,并且在计算hashCode时这些多出来的字段又是最先计算的,这样的话,则这两个对象的hashCode就会产生冲突。还是那句话,hashCode方法的实现没有最好,只有更好。
网友评论