| 常见的Error | | |
| — | — | — |
| OutOfMemoryError | StackOverflowError | NoClassDeffoundError |
| 常见的Exception | | |
| — | — | — |
| 常见的非检查性异常 | | |
| ArithmeticException | ArrayIndexOutOfBoundsException | ClassCastException |
| IllegalArgumentException | IndexOutOfBoundsException | NullPointerException |
| NumberFormatException | SecurityException | UnsupportedOperationException |
| 常见的检查性异常 | | |
| IOException | CloneNotSupportedException | IllegalAccessException |
| NoSuchFieldException | NoSuchMethodException | FileNotFoundException |
内部类
===
-
非静态内部类没法在外部类的静态方法中实例化。
-
非静态内部类的方法可以直接访问外部类的所有数据,包括私有的数据。
-
在静态内部类中调用外部类成员,成员也要求用 static 修饰。
-
创建静态内部类的对象可以直接通过外部类调用静态内部类的构造器;创建非静态的内部类的对象必须先创建外部类的对象,通过外部类的对象调用内部类的构造器。
匿名内部类
-
匿名内部类不能定义任何静态成员、方法
-
匿名内部类中的方法不能是抽象的
-
匿名内部类必须实现接口或抽象父类的所有抽象方法
-
匿名内部类不能定义构造器
-
匿名内部类访问的外部类成员变量或成员方法必须用 final 修饰
多态
==
-
父类的引用可以指向子类的对象
-
创建子类对象时,调用的方法为子类重写的方法或者继承的方法
-
如果我们在子类中编写一个独有的方法,此时就不能通过父类的引用创建的子类对象来调用该方法
抽象和接口
=====
-
抽象类不能有对象(不能用 new 关键字来创建抽象类的对象)
-
抽象类中的抽象方法必须在子类中被重写
-
接口中的所有属性默认为:public static final;
-
接口中的所有方法默认为:public abstract;
集合框架
====
- List接口存储一组不唯一,有序(插入顺序)的对象, Set接口存储一组唯一,无序的对象。
HashMap
结构图
- JDK 1.7 HashMap 结构图
- JDK 1.8 HashMap 结构图
HashMap 的工作原理
HashMap 基于 hashing 原理,我们通过 put() 和 get() 方法储存和获取对象。当我们将键值对传递给 put() 方法时,它调用键对象的 hashCode() 方法来计算 hashcode,让后找到 bucket 位置来储存 Entry 对象。当两个对象的 hashcode 相同时,它们的 bucket 位置相同,‘碰撞’会发生。因为 HashMap 使用链表存储对象,这个 Entry 会存储在链表中,当获取对象时,通过键对象的 equals() 方法找到正确的键值对,然后返回值对象。
如果 HashMap 的大小超过了负载因子(load factor)定义的容量,怎么办?
默认的负载因子大小为 0.75,也就是说,当一个 map 填满了 75% 的 bucket 时候,和其它集合类(如 ArrayList 等)一样,将会创建原来 HashMap 大小的两倍的 bucket 数组,来重新调整 map 的大小,并将原来的对象放入新的 bucket 数组中。这个过程叫作 rehashing,因为它调用 hash 方法找到新的 bucket 位置。
为什么 String, Interger 这样的 wrapper 类适合作为键?
因为 String 是不可变的,也是 final 的,而且已经重写了 equals() 和 hashCode() 方法了。其他的 wrapper 类也有这个特点。不可变性是必要的,因为为了要计算 hashCode(),就要防止键值改变,如果键值在放入时和获取时返回不同的 hashcode 的话,那么就不能从 HashMap 中找到你想要的对象。不可变性还有其他的优点如线程安全。如果你可以仅仅通过将某个 field 声明成 final 就能保证 hashCode 是不变的,那么请这么做吧。因为获取对象的时候要用到 equals() 和 hashCode() 方法,那么键对象正确的重写这两个方法是非常重要的。如果两个不相等的对象返回不同的 hashcode 的话,那么碰撞的几率就会小些,这样就能提高 HashMap 的性能。
HashMap 与 HashTable 对比
HashMap 是非 synchronized 的,性能更好,HashMap 可以接受为 null 的 key-value,而 Hashtable 是线程安全的,比 HashMap 要慢,不接受 null 的 key-value。
HashMap.java
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
···
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
···
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
···
}
HashTable.java
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable {
···
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
···
addEntry(hash, key, value, index);
return null;
}
···
public synchronized V get(Object key) {
HashtableEntry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (HashtableEntry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return (V)e.value;
}
}
return null;
}
···
}
ConcurrentHashMap
Base 1.7
ConcurrentHashMap 最外层不是一个大的数组,而是一个 Segment 的数组。每个 Segment 包含一个与 HashMap 数据结构差不多的链表数组。
在读写某个 Key 时,先取该 Key 的哈希值。并将哈希值的高 N 位对 Segment 个数取模从而得到该 Key 应该属于哪个Segment,接着如同操作 HashMap 一样操作这个 Segment。
Segment 继承自 ReentrantLock,可以很方便的对每一个 Segmen 上锁。
对于读操作,获取 Key 所在的 Segment 时,需要保证可见性。具体实现上可以使用volat