1. 手写ArrayList
1.1 ArrayList 底层原理细节
-
底层结构是一个长度可以动态增长的数组(顺序表)
transient Object[] elementData;
数组特点:在内存中分配连续的空间,只存储数据,不存储地址信息
- 节省存储空间(无需存储地址和数据间的关系)、索引查询效率高(其它地址可以通过首地址计算得到)
- 插入和删除效率低,必须提前分配固定数量的空间,可能会导致空闲浪费,按照内容查询效率低
-
通过无参构造方法,创建对象时,JDK1.7初始长度10 ,JDK1.8初始长度为0 ,在第一次添加元素时就进行扩容(每次扩容1.5倍) -
提供了一个内部类 Itr ,实现了 Iterator 接口,用来对 ArrrayList 进行遍历
1.2 自定义:List、Iterator、异常处理
List
public interface List {
int size();
Object get(int i);
boolean isEmpty();
boolean contains(Object e);
int indexOf(Object e);
void add(int i, Object e);
void add(Object e);
Object remove(int i);
boolean remove(Object e);
Object replace(int i, Object e);
Iterator iterator();
}
Iterator
public interface Iterator<T> {
boolean hasNext();
T next();
}
IndexOutOfBoundsException
public class IndexOutOfBoundsException extends RuntimeException{
public IndexOutOfBoundsException() {
}
public IndexOutOfBoundsException(String message) {
super(message);
}
}
1.3 自定义:测试类
public class TestArrayList {
public static void main(String[] args) {
List list = new ArrayList();
list.add("1111");
list.add("aaaa");
list.add("bbbb");
list.add("3333");
list.add("2222");
list.add("1111");
list.add("aaaa");
list.add("bbbb");
list.add("3333");
list.add("2222");
list.add("1111");
list.add(3, "AAAA");
System.out.println(list.size());
System.out.println(list.isEmpty());
System.out.println(list.get(2));
System.out.println(list.contains("4444"));
System.out.println(list.indexOf("2222"));
System.out.println(list.indexOf(null));
System.out.println(list.toString());
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
System.out.println(iterator.next());
}
}
1.4 自定义:ArrayList
最重要的扩容底层:
ArrayList类
package com.lwclick.list;
import java.util.Arrays;
public class ArrayList implements List{
private transient Object[] elementData;
private int size;
public ArrayList() {
this(10);
}
public ArrayList(int initialCapacity) {
elementData = new Object[initialCapacity];
}
@Override
public int size() {
return size;
}
@Override
public Object get(int i) {
if (i >= size || i < 0) {
throw new IndexOutOfBoundsException("数组索引越界" + i);
}
return elementData[i];
}
@Override
public boolean isEmpty() {
return size == 0;
}
@Override
public boolean contains(Object e) {
return indexOf(e) >= 0;
}
@Override
public int indexOf(Object e) {
int index = -1;
if (e == null) {
for (int i = 0; i < size; i++) {
if (e == elementData[i]) {
return i;
}
}
} else {
for (int i = 0; i < size; i++) {
if (e.equals(elementData[i])) {
return i;
}
}
}
return index;
}
@Override
public void add(int index, Object e) {
if (elementData.length == size) {
grow();
}
for (int i = size; i > index; i--) {
elementData[i] = elementData[i - 1];
}
elementData[index] = e;
size++;
}
@Override
public void add(Object e) {
if (elementData.length == size) {
grow();
}
elementData[size] = e;
size++;
}
private void grow() {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
Object[] newArr = new Object[newCapacity];
for (int i = 0; i < oldCapacity; i++) {
newArr[i] = elementData[i];
}
elementData = newArr;
}
@Override
public Object remove(int index) {
if (index >= size || index < 0) {
throw new IndexOutOfBoundsException("数组索引越界" + index);
}
Object e = elementData[index];
int numMoved = size - index - 1;
if (numMoved > 0) {
System.arraycopy(elementData, index + 1, elementData, index, numMoved);
}
elementData[--size] = null;
return e;
}
@Override
public boolean remove(Object e) {
if (e == null) {
for (int i = 0; i < size; i++) {
if (e == elementData[i]) {
fastRemove(i);
return true;
}
}
} else {
for (int i = 0; i < size; i++) {
if (e.equals(elementData[i])) {
fastRemove(i);
return true;
}
}
}
return false;
}
private void fastRemove(int index) {
int numMoved = size - index - 1;
if (numMoved > 0) {
System.arraycopy(elementData, index + 1, elementData, index, numMoved);
}
elementData[--size] = null;
}
@Override
public Object replace(int index, Object e) {
if (index >= size || index < 0) {
throw new IndexOutOfBoundsException("数组索引越界" + index);
}
if (e == null) {
if (e != elementData[index]) {
Object oldValue = elementData[index];
elementData[index] = e;
return oldValue;
}
} else {
if (!e.equals(elementData[index])) {
Object oldValue = elementData[index];
elementData[index] = e;
return oldValue;
}
}
return this;
}
@Override
public Iterator iterator() {
return new Itr();
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("[");
for (int i = 0; i < size; i++) {
builder.append(elementData[i] + ",");
}
if (size > 0) {
builder.deleteCharAt(builder.length() - 1);
}
builder.append("]");
return builder.toString();
}
private class Itr<T> implements Iterator<T> {
int cursor = 0;
@Override
public boolean hasNext() {
return cursor != size;
}
@Override
public T next() {
if (cursor >= size) {
throw new RuntimeException("元素已经遍历结束");
}
return (T)elementData[cursor++];
}
}
}
2. 单链表
2.1 单链表技能点
- 存储空间不连续
- 每个结点对应一个数据元素,结点由指针域和数据域组成
- 元素的逻辑关系通过结点间的链接关系体现,逻辑上相邻,物理上不一定相邻
- 按索引查询慢,存储空间大
- 插入、删除速度快
- 有元素才会分配空间,不会有闲置的结点
注:为了对空表、非空表的情况以及首元结点进行统一处理,常增加一个头结点
2.2 手写单链表
测试代码:
public static void main(String[] args) {
List list = new SingleLinkedList();
list.add("11111");
list.add("aaaaa");
list.add("bbbbb");
list.add("33333");
list.add("22222");
list.add(3, "AAAAA");
System.out.println(list.size());
System.out.println(list.isEmpty());
System.out.println(list.get(2));
System.out.println(list.contains("44444"));
System.out.println(list.indexOf("22222"));
System.out.println(list.toString());
}
add 操作及 get 操作的底层思想:
List类:
public interface List {
int size();
Object get(int i);
boolean isEmpty();
boolean contains(Object e);
int indexOf(Object e);
void add(int i, Object e);
void add(Object e);
boolean addBefore(Object obj, Object e);
boolean addAfter(Object obj, Object e);
Object remove(int i);
boolean remove(Object e);
Object replace(int i, Object e);
}
Node类:
public class Node {
Object data;
Node next;
public Node() {
}
public Node(Object data, Node next) {
this.data = data;
this.next = next;
}
public Node(Object data) {
this.data = data;
}
}
单链表 SingleLinkedList 类 :
public class SingleLinkedList implements List {
private Node head = new Node();
private int size;
@Override
public int size() {
return size;
}
@Override
public Node get(int index) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("索引越界:" + index);
}
Node p = head;
for (int i = 0; i <= index; i++) {
p = p.next;
}
return p;
}
@Override
public boolean isEmpty() {
return size == 0;
}
@Override
public void add(int index, Object e) {
if (index < 0 || index > size) {
throw new IndexOutOfBoundsException("索引越界" + index);
}
Node p = head;
if (index > 0) {
p = get(index - 1);
}
Node node = new Node(e);
node.next = p.next;
p.next = node;
size++;
}
@Override
public void add(Object e) {
add(size, e);
}
@Override
public Object remove(int index) {
if (index < 0 || index > size) {
throw new IndexOutOfBoundsException("索引越界" + index);
}
Node p = head;
if (index > 0) {
p = get(index - 1);
}
Node currentNode = p.next;
p.next = p.next.next;
currentNode.next = null;
size--;
return null;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("[");
Node p = head;
for (int i = 0; i < size; i++) {
p = p.next;
builder.append(p.data + ",");
}
if (size != 0) {
builder.charAt(builder.length() - 1);
}
return builder.toString();
}
}
3. LinkedList
3.1 底层原理
底层是一个双向链表(添加、删除元素效率高,按索引查询效率低)
同时实现了 List、Deque、Queue接口,所以可以当作线性表、队列、双端队列、栈来用
不存在容量不足的问题
3.2 手写LinkedList
LinkedList类 :
public class LinkedList implements List {
transient int size = 0;
transient Node first;
transient Node last;
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
public Node(Node<E> prev, E item, Node<E> next) {
this.item = item;
this.next = next;
this.prev = prev;
}
@Override
public String toString() {
return "Node{item=" + item + ", next=" + next + '}';
}
}
@Override
public int size() {
return size;
}
@Override
public Object get(int i) {
Node node = node(i);
return node.item;
}
@Override
public boolean isEmpty() {
return size == 0;
}
@Override
public void add(int i, Object e) {
if (i == size) {
linkLast(e);
} else {
linkBefore(e, node(i));
}
}
private Node node(int index) {
if (index < (size >> 1)) {
Node p = first;
for (int i = 0; i < index; i++)
p = p.next;
return p;
} else {
Node l = last;
for (int i = size - 1; i > index; i--)
l = l.prev;
return l;
}
}
@Override
public void add(Object e) {
linkLast(e);
}
private void linkLast(Object e) {
final Node l = last;
final Node newNode = new Node(l, e, null);
last = newNode;
if (l == null) {
first = newNode;
} else {
l.next = newNode;
}
size++;
}
private void linkBefore(Object e, Node succ) {
final Node pred = succ.prev;
final Node newNode = new Node(pred, e, succ);
succ.prev = newNode;
if (pred == null) {
first = newNode;
} else {
pred.next = newNode;
}
size++;
}
}
分析:linkLast 添加结点时的内存分配图
4. 手写HashMap
4.1 底层原理
-
底层结构是哈希表,采用了 顺序表+链表 结合结构,同一个链表上的所有元素的存储地址都是相同的,是发生冲突的元素 -
链表上每个节点就是一个 Entry,字段包括四部分 同一个链表上节点的哈希码可能不同,存储地址(主数组的索引)相同 -
哈希表的优点:
- 添加快、查询快(通过计算得到存储位置,而不是比较)
- 无序,key 唯一
-
HashMap 关键参数
4.2 手写 HashMap
Map接口
public interface Map {
void put(Object key, Object value);
Object get(Object key);
int size();
boolean isEmpty();
interface Entry {
Object getKey();
Object getValue();
}
}
TestMap测试类
public static void main(String[] args) {
Map map = new HashMap();
map.put(23,"China");
map.put(36,"Japan");
map.put(48,"America");
map.put(86,"the United States");
map.put(67,"France");
map.put(23,"Italian");
map.put(47,"England");
System.out.println(map.size());
System.out.println(Arrays.toString(map.table));
System.out.println(map);
}
HashMap 类
public class HashMap implements Map{
transient Entry[] table;
transient int size = 0;
public HashMap() {
this(16);
}
public HashMap(int capacity) {
table = new Entry[capacity];
}
class Entry implements Map.Entry {
final Object key;
Object value;
Entry next;
int hash;
public Entry(int hash, Object key, Object value, Entry next) {
this.key = key;
this.value = value;
this.next = next;
this.hash = hash;
}
@Override
public Object getKey() {
return key;
}
@Override
public Object getValue() {
return value;
}
@Override
public String toString() {
if (next != null) {
return "Entry{" +
"key=" + key +
", value=" + value +
", next=" + next +
", hash=" + hash +
'}';
} else {
return "Entry{" +
"key=" + key +
", value=" + value +
", hash=" + hash +
'}';
}
}
}
@Override
public void put(Object key, Object value) {
int hash = key.hashCode();
int index = hash % table.length;
if (table[index] == null) {
table[index] = new Entry(hash, key, value, null);
size++;
} else {
Entry entry = table[index];
while (entry != null) {
if (entry.hash == hash && (entry.key == key || key.equals(entry.key))) {
entry.value = value;
return;
}
entry = entry.next;
}
Entry firstEntry = table[index];
table[index] = new Entry(hash, key, value, firstEntry);
size++;
}
}
@Override
public Object get(Object key) {
int hash = key.hashCode();
int index = hash % table.length;
Entry entry = null;
if (table[index] != null) {
Entry currentEntry = table[index];
while (currentEntry != null) {
if (currentEntry.hash == hash && (currentEntry.key == key || key.equals(currentEntry.key))) {
entry = currentEntry;
break;
}
currentEntry = currentEntry.next;
}
}
return entry == null ? null : entry.getValue();
}
@Override
public int size() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("{");
for (int i = 0; i < table.length; i++) {
if (table[i] != null) {
Entry entry = table[i];
do {
builder.append(entry.key + "=" + entry.value + ",");
entry = entry.next;
} while (entry != null);
}
}
if (size > 0) {
builder.deleteCharAt(builder.length() - 1);
}
builder.append("}");
return builder.toString();
}
}
5. 手写 HashSet
- HashSet 底层就是 HashMap,哈希表的每个 Entry 的key是 HashSet 中的每个元素,而 Entry 的value统一为 new Object()
Set接口
public interface Set {
int size();
void add(Object obj);
boolean isEmpty();
boolean contains(Object obj);
}
TestHashSet测试类
public static void main(String[] args) {
HashSet set = new HashSet();
set.add("JavaSE");
set.add("MySQL");
set.add("JavaScript");
set.add("JavaSE");
System.out.println(set.size());
System.out.println(set.contains("JavaSE"));
System.out.println(set.contains("JavaSe"));
System.out.println(set);
}
HashSet类
public class HashSet implements Set {
private HashMap map;
private static final Object PRESENT = new Object();
public HashSet() {
map = new HashMap<>();
}
@Override
public int size() {
return map.size();
}
@Override
public void add(Object obj) {
map.put(obj, PRESENT);
}
@Override
public boolean isEmpty() {
return map.isEmpty();
}
@Override
public boolean contains(Object obj) {
return map.get(obj) != null;
}
}
6. 新一代并发集合类
6.1 三代集合类发展过程
-
第一代线程安全集合类 Vector、HashTable(使用 synchronized 修饰方法) -
第二代线程非安全集合类(主流) ArrayList、HashMap(线程不安全,但是性能好) 如果要保证线程安全 ==》 使用 Collections.synchronizedList(list) ,Collections.synchronizedMap(m) (底层使用 synchronized 代码块锁,但是锁在方法里面) -
第三代线程安全集合类 java.util.concurrent.*; ConcurrentHashMap CopyOnWriteArrayList CopyOnWriteArraySet (注意:底层为 list) 底层大都采用 Lock锁(JDK1.8的 ConcurrentHashMap 不使用 Lock锁)
代码说明:
public static void main(String[] args) {
Vector v = new Vector();
List list = new ArrayList();
list = Collections.synchronizedList(list);
ConcurrentHashMap chm;
CopyOnWriteArrayList cwal;
}
6.2 新一代并发集合类
-
ConcurrentHashMap:分段(segment)锁定 + Lock锁 使用多个锁来控制对 hash 表的不同部分(段 segment)进行的修改,采用ReentrantLock 锁来实现,如果多个操作发生在不同的段上,它们就可以并发的进行 JDK1.8中 :摒弃了Segment的概念,采用了 volatile + CAS 实现无锁化操作(底层由 数组+链表+红黑树 的方式,锁哈希表的链表中的首元素) -
CopyOnWriteArrayList: CopyOnWrite + Lock 锁 对于写操作(set()、add()、remove()等 每个操作都复制一份数组,修改完成后改变数组的引用 )使用 ReentrantLock 的 lock 和 unlock 来加锁和解锁,读操作不需要加锁 只能保证数据的最终一致性,不能保证数据的实时一致性(读的是旧数组中的元素) -
CopyOnWriteArraySet 与 HashMap 及 HashSet 的方式类似,只是底层结构变为了数组,所以与 ArrayList 性质类似 但是ArrayList 要求元素有序、不唯一,而 CopyOnWriteArraySet 要求元素顺序无所谓,唯一,所以在添加元素时,需要先判断是否已经有了元素
|