1)集合中要注意的知识点:
集合中只能存储引用数据类型,无法存储基本数据类型。比如在集合中int类型的值被包装成Integer类型放入集合。
混淆的知识点:
对与Object中定义的hashCode()其功能是不同的对象返回不同的hashCode值。注意:这只是Object中该方法的作用,其他类重写其hashCode()后并不一定满足。
比如,String重写后的hashCode()只要字符一样就返回true
2) List与Set的继承结构图。
注意:
1.ArrayList与Vector的底层都是用的数组,其必然有初始化容量。两个初始化容量都是10,不同的是:当容量不够的时候,ArrayList自动增容为1.5倍,而Vector是2倍。
2.LinkedList底层采用双向链表的数据结构.
其除了从Iterator接口中继承的迭代器外,还有List集合自己的迭代器
ListIterator,该迭代器与Iterator不同的是,迭代器自身提供set( ),add( )更改和添加的方法,在迭代的时候可以调用方法执行更改和添加的操作,
补充
- Iterator在迭代时只有删除操作,并且只能用迭代器对象删除不可以用集合对象在迭代的时候删除对象。
- ListIterator 调用add( )添加后的值在本次迭代循环中不会参与迭代!
2.如何优化ArrayList和Vector?
- 尽量减少扩容操作,因为扩容需要数组拷贝。数组拷贝很耗内存。
- 一般推荐在创建集合的时候指定初始化容量。
3. HashSet底层实际上是一个HashMap,同理,TreeSet底层实际上是一TreeMap。之所以SortedSet它存储的东西可比较,其实是因为其底层TreeMap将存储的引用类型强制类型转化为Comparable(接口 ),为什么要这么做??因为要调用该接口中的compareTo(),即xxx.compareTo(Object o),那么很显然如果存储的元素没有实现该接口必然会报ClassCastException()。而compareTo()底层实际上是利用二叉树进行排序的。所以HashSet中的元素想要能够实现排序就要实现该接口。我们平时用 的Sring,Integer等已经实现的该接口并重写了compareTo(),二者是通过CopareTo()方法来实现排序不可重复的。
另外,TreeSet还有一个有参构造方法,传入参数为一个比较器Comparator()。该比较器也是一个接口。其中有Compare()和equals().其方法与上面方法类似。区别在于解决问题的途径不同。相对来说,第一种方法的耦合度更高。一般推荐使用这一种。
补充:
中文的字符在一般数组和集合中是不支持排序的(可以给定Arrays.sort()一个比较器或给定TreeSet构造方法一个比较器 Comparator com=Collator.getInstance(Locale.CHINA)
Collator执行分区语言环境的比较并实现了Comparator接口,其getInstance方法返回一个比较器,参数传入一个地方语言关键字,是java.util.Locale类中定义的字段,以此来返回一个 可以比较中文字符的比较器)。注意:该比较器是使用GB2312编码只支持常用的6K多个汉字,并且其中分为常用字(拼音排序)和辅助字(笔画排序),只有这6k多个字中的常用字支持汉语拼音排序。
3) Map的继承结构图。
HashMap 和 HashTable有什么区别?
1.HashMap是非线程安全的 HashTable是线程安全的
2.HashMap的key值可以是空 HashTable是不可以的
3.HashMap的父类是java.util.AbstractMap<K,V> HashTable的父类是java.util.Dictionary<K,V>
4.HshMap与Hashtable的默认加载因子都是0.75都是以key,value键值对存储,但是前者默认初始化容量是16后者是11.
注意:HashSet底层是HashMap只不过它没有value这一项。HashSet是一个一个的存,而HashMap是一对一对的存。HashMap中的Key是以数组和链表组合的方式存储的。比如说前者存一个person类型该类型中有name和num属性。存进去的是一个Person而不是name,num键值对。现实逻辑中,当num和name一样我们认为是同一个对象。然而在HashMap中,只要是Key一样我们就认为是同一个对象(对应到HashSet中key一样就是name和num都一样),不会去看value。此处容易混淆。
关于Map集合中常用的方法
void clear(); | 清空Map |
boolean isEmpty(); | 判断该集合是否为空 |
int size(); 。 | 获取Map中键值对的个数 |
Object put(Object key, Object value); | 向集合中添加键值对 |
Object get(Object key); | 通过key获取value |
boolean containsKey(Object key); | 判断Map中是否包含这样的key |
boolean containsValue(Object value); | 判断Map中是否包含这样的value |
Object remove(Object key); | 通过key将键值对删除. |
Collection values(); | 获取Map集合中所有的value |
Set keySet(); | 获取Map中所有的key |
Set entrySet(); | 返回此映射中包含的映射关系的 Set 视图。 |
4) 链表的数据结构(单向和双向)。
5) HasMap的数据结构。
注意:
1.HashSet底层实际上是一个HashMap,HashMap底层采用了哈希表数据结构。
2.哈希表又叫做散列表,哈希表底层是一个数组,这个数组中每一个元素 是一个单向链表。每个单向链表都有一个独一无二的hash值,代表数组的下标。在某个单向链表中的每一个节点上的hash值是相等的。hash值实际上 是key调用hashCode方法,在通过"hash function"转换成的值。
3.如何向哈希表中添加元素:
先调用被存储的key的hashCode方法,经过哈希算法得出hash值,如果在 这个哈希表中不存在这个 hash值,则直接加入元素。如果该hash值已经 存在, 继续调用key之间的equals方法,如果equals方法返回false,则将该元素添加。如果equals方法返回true,则覆盖该元素。 (此处也暗示了我们要考虑重写hashCode和equals方法)
4.HashSet其实是HashMap中的key部分。HashSet有什么特点,HashMap中的key 应该具有相同的特点。
5.HashMap和HashSet初始化容量都是 16,默认加载因子是0.75(即当达到当前容量的75%时自动扩容,扩容2倍)。
代码例子:
/**
关于往Set集合中存储的元素,该元素的hashCode和equals方法.
HashMap中有一个put方法,put(key,value) key是无序不可重复的.
结论:存储在HashSet集合或者HashMap集合key部分的元素,需要同时重写hashCode+equals
*/
import java.util.*;
public class SetTest02{
public static void main(String[] args){
//创建集合
Set es = new HashSet();
Employee e1 = new Employee("1000","JACK");
Employee e2 = new Employee("1000","JACK");
Employee e3 = new Employee("1000","SCOTT");
Employee e4 = new Employee("2001","SUN");
Employee e5 = new Employee("3000","JIM");
Employee e6 = new Employee("3001","COOK");
System.out.println(e1.hashCode());
System.out.println(e2.hashCode());
//添加元素
es.add(e1);
es.add(e2);
es.add(e3);
es.add(e4);
es.add(e5);
es.add(e6);
//查看集合元素个数
System.out.println(es.size()); //5
}
}
//根据现实的业务逻辑得知:该公司员工编号是: 1000 - 9999
class Employee{
//编号
String no;
//姓名
String name;
//Constructor
Employee(String no,String name){
this.no = no;
this.name = name;
}
//重写equals方法.
//如果员工编号相同,并且名字相同,则是同一个对象
public boolean equals(Object o){
//重写equals方法格式参考.
if(this==o){
return true;
}
if(o instanceof Employee){
Employee e = (Employee)o;
if(e.no.equals(this.no) && e.name.equals(this.name)){
return true;
}
}
return false;
}
//重写hashCode方法.
public int hashCode(){
//以员工编号分组.
//return no.hashCode();
return (no+name.hashCode());//HashMap得到对象的hashCode值,在对这个值进行哈希法
//(会对容量(初始化的容量为16)取模)。
//理论上说,只要只返回对象的的一个int属性为随机的,那么就可以认为其可以代表对象hashcode
}
}
补充:哈希表(散列表)哈希算法
对不同的关键字可能得到同一散列地址,即k1≠k2,而f(k1)=f(k2),这种现象称为碰撞(英语:Collision)。 若对于关键字集合中的任一个关键字,经散列函数映象到地址集合中任何一个地址的概率是相等的,则称此类散列函数为均匀散列函数(Uniform Hash function),这就是使关键字经过散列函数得到一个“随机的地址”,从而减少碰撞。
散列表的查找过程基本上和造表过程相同。一些关键码可通过散列函数转换的地址直接找到,另一些关键码在散列函数得到的地址上产生了冲突,需要按处理冲突的方法进行查找。
查找过程中,关键码的比较次数,取决于产生冲突的多少,产生的冲突少,查找效率就高,产生的冲突多,查找效率就低。因此,影响产生冲突多少的因素,也就是影响查找效率的因素。影响产生冲突多少有以下三个因素:
1. 散列函数是否均匀;
2. 处理冲突的方法;
3. 散列表的装填因子。
散列表的装填因子定义为:α= 填入表中的元素个数 / 散列表的长度
α是散列表装满程度的标志因子。由于表长是定值,α与“填入表中的元素个数”成正比,所以,α越大,填入表中的元素较多,产生冲突的可能性就越大;α越小,填入表中的元素较少,产生冲突的可能性就越小。
实际上,散列表的平均查找长度是装填因子α的函数,只是不同处理冲突的方法有不同的函数。
从以上我们可以得知:
1. HashMap的默认加载因子0.75不是随便给定的,它是根据实际解决冲突方法的函数及内存使用效率结合计算的出来。
因为α= 填入表中的元素个数 / 散列表的长度,,α越大,填入表中的元素较多,产生冲突的可能性就越大,填充的少了效率会高但是浪费内存;
2.调用对象的hasCode值之后要经过散列函数让其碰撞减少提高性能,只要hashCode()中计算的对象具有随机性,就能满足均匀散列的要求
6) List 、Set、Map的空值
- List 允许任意数量的空值。
- Set 最多允许一个空值的出现。
- Map 只允许出现一个空键但允许任意数量的空值。