一、基本数据类型
注释
单行注释://
区域注释:/* */
文档注释:/** */
数值
对于byte类型而言,有28个可能的数值,一半是负数一半是正数。因为0被存储为正的二进制,因此整数的个数比负数少1。
如果要表示八进制在数值前加0,要表示十六进制在数值前加0x。
使用float类型时,必须添加后缀F或f。在JAVA SE5.0前,浮点型只能用十进制数表示,从JAVA SE5.0之后可以使用十六进制表示浮点数值。例如:0.125可以表示为0x1.0p-3。在十六进制表示法中,使用p表示指数,而不是e。
JAVA采用UNICODE编码,因此每个字符占2个字节。
boolean不能与任何类型的值进行转换替换。也就是说JAVA中没有false是0,true是非0这种说法。
数值转换
自动转换:byte->short(char)->int->long->float->double,低精度向高精度转换时是自动的
手动强制转换:高精度向低精度转换,要进行手动强制转换。
隐含强制转换:byte b=123;因为123是int类型的字面常量,他们之间转换需要使用强制类型转换。这种情况下由系统自动识别进行转换。但是对于变量则不行。如:int i=123; byte b=i;
二、表达式
三、流程控制
四、数组
五、对象和类
public static void sortAndPrint(int... entrys){
//此时的entrys可以当作数组来进行操作
Arrays.sort(entrys);
}
//调用该方法
sortAndPrint(1,2,3,4,5,6,7,8,9,10);
六、访问控制
七、继承
八、接口
public class LeftFragment extends Fragment{
public interface MyListener{
public void showMessage(int index);
}
private MyListener mListener;
@Override
public void onAttach(Activity activity) {/*判断宿主activity是否实现了接口MyListener*/
super.onAttach(activity);
try {
mListener = (MyListener) activity;
}catch (ClassCastException e) {
throw new ClassCastException(getActivity().getClass().getName()
+" must implements interface MyListener");
}
}
class MyButtonClickListener implements View.OnClickListener{
@Override
public void onClick(View v) {
if(button == mButton1) {
mListener.showMessage(1);
}
if(button == mButton2) {
mListener.showMessage(2);
}
if(button == mButton3) {
mListener.showMessage(3);
}
}
}
}
public class MainActivity extends Activity implements LeftFragment.MyListener{
@Override
public void showMessage(int index) {
if(1 == index) {
showMessageView.setText(R.string.first_page);
}else if(2 == index) {
showMessageView.setText(R.string.second_page);
}else {
showMessageView.setText(R.string.then_page);
}
}
}
九、构造器
- 调用兄弟构造器
- 重载构造器之间的相互调用称为调用兄弟构造器。
- 使用关键字this来调用兄弟构造器
public H(String s){
}
public H(){
this("lin");
}
十、内部类
public class text {
public static void main(String[] args) {
int j = 0;
H h = new H() {
public void change(H h) {
System.out.println(i);
System.out.println(j);
}
public void la(){
}
};
h.change(h);
//无法调用h.la();
}
}
class H {
public int i = 0;
public H() {
}
public void change(H h) {
}
}
Button.setOnClickListener(new View.onCliclListener(){
//匿名内部类,不用重新创建一个类
}
);
十一、异常处理
//以下代码无法通过编译,如果将IOException改为Exception则可以
try {
int a=0;
} catch (IOException e) {
System.out.println("catch");
}
public static void la(int i) throws Exception {
try {
throw new IOException() ;
} catch (Exception e) {
throw e;
}
}
public static void la(int i) throws Exception {
}
public static void la(int i) {
try {
if(i==1){
throw new IOException();
}else {
throw new ClassNotFoundException();
}
} catch (IOException e) {
// TODO: handle exception
}catch (ClassNotFoundException e) {
// TODO: handle exception
}catch (Exception e) {
// TODO: handle exception
}
}
十二、字符串
//将默认编码格式转换为iso8856
String s1="LinGengLiang";
String s2=new String(s1.getBytes(),"iso8856");
//再将iso8856转换为gd2312
String s3=new String(s2.getBytes("iso8856"),"gd2312");
十三、集合框架
HashMap map=new HashMap();
map.put(1, 123);
map.put("1", "456");
System.out.println(map.get(1));
System.out.println(map.get("1"));
Collection treeSet=new TreeSet();
Iterator i=treeSet.iterator();//获取迭代器
//使用hasNext,next等方法进行访问
class H implements Comparable{
public int i;
public int k;
public H(int i,int k) {
this.i=i;
this.k=k;
}
@Override
public int compareTo(Object o) {
H h=(H) o;
return this.i-h.i;
}
}
class Compare implements Comparator{
@Override
public int compare(Object o1, Object o2) {
H h1=(H) o1;
H h2=(H) o2;
return h1.k-h2.k;
}
}
- Set treeSet=new TreeSet(new Compare()); //为集合添加比较器
//Node是单向链表,它实现了Map.Entry接口
static class Node<k,v> implements Map.Entry<k,v> {
final int hash;
final K key;
V value;
Node<k,v> next;
//构造函数Hash值 键 值 下一个节点
Node(int hash, K key, V value, Node<k,v> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + = + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
//判断两个node是否相等,若key和value都相等,返回true。可以与自身比较为true
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<!--?,?--> e = (Map.Entry<!--?,?-->)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
static final class TreeNode<k,v> extends LinkedHashMap.Entry<k,v> {
TreeNode<k,v> parent; // 父节点
TreeNode<k,v> left; //左子树
TreeNode<k,v> right;//右子树
TreeNode<k,v> prev; // needed to unlink next upon deletion
boolean red; //颜色属性
TreeNode(int hash, K key, V val, Node<k,v> next) {
super(hash, key, val, next);
}
//返回当前节点的根节点
final TreeNode<k,v> root() {
for (TreeNode<k,v> r = this, p;;) {
if ((p = r.parent) == null)
return r;
r = p;
}
}
//............接下来还有许多方法
}
填充比,默认值为0.75,如果实际元素所占容量占分配容量的75%时就要扩容了。如果填充比很大,说明利用的空间很多,但是查找的效率很低,因为链表的长度很大(当然最新版本使用了红黑树后会改进很多),HashMap本来是以空间换时间,所以填充比没必要太大。但是填充比太小又会导致空间浪费。如果关注内存,填充比可以稍大,如果主要关注查找性能,填充比可以稍小。
private static final long serialVersionUID = 362498820763181265L;
- static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 默认容量为16
- static final int MAXIMUM_CAPACITY = 1 << 30;//最大容量
- static final float DEFAULT_LOAD_FACTOR = 0.75f;//填充比
- //当add一个元素到某个位桶,其链表长度达到8时将链表转换为红黑树
static final int TREEIFY_THRESHOLD = 8;
- static final int UNTREEIFY_THRESHOLD = 6;
- static final int MIN_TREEIFY_CAPACITY = 64;
- transient Node<k,v>[] table;//存储元素的数组
- transient Set<map.entry<k,v>> entrySet;
- transient int size;//存放元素的个数
- transient int modCount;//被修改的次数fast-fail机制
- int threshold;//临界值 当实际大小(容量*填充比)超过临界值时,会进行扩容
- final float loadFactor;//填充比
final Node<K,V>[] resize() {
//将旧数组赋值给oldTab
Node<K,V>[] oldTab = table;
//获取到目前的容量
int oldCap = (oldTab == null) ? 0 : oldTab.length;
//临界值
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
//当目前的容量超过最大容量时直接返回久数组
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
//否则将容量扩大两倍,也将临界值扩大两倍
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
//数组辅助到新的数组中,分红黑树和链表讨论
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
首先由key值通过hash(key)获取hash值h,再通过 h&(length-1)得到所在数组位置。一般对于哈希表的散列常用的方法有直接定址法,除留余数法等,既要便于计算,又能减少冲突。h&(length-1)相当于对数组长度取余,这样效率比较高。
//这段代码保证HashMap的容量总是2的n次方
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
这段代码通过移位操作保证了哈希表的容量一直是2的整数倍。可以从源码看出,在HashMap的构造函数中,都直接或间接的调用了tableSizeFor函数。下面分析原因:length为2的整数幂保证了length-1最后一位(当然是二进制表示)为1,从而保证了取索引操作 h&(length-1)的最后一位同时有为0和为1的可能性,保证了散列的均匀性。反过来讲,当length为奇数时,length-1最后一位为0,这样与h按位与的最后一位肯定为0,即索引位置肯定是偶数,这样数组的奇数位置全部没有放置元素,浪费了大量空间。
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<k,v>[] tab; Node<k,v> p; int n, i;
//如果tab为空或长度为0,则分配内存resize()
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//(n - 1) & hash找到put位置,如果为空,则直接put
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<k,v> e; K k;
//第一节节点hash值同,且key值与插入key相同
if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)//属于红黑树处理冲突
e = ((TreeNode<k,v>)p).putTreeVal(this, tab, hash, key, value);
else {
//链表处理冲突
for (int binCount = 0; ; ++binCount) {
//p第一次指向表头,以后依次后移
if ((e = p.next) == null) {
//e为空,表示已到表尾也没有找到key值相同节点,则新建节点
p.next = newNode(hash, key, value, null);
//新增节点后如果节点个数到达阈值,则将链表转换为红黑树
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//容许null==null
if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;//更新p指向下一个节点
}
}
//更新hash值和key值均相同的节点Value值
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e); //该方法在LinkedHashMap被调用
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
public V get(Object key) {
Node<k,v> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
final Node<k,v> getNode(int hash, Object key) {
Node<k,v>[] tab; Node<k,v> first, e; int n; K k;
//hash & (length-1)得到对象的保存位
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
//如果第一个节点是TreeNode,说明采用的是数组+红黑树结构处理冲突
//遍历红黑树,得到节点值
if (first instanceof TreeNode)
return ((TreeNode<k,v>)first).getTreeNode(hash, key);
//链表结构处理
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
// 版本号
private static final long serialVersionUID = 8683452581122892189L;
// 缺省容量为10
private static final int DEFAULT_CAPACITY = 10;
// 空对象数组,当传入大小为0时,运用此数组
private static final Object[] EMPTY_ELEMENTDATA = {};
// 缺省空对象数组,当没有传入大小时,运用此数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 元素数组,表示该数组不能被序列化,该数组是实际用来存储的
transient Object[] elementData;
// 实际元素大小,默认为0
private int size;
// 最大数组容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
}
public ArrayList() {
// 无参构造函数,设置元素数组为空
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
public boolean add(E e) { // 添加元素
ensureCapacityInternal(size + 1); // 判断数组是否需要扩容
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // 判断元素数组是否为空数组
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); // 取较大值
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
// 结构性修改加1
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
int oldCapacity = elementData.length; // 旧容量
int newCapacity = oldCapacity + (oldCapacity >> 1); // 新容量为旧容量的1.5倍
if (newCapacity - minCapacity < 0) // 新容量小于参数指定容量,修改新容量
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0) // 新容量大于最大容量
newCapacity = hugeCapacity(minCapacity); // 指定新容量
// 拷贝扩容,扩容的方式就是直接复制到一个新数组中
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {if (minCapacity < 0) // overflowthrow new OutOfMemoryError();return (minCapacity > MAX_ARRAY_SIZE) ?Integer.MAX_VALUE :MAX_ARRAY_SIZE;}
public void add(int index, E element) {
//检查索引是否合法,如果不合法抛出异常
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
public E set(int index, E element) {
// 检验索引是否合法,
如果不合法抛出异常rangeCheck(index);
// 旧值
E oldValue = elementData(index);
// 赋新值
elementData[index] = element;
// 返回旧值
return oldValue;
}
public E get(int index) {
// 检验索引是否合法
rangeCheck(index);
return elementData(index);
}
public E remove(int index) {
// 检查索引是否合法
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
// 需要移动的元素的个数
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 赋值为空,有利于进行GC
elementData[--size] = null;
// 返回旧值
return oldValue;
}
- LinkedList分析
- LinkedList底层使用的双向链表结构,有一个头结点和一个尾结点,双向链表意味着我们可以从头开始正向遍历,或者是从尾开始逆向遍历,并且可以针对头部和尾部进行相应的操作。
- LinkedList的类继承结构很有意思,我们着重要看是Deque接口,Deque接口表示是一个双端队列,那么也意味着LinkedList是双端队列的一种实现(在尾结点插入,在头结点删除,和队列一样),所以,基于双端队列的操作在LinkedList中全部有效。
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
- 静态内部类Node就是实际的结点,用于存放实际元素的地方。
private static class Node<E> {
E item; // 数据域
Node<E> next; // 后继
Node<E> prev; // 前驱
// 构造函数,赋值前驱后继
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
- LinkedList的属性非常简单,一个头结点、一个尾结点、一个表示链表中实际元素个数的变量。注意,头结点、尾结点都有transient关键字修饰,这也意味着在序列化时该域是不会序列化的。
- add函数:将元素添加到链表尾部。
public boolean add(E e) {
// 添加到末尾
linkLast(e);
return true;
}
void linkLast(E e) {
// 保存尾结点,l为final类型,不可更改
final Node<E> l = last;
// 新生成结点的前驱为l,后继为null
final Node<E> newNode = new Node<>(l, e, null);
// 重新赋值尾结点
last = newNode;
if (l == null) // 尾结点为空
first = newNode; // 赋值头结点
else // 尾结点不为空
l.next = newNode; // 尾结点的后继为新生成的结点
// 大小加1
size++;
// 结构性修改加1
modCount++;
}
- addAll(int index,Collection c)函数:参数中的index表示在索引下标为index的结点(实际上是第index + 1个结点)的前面插入。在addAll函数中,addAll函数中还会调用到node函数,get函数也会调用到node函数,此函数是根据索引下标找到该结点并返回。
public boolean addAll(int index, Collection<? extends E> c) {
// 检查插入的的位置是否合法
checkPositionIndex(index);
// 将集合转化为数组
Object[] a = c.toArray();
// 保存集合大小
int numNew = a.length;
if (numNew == 0) // 集合为空,直接返回
return false;
Node<E> pred, succ; // 前驱,后继
if (index == size) { // 如果插入位置为链表末尾,则后继为null,前驱为尾结点
succ = null;
pred = last;
} else { // 插入位置为其他某个位置
succ = node(index); // 寻找到该结点
pred = succ.prev; // 保存该结点的前驱
}
for (Object o : a) { // 遍历数组
@SuppressWarnings("unchecked") E e = (E) o; // 向下转型
// 生成新结点
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null) //表示在第一个元素之前插入(索引为0的结点)
first = newNode;
else
pred.next = newNode;
pred = newNode;
}
if (succ == null) { // 表示在最后一个元素之后插入
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
}
// 修改实际元素个数
size += numNew;
// 结构性修改加1
modCount++;
return true;
}
Node<E> node(int index) {
// 判断插入的位置在链表前半段或者是后半段
if (index < (size >> 1)) { // 插入位置在前半段
Node<E> x = first;
for (int i = 0; i < index; i++) // 从头结点开始正向遍历
x = x.next;
return x; // 返回该结点
} else { // 插入位置在后半段
Node<E> x = last;
for (int i = size - 1; i > index; i--) // 从尾结点开始反向遍历
x = x.prev;
return x; // 返回该结点
}
}
在根据索引查找结点时,会有一个小优化,结点在前半段则从头开始遍历,在后半段则从尾开始遍历,这样就保证了只需要遍历最多一半结点就可以找到指定索引的结点。
- unlink函数:在调用remove移除结点时,会调用该方法。
E unlink(Node<E> x) {
// 保存结点的元素
final E element = x.item;
// 保存x的后继
final Node<E> next = x.next;
// 保存x的前驱
final Node<E> prev = x.prev;
if (prev == null) { // 前驱为空,表示删除的结点为头结点
first = next; // 重新赋值头结点
} else { // 删除的结点不为头结点
prev.next = next; // 赋值前驱结点的后继
x.prev = null; // 结点的前驱为空,切断结点的前驱指针
}
if (next == null) { // 后继为空,表示删除的结点为尾结点
last = prev; // 重新赋值尾结点
} else { // 删除的结点不为尾结点
next.prev = prev; // 赋值后继结点的前驱
x.next = null; // 结点的后继为空,切断结点的后继指针
}
x.item = null; // 结点元素赋值为空
// 减少元素实际个数
size--;
// 结构性修改加1
modCount++;
// 返回结点的旧元素
return element;
}
- TreeMap
- HashMap和LinkedHashMap都是用哈希值去寻找我们想要的键值对,优点是有O(1)的查找速度。但是如果对查找性能要求不高,反而对有序性要求比较高(key值有序)的应用场景就可以使用TreeMap。
- TreeMap继承了NavigableMap,而NavigableMap继承自SortedMap,NavigableMap有几种方法,分别是不同的比较要求:floorKey是小于等于,ceilingKey是大于等于,lowerKey是小于,higherKey是大于。
public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable
{
private final Comparator<? super K> comparator;
-
//
红黑树的结点,root为根节点,接下来所有对红黑树的操作都通过该root结点 private transient Entry<K,V> root = null;
private transient int size = 0;
private transient int modCount = 0;
public TreeMap() {
comparator = null;
}
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
//后面省略
}
static final class Entry<K,V> implements Map.Entry<K,V> {
K key;
V value;
Entry<K,V> left = null;
Entry<K,V> right = null;
Entry<K,V> parent;
boolean color = BLACK; //红黑树默认颜色为黑色
//后续省略
}
- 红黑树的第4条性质保证了这些路径中的任意一条都不存在连续的红节点,而红黑树的第5条性质又保证了所有的这些路径上的黑色节点的数目相同。因而最短路径必定是只包含黑色节点的路径,而最长路径为红黑节点互相交叉的路径,由于所有的路径的起点必须是黑色的,而红色节点又不能连续存在,因而最长路径的长度为全为黑色节点路径长度的二倍。
- put方法:
public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) {
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
//如果有比较器则使用该比较器进行比较,找到插入位置的父节点
//如果没有比较器则使用key值默认的比较器
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
else {
if (key == null)
throw new NullPointerException();
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
//修复红黑树
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
- get方法:就是二叉查找树的查找方法,通过比较器和每个结点进行比较,判断是向左移动还是向右移动。
final Entry<K,V> getEntry(Object key) {
// Offload comparator-based version for sake of performance
if (comparator != null)
return getEntryUsingComparator(key);
if (key == null)
throw new NullPointerException();
Comparable<? super K> k = (Comparable<? super K>) key;
Entry<K,V> p = root;
while (p != null) {
int cmp = k.compareTo(p.key);
if (cmp < 0)
p = p.left;
else if (cmp > 0)
p = p.right;
else
return p;
}
return null;
}
- LinkedHashMap
- 当希望按照插入的顺序输出,又可以使用Hash来快速查找对象,可以使用LinkedHashMap,它是一个按顺序存放的双向链表保证key的顺序。
- LinkedHashMap继承了HashMap,同时继承HashMap的Node对象,生成自己独特的存储数据结构Entry<K,V>。
-
public class LinkedHashMap<K,V>
extends HashMap<K,V> implements Map<K,V>
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
然后又三个自己的独特成员://链表头结点
transient LinkedHashMap.Entry<K,V> head;
//链表尾结点
transient LinkedHashMap.Entry<K,V> tail;
// 简单说就是这个用来控制元素的顺序,
// true: 是访问的顺序,也就是谁最先访问,就排在第一位
// false:存放顺序,就是你put 元素的时候的顺序
final boolean accessOrder;
-
- 初始化:LinkedHashMap初始化时,可以选择是否初始化accessOrder。如果不初始化,则默认是false,此时保持放入元素先后的顺序不变化,即按照你插入的顺序排列和输出。如果选择初始化为true,则按照访问顺序排列。
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);
accessOrder = false;
}
public LinkedHashMap() {
super();
accessOrder = false;
}
public LinkedHashMap(Map<? extends K, ? extends V> m) {
super(m);
accessOrder = false;
}
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
- LinkedHashMap没有重写put函数,也就是说它直接使用的是HashMap的put函数。在HashMap中当找到了存放结点并且这个结点已经存在时,有这样一段代码。
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e); //该方法在LinkedHashMap被调用
return oldValue;
}
afterNodeAccess这个方法却在LinkedHashMap中实现:void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
if (accessOrder && (last = tail) != e) {
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a != null)
a.before = b;
else
last = b;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
tail = p;
++modCount;
}
}
- get函数,直接取出结点,如果accessOrder为真,则调用afterNodeAccess方法重新进行排序:
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
- HashSet
- HashSet底层是基于HashMap 或者 LinkedHashMap实现的,但是HashSet中由于只包含键,不包含值,由于在底层具体实现时,使用的HashMap或者是LinkedHashMap(可以指定构造函数来确定使用哪种结构),我们知道HashMap是键值对存储,所以为了适应HashMap存储,HashSet增加了一个PRESENT类域(类所有),所有的键都有同一个值(PRESENT)。
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();
- 初始化,通过构造函数来判断到底使用HashMap还是LinkedHashMap。
public HashSet() {
map = new HashMap<>();
}
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
- add函数:
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
- HashSet没有get函数,只能通过迭代器获取存入的值。
public Iterator<E> iterator() {
return map.keySet().iterator();
}
- TreeSet其实是由TreeMap底层实现,方法和HashSet差不多,就是通过TreeMap进行存储生成红黑树。并且增加了一个PRESENT类域(类所有),所有的键都有同一个值(PRESENT)。
- ConcurrentHashMap http://blog.csdn.net/u010723709/article/details/48007881 http://blog.csdn.net/sk199048/article/details/50847722 http://www.cnblogs.com/huaizuo/archive/2016/04/20/5413069.html
- ConcurrentHashMap是conccurrent家族中的一个类,由于它可以高效地支持并发操作,以及被广泛使用。与同是线程安全的老大哥HashTable相比,它已经更胜一筹,因此它的锁更加细化,而不是像HashTable一样为几乎每个方法都添加了synchronized锁,这样的锁无疑会影响到性能。
- 在源码JDK8版本中,它摒弃了Segment(锁段)的概念,而是启用了一种全新的方式实现,利用CAS算法。它沿用了与它同时期的HashMap版本的思想,底层依然由“数组”+链表+红黑树的方式思想,但是为了做到并发,又增加了很多辅助的类,例如TreeBin,Traverser等对象内部类。
- sizeCtl是一个控制标识符,因为它是一个控制标识符,在不同的地方有不同的用途,而且它的取值不同,也代表不同的含义。实际容量>=sizeCtl,则扩容。
- Node与HashMap的定义很相似,但是它的value和next属性设置了volatile同步锁,它不允许调用setValue方法直接改变Node的value域,它增加了find方法辅助map.get()方法。
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
volatile V val;
volatile Node<K,V> next;
Node(int hash, K key, V val, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.val = val;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return val; }
public final int hashCode() { return key.hashCode() ^ val.hashCode(); }
public final String toString(){ return key + "=" + val; }
public final V setValue(V value) {
throw new UnsupportedOperationException();
}
public final boolean equals(Object o) {
Object k, v, u; Map.Entry<?,?> e;
return ((o instanceof Map.Entry) &&
(k = (e = (Map.Entry<?,?>)o).getKey()) != null &&
(v = e.getValue()) != null &&
(k == key || k.equals(key)) &&
(v == (u = val) || v.equals(u)));
}
/**
* Virtualized support for map.get(); overridden in subclasses.
*/
Node<K,V> find(int h, Object k) {
Node<K,V> e = this;
if (k != null) {
do {
K ek;
if (e.hash == h &&
((ek = e.key) == k || (ek != null && k.equals(ek))))
return e;
} while ((e = e.next) != null);
}
return null;
}
}
- 当链表长度过长的时候,会转换为TreeNode。但是与HashMap不相同的是,它并不是直接转换为红黑树,而是把这些结点包装成TreeNode放在TreeBin对象中,由TreeBin完成对红黑树的包装。而且TreeNode在ConcurrentHashMap集成自Node类,而并非HashMap中的集成自LinkedHashMap.Entry<K,V>类,也就是说TreeNode带有next指针,这样做的目的是方便基于TreeBin的访问。
static final class TreeNode<K,V> extends Node<K,V> {
TreeNode<K,V> parent; // red-black tree links
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev; // needed to unlink next upon deletion
boolean red;
TreeNode(int hash, K key, V val, Node<K,V> next,
TreeNode<K,V> parent) {
super(hash, key, val, next);
this.parent = parent;
}
Node<K,V> find(int h, Object k) {
return findTreeNode(h, k, null);
}
/**
* Returns the TreeNode (or null if not found) for the given key
* starting at given root.
*/
final TreeNode<K,V> findTreeNode(int h, Object k, Class<?> kc) {
if (k != null) {
TreeNode<K,V> p = this;
do {
int ph, dir; K pk; TreeNode<K,V> q;
TreeNode<K,V> pl = p.left, pr = p.right;
if ((ph = p.hash) > h)
p = pl;
else if (ph < h)
p = pr;
else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
return p;
else if (pl == null)
p = pr;
else if (pr == null)
p = pl;
else if ((kc != null ||
(kc = comparableClassFor(k)) != null) &&
(dir = compareComparables(kc, k, pk)) != 0)
p = (dir < 0) ? pl : pr;
else if ((q = pr.findTreeNode(h, k, kc)) != null)
return q;
else
p = pl;
} while (p != null);
}
return null;
}
}
- TreeBin:这个类并不负责包装用户的key、value信息,而是包装的很多TreeNode节点。它代替了TreeNode的根节点,也就是说在实际的ConcurrentHashMap“数组”中,存放的是TreeBin对象,而不是TreeNode对象,这是与HashMap的区别。另外这个类还带有了读写锁。可以看到在构造TreeBin节点时,仅仅指定了它的hash值为TREEBIN常量,这也就是个标识位。
static final class TreeBin<K,V> extends Node<K,V> {
TreeNode<K,V> root;
volatile TreeNode<K,V> first;
volatile Thread waiter;
volatile int lockState;
// values for lockState
static final int WRITER = 1; // set while holding write lock
static final int WAITER = 2; // set when waiting for write lock
static final int READER = 4; // increment value for setting read lock
- ForwardingNode:一个用于连接两个table的节点类。它包含一个nextTable指针,用于指向下一张表。而且这个节点的key value next指针全部为null,它的hash值为-1。这里面定义的find的方法是从nextTable里进行查询节点,而不是以自身为头节点进行查找。
- CAS:在ConcurrentHashMap中,随处可以看到U, 大量使用了U.compareAndSwapXXX的方法,这个方法是利用一个CAS算法实现无锁化的修改值的操作,他可以大大降低锁代理的性能消耗。这个算法的基本思想就是不断地去比较当前内存中的变量值与你指定的一个变量值是否相等,如果相等,则接受你指定的修改的值,否则拒绝你的操作。因为当前线程中的值已经不是最新的值,你的修改很可能会覆盖掉其他线程修改的结果。
- Unsafe静态块:unsafe代码块控制了一些属性的修改工作,比如最常用的SIZECTL 。在这一版本的concurrentHashMap中,大量应用来的CAS方法进行变量、属性的修改工作。利用CAS进行无锁操作,可以大大提高性能。
十四、泛型
- 泛型类型
- 一个泛型类的定义格式如下:这里引入了类型变量T,一个类型变量可以是用户指定的任何非基本数据类型。类型变量T不能再静态方法中使用。
class 类名<T1,T2.......Tn>{...........}
- 类型形式参数的名字用单个、大写字母表示。一下是常用的类型形式参数名:
E-元素(常用语集合框架)
K-键值
N-数字
T-类型
V-值
S\U\V-第二三四类型
- 钻石运算符:JAVA SE7之后的版本中,调用泛型类的构造函数时,只要编译器能够通过上下文确定或推断出类型实际参数,就可以用类型实际参数的空集<>代替类型实际参数。
ClassName<Integer> cn=new ClassName<>();
- 泛型类可以有多个类型形式参数:
class className<K,V> implements Pair<K,V> //一旦类继承或实现了泛型类或接口,则该类必须也为泛型类,否则编译错误
- 原生类型:原生类型是不带有任何类型参数的泛型类或接口的名字。
ClassName<Integer> cn=new ClassName<>();
ClassName cn=new ClassName();
允许把一个参数化类型传递给一个对应的原生类型,但是把原生类型赋值给参数化类型则会警告。
- 泛型方法
- 泛型方法是一类引入自己类型形式参数的方法。这类似于声明一个泛型类型,只是类型形式参数的辖域限制在声明该参数的方法中。该方法允许静态和非静态的泛型方法。
public class text<T> {
public static void main(String[] args) {
NumberOf1(2, 3);
}
public static <K,V> void NumberOf1(K k,V v) {
System.out.println(k);
System.out.println(v);
}
}
-
public class text<T> {
T t;
public static void main(String[] args) throws InterruptedException {
text<Integer> tt=new text<Integer>();
tt.t=1;
System.out.println(tt.t);
tt.NumberOf1("123456"); //正确
tt.NumberOf2("123456") //错误,因为类中的T为Integer类型
}
//以下这两个函数是完全不同的,NumberOf1中的T是方法内部自己的类型参数和类中的T无关
//NumberOf2中的T是类中的T,必须被类中T的类型约束
public <T> void NumberOf1(T t) {
System.out.println(t);
}
public void NumberOf2(T t) {
System.out.println(t);
}
}
- 受限类型形式参数
- 有时需要约束可以作为“类型实际参数”使用的类型,这就是受限类型形式参数所要发挥的作用。
class ClassName<T extends Number> //T必须是Number的直接或间接子类
- 多重限制:有多重限制的类型变量,是所有列在限制中的类型的子类型。如果一个类是限制之一,则必须一开始就要指定这个类,并且有且只有一个类。且T必须全部继承或实现ABC。
<T extends A & B & C>
- 泛型、继承和子类型
- Integer是Number的子类型,所以Integer类型可以赋值给Number类型。但是List<Integer>并不是List<Number>的子类型,所以List<Integer>是不能赋值给List<Number>类型的。也就是说声明中的<>与初始化中的<>必须完全一致,否则编译错误。
- ArrayList<E> implements List<E>,List<E> extends Collection<E>,所以ArrayList是List的子类型,List是Collection的子类型。
//这样是正确的,并且是提倡的写法
Collection<Number> c=new ArrayList<Number>();
//这样是错误的,<>中的类型不同,所以两者并不是父子类关系
Collection<Number> c=new ArrayList<Integer>();
- 泛型类和非泛型类中构造函数都可以是泛型的:
class Method<X>{
public <T> H(T t) {
}
}
//创建该类型
Method<Integer> m=new Method<>("123");
//由此可见,X的类型是Integer,T的类型是String
- 通配符
- ?表示未知类型,被称为通配符。通配符可以在各种情况下使用,可以作为形式参数、域或局部变量的类型,也可以作为返回值类型。但是,通配符不能用于泛型方法调用、泛型类实例创建或者超类的类型实际参数。当使用通配符时,对泛型类的操作不能依赖于类型形式参数。例如:List<?>,不能使用add方法,因为该方法依赖了类型形式参数。
- 上界通配符:可以放宽对变量的限制。
//可以匹配Number类或其子类的列表ArrayList,但是该列表不能做插入或取出操作,只能遍历
public void NumberOf1(ArrayList<? extends Number> a) {
}
- 无界通配符:当一个方法可以用Object类提供的功能来实现时,无界通配符是适用的;若代码使用了泛型类中的方法,而这些方法又是不依赖于类型形式参数(例如list.size()等),那么无界通配符也是适用的。List<Object>和List<?>是不一样的,我们可以向List<Object>中插入一个Object或者Object的任意子类型,但是只能向List<?>中插入null。
- 下界通配符:下界通配符将未知类型限制为某个指定类型或者该类型的超类型。
//这对Integer的父类,例如Number、Object都有效
public static void NumberOf1(Collection<? super Integer> a) {
a.add(1);
}
- 通配符和子类型:
- Java泛型实现原理——类型擦除
- 类型擦除后保留原始类型,字节码中保留的就是原始类型。无论何时定义一个泛型类型,相应的原始类型都会被自动地提供。类型变量被擦除,并使用其限定类型(如果无限定类型用Object)替换。限定类型:<T extends Number>,Number就是其限定类型,如果有则用Number替换。
class Pair<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
class Pair {
private Object value;
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
-
十五、反射与注解
- 反射为Java程序在运行时提供了动态的能力,而注解允许通过一定的方式编写描述类的元数据,这些元数据可以为编译器提供信息,也可以进入字节码文件在运行时使用。
- 反射——Class的使用
- Class类属于java.lang包,不需要使用import语句引入特殊的包就可以使用,其对象代表一个类,携带类的相应信息,主要包括构造器、方法、成员变量等。
- 不可以使用Class对象的构造器来创建Class类对象,只能使用提供的静态工厂方法来获取对象。
- 想要获取一个Class对象不一定要使用forName方法进行加载,直接调用对象的getClass方法来获取对象所在类对应的Class对象。也可以使用类名.Class来获取类对象。
- 精确判断对象类型:instanceof无法精确判断对象类型,因为子类对象可以看作父类类型。而利用反射则可以判断是否是精确的类型。
class Father{}
class Son extends Father()
Son son=new Son();
son instanceof Father //返回true
son.getClass()==Class.for(com.test.Father) //返回false
son.getClass()==Father.class //返回false
- 反射——Field
- Field类的对象代表成员变量,携带成员变量的信息。与Class类类似,一样不可以通过构造器创建Field类的类对象,对象都是通过Class类对象提供的get系列方法获得的。
-
- 反射——Method
- Method类的对象代表一个方法,携带方法的相关信息。
- 不管实际对应方法的返回值为什么类型,都作为Object类型返回。若返回值为基本数据类型,则返回对应封装类的对象。
- obj参数之处要被调用方法所属的对象,若调用静态的方法用null值。
- args之处要被调用方法的参数序列,若方法没有参数则传递空数组——new Object[0],若方法有基本数据类型的参数则使用基本数据类型的封装类对象。
- 反射——Constructor
- Constructor类的对象代表一个构造器,携带构造器的相关信息。
-
- Class类提供的newInstance方法只能调用对应类的无参构造器动态创建对象,若希望调用有参构造器则需要使用Constructor类的newInstance方法。
- 反射与修饰符
- Class、Field、Method、Constructor类都提供了用于获取各自表示类、成员变量、方法和构造器对应修饰符的方法.
- public int getModifiers(); 该方法返回的是整数类型,可以用Modifier的静态常量来表示。
- 利用反射动态创建数组对象
- java.lang.reflect.Array类中提供了动态创建数组的newInstance方法。
-
- 注解
- 声明自己的注解:
@interface <注解名称>{
<注解属性类型> <注解属性名称> [default <默认值>];
java.lang.String last();
}
//使用注解是可以直接使用@<注解名称>(last=“...”)
//此后获取到该注解,可直接调用last方法获取用户输入的属性值
- 确定注解的使用目标:
@Target(ElementType.<使用目标点>)
- 通过反射提取注解信息:
<注解名称> 引用=xxx.getAnnotation(<注解名称>.class);
xxx表示不同的使用目标对应反射类对象的引用。
十六、枚举