HashMap 与 内部类
1.HashMap 是非线程安全的,需要collections的同步方法synchronizedMap。继承AbstractMap。ConcurrentHashMap是线程安全的,使用了分段锁,
HashTable是线程安全的,任一时间只有一个线程能写Hashtable。继承自Dictionary。
LinkedHashMap 保存插入顺序,也可按照构造时的参数,按照访问次序排序。
TreeMap能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,在使用TreeMap时,key必须实现Comparable接口或者在构造TreeMap传入自定义的Comparator,否则会在运行时抛出java.lang.ClassCastException类型的异常。
2.HashMap是数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的。首先是hash数组,然后相同的hash值放在一个链表中,超过8个节点,链表转换为红黑树。
3.Hash算法本质上就是三步:取key的hashCode值、高位运算、取模运算。h & (table.length -1)是取模运算。当length总是2的n次方时,h& (length-1)运算等价于对length取模,也就是h%length,但是&比%具有更高的效率。通过hashCode()的高16位异或低16位实现的:(h = k.hashCode()) ^ (h >>> 16),主要是从速度、功效、质量来考虑的,这么做可以在数组table的length比较小的时候,也能保证考虑到高低Bit都参与到Hash的计算中。
4、put方法。
①.判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容;
②.根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向⑥,如果table[i]不为空,转向③;
③.判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向④,这里的相同指的是hashCode以及equals;
④.判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向⑤;
⑤.遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可;
⑥.插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容。
5.扩容
超过阈值进行扩容,阈值等于threshold=Load factor*length。使用一个容量更大的数组来代替已有的容量小的数组,transfer()方法将原有Entry数组的元素拷贝到新的Entry数组里。
e.hash & oldCap
这样不用重新算,如果是链表储存的话。这个位与没扩容之前与相同,扩容之后也相同,就不需要重新计算hash。直接放置位置。
结论
(1) 扩容是一个特别耗性能的操作,所以当程序员在使用HashMap的时候,估算map的大小,初始化的时候给一个大致的数值,避免map进行频繁的扩容。
(2) 负载因子是可以修改的,也可以大于1,但是建议不要轻易修改,除非情况非常特殊。
(3) HashMap是线程不安全的,不要在并发的环境中同时操作HashMap,建议使用ConcurrentHashMap。
(4) JDK1.8引入红黑树大程度优化了HashMap的性能。
内部类
成员内部类、局部内部类、匿名内部类和静态内部类。
1.成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。默认构造方法会构造一个外部类指针。编译器会默认为成员内部类添加了一个指向外部类对象的引用,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象。
当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问:
外部类.this.成员变量 外部类.this.成员方法
外部类想访问成员内部类的成员却不是这么随心所欲了。在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问。
2.局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。局部内部类就像是方法里面的一个局部变量一样,是不能有 public、protected、private 以及 static 修饰符的。
3.匿名内部类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是重写。
局部内部类和匿名内部类只能访问局部final变量,变量生命周期不一样的原因,
4.静态内部类,类的前面多了一个关键字static。静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法。 不需要先创建外部类。
创建静态内部类对象的一般形式为: 外部类类名.内部类类名 xxx = new 外部类类名.内部类类名()
创建成员内部类对象的一般形式为: 外部类类名.内部类类名 xxx = 外部类对象名.new 内部类类名()
关于成员内部类的继承问题。一般来说,内部类是很少用来作为继承用的。但是当用来继承的话,要注意两点:
- 1)成员内部类的引用方式必须为 Outter.Inner
- 2)构造器中必须有指向外部类对象的引用,并通过这个引用调用super()。
内部类的好处:
- 1.每个内部类都能独立的继承一个接口的实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。内部类使得多继承的解决方案变得完整。
- 2.方便将存在一定逻辑关系的类组织在一起,又可以对外界隐藏。
- 3.方便编写事件驱动程序。
- 4.方便编写线程代码。
跳台阶
public class Solution {
public int JumpFloor(int target) {
int[] dp = new int[target];
dp[0]=1;
dp[1]=2;
if(target>2){
for(int i=2;i<target;i++){
dp[i]=dp[i-1]+dp[i-2];
}
}
return dp[target-1];
}
}
这样写会数组越界,因为当为1 的时候,dp[1]就越界了。数组加个1解决。
public class Solution {
public int JumpFloor(int target) {
int[] dp = new int[target+1];
dp[0]=1;
dp[1]=2;
if(target>2){
for(int i=2;i<target;i++){
dp[i]=dp[i-1]+dp[i-2];
}
}
return dp[target-1];
}
}