目录
2.native关键字(JNI java native interface)
一.Object类
1.介绍
Object 类是所有类的基类,当一个类没有直接继承某个类时,默认继承Object类
2.native关键字(JNI java native interface)
问题:为什么要用 native 来修饰方法,这样做有什么用?
答:native 用来修饰方法,Java 调用非 Java 代码的接口。
3.equals方法
在Object类中,== 运算符和 equals 方法是等价的,都是比较两个对象的引用是否相等。
String类重写了Object的equals方法,String中的equals方法是判断字符串的内容是否相同。
Object中equals方法源码如下
public boolean equals(Object obj) {
return (this == obj);
}
4.clone方法
源码
protected native Object clone() throws CloneNotSupportedException;
保护方法,实现对象的浅拷贝,只有实现了Cloneable接口才可以调用该方法,否则抛出 CloneNotSupportedException异常。
5.finalize 方法
源码
protected void finalize() throws Throwable { }
当 GC 确定不再有对该对象的引用时,GC 会调用对象的 finalize() 方法来清除回收。
二.ArrayList类
1.ArryayList源码字段属性
//集合的默认大小
private static final int DEFAULT_CAPACITY = 10;
//空的数组实例
private static final Object[] EMPTY_ELEMENTDATA = {};
//这也是一个空的数组实例,和EMPTY_ELEMENTDATA空数组相比是用于了解添加元素时数组膨胀多少
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存储 ArrayList集合的元素,集合的长度即这个数组的长度
//1、当 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 时将会清空ArrayList
//2、当添加第一个元素时,elementData 长度会扩展为 DEFAULT_CAPACITY=10
transient Object[] elementData;
//表示集合的长度
private int size;
2.无参构造
此无参构造函数将创建一个 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 声明的数组,注意此时初始容量是0,而不是大家以为的 10。根据默认构造函数创建的集合,ArrayList list = new ArrayList();此时集合长度是0,在添加一个元素时,数组才会扩容为10
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
3.有参构造1
初始化集合大小创建 ArrayList 集合。当大于0时,给定多少那就创建多大的数组;当等于0时,创建一个空数组;当小于0时,抛出异常。
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);
}
}
4.有参构造2
将已有的集合复制到 ArrayList 集合中
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
5.添加元素
add(E e);
源码
public boolean add(E e) {
//添加元素之前,首先要确定集合的大小(是否需要扩容)
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
6.删除元素
remove(int index);
7.修改元素
set(int index, E e);
8.查找元素
get(int index)
三.LinkedList类
1.LinkedList定义
LinkedList是一种可以在任何位置进行高效地插入和移除操作的有序序列,它是基于双向链表实现的
2.LinkedList源码字段属性
//链表元素(节点)的个数
transient int size = 0;
/**
*指向第一个节点的指针
*/
transient Node<E> first;
/**
*指向最后一个节点的指针
*/
transient Node<E> last;
Node 类,这是 LinkedList 类中的一个内部类,其中每一个元素就代表一个 Node 类对象,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;
}
}
3.构造函数
public LinkedList() {
}
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
LinkedList 有两个构造函数,第一个是默认的空的构造函数,第二个是将已有元素的集合Collection 的 实例添加到 LinkedList 中,调用的是 addAll() 方法
注意:LinkedList 是没有初始化链表大小的构造函数,因为链表不像数组,一个定义好的数组是必 须要有确定的大小,然后去分配内存空间,而链表不一样,它没有确定的大小,通过指针的移动来指向 下一个内存地址的分配。
4.添加元素
(1)将指定元素添加到链表头
addFirst(E e);
(2)将指定元素添加到链表尾,无返回值
addLast(E e)
(3)将指定元素添加到链表尾,返回boolean
add(E e)
(4)将指定的元素插入此列表中的指定位置
add(int index, E element)
(5)按照指定集合的迭代器返回的顺序,将指定集合中的所有元素追加到此列表的末尾
addAll(Collection<? extends E> c)
5.修改元素
(1)用指定的元素替换此列表中指定位置的元素
set(int index, E element)
6.查找元素
(1)返回此列表中的第一个元素
getFirst()
(2)返回此列表中的最后一个元素
getLast()
(3)返回指定索引处的元素
get(int index)
(4)返回此列表中指定元素第一次出现的索引,如果此列表不包含元素,则返回-1
indexOf(Object o)
7.删除元素
(1)移除并返回第一个元素
remove()和removeFirst()
(2)中删除并返回最后一个元素
removeLast()
(3)删除指定位置的元素,并返回该元素
remove(int index)
8.ArrayList和LinkedList的区别
(1)ArrayList是数组实现的集合操作,而LinkedList是链表实现的集合操作
(2)对于大数据量的查询使用get(index)方法,ArrayList复杂度为O(1),LinkedList为O(n)
四.HashMap类
* HashMap底层数据结构(为什么引入红黑树、存储数据的过程、哈希碰撞相关问题)
* HashMap成员变量(初始化容量是多少、负载因子、数组长度为什么是2的n次幂)
* HashMap扩容机制(什么时候需要扩容? 怎么进行扩容?)
* JDK7 与 Jdk8比较,JDK8进行了什么优化
1.定义
(1)HashMap基于哈希表的Map接口实现以key-value存储形式存在。
(2)HashMap的实现不是同步的,它不是线程安全的。它的key、value都可以为null。
(3)HashMap中的映射不是有序的
JDK1.7 HashMap数据结构:数组 + 链表
JDK1.8 HashMap数据结构:数组 + 链表 / 红黑树
2.哈希表(Hash表)
(1)定义
Hash表是一种根据关键字值(key - value)而直接进行访问的数据结构,在链表、数组等数据结构中,查找某个关键字,通常要遍历整个数据结构,也就是O(N)的复杂度,但是对于哈希表来说,只是O(1)复杂度。
(2)散列函数
哈希表是通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表,只需要O(1)的时间级。
(3)多个 key 通过散列函数会得到相同的值,这时候怎么办?
链地址法:相同的值挂到链表下
如果数据量大,会造成子链表很长,那么我们查找所需遍历的时间也会很长。
3.JDK1.8以前HashMap数据结构
JDK 8 以前 HashMap 的实现是 数组+链表,即使哈希函数取得再好,也很难达到元素百分百均匀分布。
当 HashMap 中有大量的元素都存放到同一个桶中时,这个桶下有一条长长的链表,极端情况HashMap 就相当于一个单链表,假如单链表有 n 个元素,遍历的时间复杂度就是 O(n),完全失去了它的优势。
4.JDK1.8以后HashMap数据结构
JDK 8 后 HashMap 的实现是 数组+链表+红黑树
桶中的结构可能是链表,也可能是红黑树,当链表长度大于阈值(默认为8)并且当前数组的长度大于64时,此时此索引位置上的所有数据改为使用红黑树存储。
5.HashMap源码字段属性
//序列化和反序列化时,通过该字段进行版本一致性验证
private static final long serialVersionUID = 362498820763181265L;
//默认 HashMap 集合初始容量为16(必须是 2 的倍数)
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
//集合的最大容量,如果通过带参构造指定的最大容量超过此数,默认还是使用此数
static final int MAXIMUM_CAPACITY = 1 << 30;
//默认的填充因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//当桶(bucket)上的结点数大于这个值时会转成红黑树(JDK1.8新增)
static final int TREEIFY_THRESHOLD = 8;
//当桶(bucket)上的节点数小于这个值时会转成链表(JDK1.8新增)
static final int UNTREEIFY_THRESHOLD = 6;
/**(JDK1.8新增)
* 当集合中的容量大于这个值时,表中的桶才能进行树形化 ,否则桶内元素太多时会扩容,
* 而不是树形化 为了避免进行扩容、树形化选择的冲突,这个值不能小于 4 *
TREEIFY_THRESHOLD
*/
static final int MIN_TREEIFY_CAPACITY = 64;
/**
* 初始化使用,长度总是 2的幂
*/
transient Node<K,V>[] table;
/**
* 保存缓存的entrySet()
*/
transient Set<Map.Entry<K,V>> entrySet;
/**
* 此映射中包含的键值映射的数量。(集合存储键值对的数量)
*/
transient int size;
/**
* 跟前面ArrayList和LinkedList集合中的字段modCount一样,记录集合被修改的次数
* 主要用于迭代器中的快速失败
*/
transient int modCount;
/**
* 调整大小的下一个大小值(容量*加载因子)。capacity * load factor
*/
int threshold;
/**
* 散列表的加载因子。
*/
final float loadFactor;
6.添加元素
put(key,value);
7.删除元素
remove(key);
8.查找元素
get(key);
9.扩容机制
我们知道集合是由数组+链表+红黑树构成,向 HashMap 中插入元素时,如果HashMap 集合的元素已经大于了最大承载容量,那么必须扩大数组的长度,Java中数组是无法自动扩容的,我们采用的方法是用一个更大的数组代替这个小的数组,就好比以前是用小桶装水,现在小桶装不下了,我们使用一个更大的桶。1.8使用的是2次幂的扩展(指长度扩为原来2倍)。
五.ConcurrentHashMap类
1. ConcurrentHashMap 1.7
(1)存储结构
在JDK1.7中ConcurrentHashMap采用了数组+分段锁的方式实现, Segment 的个数一旦初始化就不能改变,Segment 的个数是 16 个,所以可以认为 ConcurrentHashMap 默认支持最多 16 个线程并发。
Segment(分段锁):是为了减少锁的粒度。
2.ConcurrentHashMap 1.8
(1)存储结构
在JDK1.8中ConcurrentHashMap采用了 Node 数组 + 链表 / 红黑树 。当冲突链表达到一定长度时,链表会转换成红黑树。
使用的 Synchronized 锁加 CAS 的机制保证线程安全。
六.Synchronized类
1.作用
synchronized的作用是保证在同一时刻, 被修饰的代码块或方法只会有一个线程执行,以达到保证并发安全的效果。
2.Synchronized用法
从语法上讲,Synchronized可以把任何一个非null对象作为"锁",在HotSpot JVM实现中,锁有个专门 的名字:对象监视器(Object Monitor)。可以修饰普通方法,静态方法,
(1)修饰普通方法(锁对象是this,使用同一个类的两个对象去访问不是同一把锁)
public synchronized void run() {
System.out.println(1);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(2);
}
使用同一对象访问,结果是同步的
Demo demo = new Demo();
new Thread(() -> demo.run()).start();
new Thread(() -> demo.run()).start();
//结果如下
1
2
1
2
使用不同对象访问,结果是异步的
Demo demo = new Demo();
new Thread(() -> demo.run()).start();
Demo demo2 = new Demo();
new Thread(() -> demo2.run()).start();
//结果如下
1
1
2
2
(2)修饰静态方法(锁对象是当前类的class字节码文件,使用同一个类的两个对象去访问是同一把锁)
public static synchronized void run() {
System.out.println(1);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(2);
}
使用不同的对象访问,结果是同步的
Demo demo = new Demo();
new Thread(() -> demo.run()).start();
Demo demo2 = new Demo();
new Thread(() -> demo2.run()).start();
//结果如下
1
2
1
2