###.java核心内容
1.集合
2.泛型
3.反射
4.注解
1.数据结构
1.数组
数组的三种定义方式,和数组容易出现的异常,空指针的数组下标越界
特点:
内存地址连续,使用之前必须要指定长度
可以通过下标访问的方式访问资源,查询效率比较高,
增删效率低,需要复制数组,源码可见
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;}
可见,在增加操作的时候,会先对数组的大小扩容判断,然后再创建一个比原来容器大小加1的数组存放数据
2.链表
单向链表和双向链表
双向链表
特点:
1.灵活的空间要求,没有要求存储空间的地址连续
2.不能通过下标访问,只能顺序便利,一个一个的读取
3.因为是连续的,对于头尾的数据增删效率比较高
数据结构
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;
}
}
3.树
平衡二叉树
特点:
数据按照从低到高,从左到右依次排列
左节点深度和右节点深度一样
红黑树
1.根节点必须是黑色
2.每个叶子节点是黑色(其中每个叶子节点包含两个空节点,一般未显示)
3.红色节点的两个子节点必须是黑色
4.任意根节点到每个叶子节点的路劲包含相同数量的黑节点
是一个黑平衡二叉树
1.recolor 重新标志节点为红色或者黑色
2.rotation旋转实现平衡(树达到平衡的关键)
首先约定插入的新节点的颜色都为红色。然后将该节点插入的按二叉查找树的规则插入到树中。这个节点后文称为N
\1. 根节点为空。这种情况,将N的颜色改为黑色即可。
\2. N的父节点为黑色。这种情况不需要做修改。
\3. N的父节点为红色(根据性质3,N的祖父节点必为黑色)。
(1)N的叔父节点为红色。这种情况,将N的父节点和叔父节点的颜色都改为黑色,若祖父节点是跟节点就将其改为黑色,否则将其颜色改为红色,并以祖父节点为插入的目标节点从情况1开始递归检测。
-
(2)N的叔父节点为黑色, 且N和N的父节点在同一边(即父节点为祖父的左儿子时,N也是父节点的左儿子。父节点为祖父节点的右儿子时。N也是父节点的右儿子)。以父节点为祖父节的左儿子为例,将父节点改为黑色,祖父节点改为红色,然后以祖父节点为基准右旋。(N为父节点右儿子时做相应的左旋。)
(3)N的叔父节点为黑色,切N和N的父节点不在同一边(即父节点为祖父的左儿子时,N是父节点的右儿子。父节点为祖父节点的右儿子时。N也是父节点左右儿子)。以父节点为祖父节点的左儿子为例。以父节点为基准,进行左旋,然后以父节点为目标插入节点进入情况3的b情况进行操作
2.集合
Collection接口
map接口
iterator迭代器
工具类:
collections
Arrays
比较器
comparable compartor
ArrayList数组源码分析
构造方法:
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);
}
}
add方法:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//return 1 or 10
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++; //操作次数,自增
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
//复制数组和容器大小,返回一个新的满足条件的数组
}
上面一段代码建议自己阅读源码,根据方法名称了解每个方法的作用
get方法
public E get(int index) {
//判断下标是否越界
rangeCheck(index);
//通过下标返回对应数据
return elementData(index);
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
set方法
public E set(int index, E element) {
rangeCheck(index);
//获取下标原来的值,赋值新的值,返回原来的值
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
remove方法
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
//获取要移动的元素个数
int numMoved = size - index - 1;
if (numMoved > 0)
//操作原来的数组,将需要移除的下标之后的数据,整体复制替换掉从要移除下标开始的后面的数据,并将最后一个下标位置的数据置换为Null
例如:
{1,2,3,4,5}
这里移除掉第三个位置的数据
{1,2,4,5,null}
变换成这种形式
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
linkedList
通过双向链表实现,数据结构具有双向链表的优缺点,既然是双向链表,他的顺序访问效率很高,随机访问效率比较低,包含一个非常重要的私有内部静态内: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;
}
}
push方法:
list2.push("1");
private void linkFirst(E e) {
final Node<E> f = first;
//创建一个新的节点,e表示元素,null表示头结点,f表示尾节点(上一次插入前获取的头结点)
final Node<E> newNode = new Node<>(null, e, f);
//将新创建的节点赋值给第一个节点
first = newNode;
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
add方法
list2.add("2");
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
get方法
list2.get(1);
public E get(int index) {
//检查Index是否合法
checkElementIndex(index);
//返回索引位置的元素(内部涉及便利操作)
return node(index).item;
}
set方法
list2.set(1,"4");
public E set(int index, E element) {
checkElementIndex(index);
//获取到原来索引的值并返回
Node<E> x = node(index);
E oldVal = x.item;
//将新的值赋值给索引位置的节点
x.item = element;
return oldVal;
}
Vector
和ArrayList很类似,都是以动态数组的形式来存储数据,Vector是线程安全的
因为每个方法都加synchronized锁,性能较差
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
使用Collections工具类替换
List<String> stringList = Collections.synchronizedList(list);
public static <T> List<T> synchronizedList(List<T> list) {
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList<>(list) :
new SynchronizedList<>(list));
}
本质上通过添加如下代码,实现对list操作的同步安全
final List<E> list;
public E get(int index) {
synchronized (mutex) {return list.get(index);}
}
public E set(int index, E element) {
synchronized (mutex) {return list.set(index, element);}
}
public void add(int index, E element) {
synchronized (mutex) {list.add(index, element);}
}
public E remove(int index) {
synchronized (mutex) {return list.remove(index);}
}
Set接口
1.HashSet
HashSet实现了set接口,由哈希表支持,不保证set的迭代顺序,但是保证数据有序,可以为Null
HashSet hashSet = new HashSet();
public HashSet() {
map = new HashMap<>();
}
hashset本质上是一个hashMap
add方法
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
private static final Object PRESENT = new Object();
本质上是将数据保存在hashmap中,key是我们添加的内从容,value及时我们定义的一个object对象,底层数据结构是哈希表,hashset的本质是一个没有重复元素的集合。
2.treeset
基于treemap的实现,使用元素的自然顺序对元素进行排序,
TreeSet treeSet = new TreeSet();
public TreeSet() {
this(new TreeMap<E,Object>());
}
public TreeMap() {
comparator = null;
}
private transient NavigableMap<E,Object> m;
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
add方法
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
本质上是将数据保存在treeMap中,key是我们添加到内容,value是我们定义的object对象。
Map接口
map集合的特点:
1.能够存储唯一列的数据
2.可以存储可以重复的数据
3.值得顺序取决于键的顺序
4.键和值都是可以存储null元素
TreeMap
本质上是一个红黑树的实现,红黑树特点具体见上面
HashMap
TreeMap treeMap = new TreeMap();
//treemap中存在root成员变量,其本质就是一个红黑树
private transient Entry<K,V> root;
K key;
V value;
Entry<K,V> left;//左子节点
Entry<K,V> right;//右子节点
Entry<K,V> parent;//父节点
boolean color = BLACK;//默认黑色
put方法:
treeMap.put(1,"2");
源码如下:
public V put(K key, V value) {
//根节点赋值给t
Entry<K,V> t = root;
//判断初始化第一次插入,比较值得大小
if (t == null) {
compare(key, key); // type (and possibly null) check
//创建一个entry对象并复制给root根节点
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
//不是第一次插入
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
if (cpr != null) {
//开始循环
do {
parent = t;//将root根节点赋值给parent
cmp = cpr.compare(key, t.key);//比较两个值得大小(key决定了元素存放的位置,后再hashmap中会讲到)
if (cmp < 0)
t = t.left;//如果key小于根节点的key,存放在左侧
else if (cmp > 0)
t = t.right;//如果key大于根节点的key,存放在右侧
else
return t.setValue(value);//否则覆盖当前位置的值
} while (t != null);
}
else {
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
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对象,将父节点的值赋值给e
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;
}
红黑树平衡方法:fixAfterInsertion
private void fixAfterInsertion(Entry<K,V> x) {
//插入节点默认给红色
x.color = RED;
//判断插入节点不为空,不为根节点,颜色不为黑色
while (x != null && x != root && x.parent.color == RED) {
//如果父节点在祖父节点的左侧
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
Entry<K,V> y = rightOf(parentOf(parentOf(x)));
如果父节点是红色
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
//直接插入,并将祖父节点设置为插入节点,继续判断
x = parentOf(parentOf(x));
} else {
//如果插入节点再父节点的右侧
if (x == rightOf(parentOf(x))) {
x = parentOf(x);
需要插入后左旋
rotateLeft(x);
}
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
//再右旋
rotateRight(parentOf(parentOf(x)));
}
} else {
//如果父节点在祖父节点的右侧
Entry<K,V> y = leftOf(parentOf(parentOf(x)));
//如果父节点是红色,一次类推
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
if (x == leftOf(parentOf(x))) {
x = parentOf(x);
rotateRight(x);
}
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateLeft(parentOf(parentOf(x)));
}
}
}
root.color = BLACK;
}