java中关于数据结构的工具类,暂时就这么称呼吧,其实也贼拉多,害看图包
上面的图肯定时不全,知识一些常用的。简单看一下上面列举出来的“工具类”的源码和一些常见的面试问题:
List接口中的“工具类”,是一个有序集合,可以重复。
ArrayList:底层使用数组实现的,数据是有序数据(插入的顺序),具有索引查找比较快,线程不安全。
属性:
//默认容量
private static final int DEFAULT_CAPACITY = 10;
//空集合
private static final Object[] EMPTY_ELEMENTDATA = {};
//默认空集合
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//数据存储数组,不参与序列化
transient Object[] elementData;
//容量
private int size;
//最大容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
构造方法:
/*
*指定长度的构造方法
*/
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 ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/*
*将其他Collection转化为ArrayList的构造方法
*/
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
this.elementData = EMPTY_ELEMENTDATA;
}
}
增删改查的方法:
/*
*查找方法
*/
public E get(int index) {
//检查index是否非法
rangeCheck(index);
return elementData(index);
}
/*
*更新方法
*/
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
//如果index非法抛出异常
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
//获取指定index的元素
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
/*
*添加方法
*/
public boolean add(E e) {
//检查容量是否够
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
/*
*下面这些是首先检查是不是空数组添加元素,如果空数组添加元素,那么初始这个数组长度为默认值
*如果并不是空数组,先检查扩容后会不会超过最大容量,如果没有那么进行扩容1.5倍
*/
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//判断是不是空数组
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);
}
//检测扩容后会不会超过最大值
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
/*
*删除函数(通过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);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
/*
*删除函数(通过对象来删除)
*查找到object对应的index
*下面的函数主要是通过fastRemove()来删除的
*/
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
//真正执行删除的函数,与通过index删除的index相同
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
/*
*删除范围
*/
protected void removeRange(int fromIndex, int toIndex) {
modCount++;
int numMoved = size - toIndex;
System.arraycopy(elementData, toIndex, elementData, fromIndex,
numMoved);
// clear to let GC do its work
int newSize = size - (toIndex-fromIndex);
for (int i = newSize; i < size; i++) {
elementData[i] = null;
}
size = newSize;
}
LinkedList:底层采用双向链表实现,其实也可以当作堆或者栈来使用,线程不安全。
//属性:
//长度
transient int size = 0;
//首节点
transient Node<E> first;
//尾节点
transient Node<E> last;
//节点(采用的双向链表)
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;
}
}
//构造方法
//空构造方法
public LinkedList() {}
//插入函数
/*
*头节点插入
*/
public void addFirst(E e) {
linkFirst(e);
}
private void linkFirst(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
//判断是否是第一个节点
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
/*
*尾节点插入
*/
public void addLast(E e) {
linkLast(e);
}
public boolean add(E e) {
linkLast(e);
return true;
}
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++;
}
//通过头插和尾插可以看出。linkList中没有头元素(一个空元素起点)
//删除函数
/*
*循环找到待删除的元素,源码中对待删除的元素是否为空进行了判断
*/
public boolean remove(Object o) {
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
final Node<E> next = x.next;
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--;
modCount++;
return element;
}
//删除首节点
private E unlinkLast(Node<E> l) {
final E element = l.item;
final Node<E> prev = l.prev;
l.item = null;
l.prev = null; // help GC
last = prev;
if (prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}
public E remove(int index) {
//检查index是否合法
checkElementIndex(index);
//通过index查找到对应的值,然后删除
return unlink(node(index));
}
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
//循环找到index对应的元素
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;
}
}
/*
*清空函数
*/
public void clear() {
//清空所有节点
for (Node<E> x = first; x != null; ) {
Node<E> next = x.next;
x.item = null;
x.next = null;
x.prev = null;
x = next;
}
first = last = null;
size = 0;
modCount++;
}
/*
*获取函数
*/
public E get(int index) {
//检查index是否合法
checkElementIndex(index);
//循环找到index对应的对应,然后取值
return node(index).item;
}
/*
*更新函数
*/
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
//除了这些函数以后还有好多对首节点,尾节点操作的函数,就不一一讨论了。
//但是下面LinkList封装了队列的操作,瞅上那么一瞅
/*
*操作函数
*/
public void push(E e) {
addFirst(e);
}
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
Vector:底层使用数组实现 ,其他方法根ArrayList差不多,但是这个在方法上都加了synchronized锁保证了线程安全,同时也是他的效率下降。
//数据存储
protected Object[] elementData;
//数据个数
protected int elementCount;
//扩容时候的增加量
protected int capacityIncrement;
//最大个数
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//构造函数
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
public Vector() {
this(10);
}
//添加函数
public synchronized void addElement(E obj) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = obj;
}
//检查下表是否越界
private void ensureCapacityHelper(int minCapacity) {
// 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 + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
//删除函数 先查找到位置,然后删除
public synchronized boolean removeElement(Object obj) {
modCount++;
int i = indexOf(obj);
if (i >= 0) {
removeElementAt(i);
return true;
}
return false;
}
//真正删除的函数
public synchronized void removeElementAt(int index) {
modCount++;
if (index >= elementCount) {
throw new ArrayIndexOutOfBoundsException(index + " >= " +
elementCount);
}
else if (index < 0) {
throw new ArrayIndexOutOfBoundsException(index);
}
int j = elementCount - index - 1;
if (j > 0) {
System.arraycopy(elementData, index + 1, elementData, index, j);
}
elementCount--;
elementData[elementCount] = null; /* to let gc do its work */
}
//获取函数
public synchronized E get(int index) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
return elementData(index);
}
//修改函数
public synchronized E set(int index, E element) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
MAP接口的“工具类”:储存的是键值对,键不可以重复,value可以重复,键可以为null,值不可以为null
HashMap:这个应该算是面试必备的了,面试一定会问道的,是一个散列表,采用拉链法的形式解决hash冲突,当拉链的长度大于8时,转化为红黑树,是线程 不安全的。key和value都可以为null。(transient 修饰的变量不参与序列化过程)
//默认初始容量
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;
//链表最大阈值,超过这个长度会转化为红黑树
static final int TREEIFY_THRESHOLD = 8;
//当链表长度小于这个值的时候会转化为链表
static final int UNTREEIFY_THRESHOLD = 6;
//树的最小容量
static final int MIN_TREEIFY_CAPACITY = 64;
// 存储元素的数组,总是2的倍数
transient Node<k,v>[] table;
transient Set<map.entry<k,v>> entrySet;
// 存放元素的个数,注意这个不等于数组的长度。
transient int size;
// 每次扩容和更改map结构的计数器
transient int modCount;
// 临界值 当实际大小(容量*填充因子)超过临界值时,会进行扩容
int threshold;
//构造函数
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
}
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
//get方法
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
//hash方法
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
//获取节点的方法
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
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) {
//红黑树
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 V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Node<K,V>[] tab; Node<K,V> p; int n, index;
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
Node<K,V> node = null, e; K k; V v;
//找到上边的节点
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p;
else if ((e = p.next) != null) {
//树结构遍历
if (p instanceof TreeNode)
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
else {
//链表结构遍历
do {
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
//找到待删除元素
node = e;
break;
}
p = e;
} while ((e = e.next) != null);
}
}
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
//树结构删除节点
if (node instanceof TreeNode)
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
else if (node == p)
//如果删除的是头节点
tab[index] = node.next;
else
//删除其他节点
p.next = node.next;
++modCount;
--size;
afterNodeRemoval(node);
return node;
}
}
return null;
}
下面我们好好分析一下这个hashmap(问答形式):
1.谈谈你理解的 HashMap,讲讲其中的 get put 过程?
HashMap是一个散列表,采用拉链法解决hash冲突,当链长到达一定长度时候,转化为红黑树。
get过程:
先取到key的hash值,然后hash值与数组长度-1与操作得到数组下标值,定位到相应的数组,判断节点是树结构还是表结构,如果是树结构的话,根据树结构去寻找,如果是表节后的话,依次遍历比较key得到相应的value。
put过程:
首先通过key计算hash值,然后将hash值&length-1得到下标值,判断有无头节点,如果没有,此节点作为头节点,然后存在了,判断头节点类型,如果链表类型,那么在尾部插入,如果是树形结构那么按照树的规则插入。
他的hash函数:
//hash方法
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
2.HashMap为什么不直接使用hashCode()处理后的哈希值直接作为table的下标?
hashcode()返回的值是int类型 -2^31 ~ 2^31-1 ,而HashMap的范围是16~2^30,HashMap通常情况下是取不到最大值的,并且设备上也没有这个大的存储空间,导致hashcode()的值不在数组范围内,从而无法匹配存储位置。
3.HashMap怎么寻找table的下标值的解决办法?
他自己实现了hash()方法,通过两次扰动使得他自己的哈希值高低位自行进行异或运算,降低哈希冲突的概率,使数据更平均。
然后通过计算的hash值与数组的最大值进行与操作,将结果作为下标值
4.为什么两次扰动?
为了增大hash值的随机性,使分布更均匀,提高了对应的数组存储下标位置的随机性&均匀性,最终减少了Hash冲突,两次就够了,已经达到了高位低位同时运算的目的。
5为什么HashMap中String、Integer这样的包装类适合作为K?
因为包装类重写了hashcode()和equal()方法,遵守HashMap的约束规范,还有Integer和String这两次中都使用的final修饰,都是不可变的,不会存在hash值不同的情况。
6.如果在HashMap中想用自己的Object做为Key怎么办?
重写hashCode方法,因为需要计算数据的存储位置,不要试图从散列表删除一个对象来提升性能,这让虽然能更快,但是可能会导致更多的Hash碰撞。
7.table的长度为什么是2的幂次方?
只有当2的幂次方的时候,length-1的二进制值为111111这种格式,所以length-1&hash=hash%(length-1),及实现了key的定位。
并且当这种时候,在进行与操作的时候会非常快,而且如果不是2的幂次,比如15,15-1=14二进制即1110,这样1111,1101,1011,0111,1001,0101,0011,0001这几个空间永远不会取到的。这样就会增大hash冲突的概率。
这些问题只是针对于hashmap,但是更多时的时候是将他与其他“工具类进行比较”
HashTable:其实这是一个已经废弃的工具类,他也是一个hash表,采用拉链法解决hash冲突,他是线程安全的。
简单的看一下源码:
//取值
public synchronized V get(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
//计算hash,为什么没有直接用hashcode?为什么要& 0x7FFFFFF?
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return (V)e.value;
}
}
return null;
}
//存值
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
//拉链
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
addEntry(hash, key, value, index);
return null;
}
ConcurrentHashMap:这个也是一个特别重要的工具类,这个类是间接的实现了map接口的,线程安全的,所以也放在这里说一下。
他在1.7和1.8中实现也有很大的不同,先看在1.7中
采用了分段锁技术,其中 Segment 继承于 ReentrantLock。不会像 HashTable 那样不管是 put 还是 get 操作都需要做同步处理,理论上 ConcurrentHashMap 支持 CurrencyLevel (Segment 数组数量)的线程并发。每当一个线程占用锁访问一个 Segment 时,不会影响到其他的 Segment。因为我的环境是1.8,所以1.7的源码就不多看了。
static class Segment<K,V> extends ReentrantLock implements Serializable {
private static final long serialVersionUID = 2249069246763182397L;
final float loadFactor;
Segment(float lf) { this.loadFactor = lf; }
}
再看看1.8中的ConcurrentHashMap ,采用CAS+Synchoried来保证并发操作,查询效率比1.7高。
//最大容量
private static final int MAXIMUM_CAPACITY = 1 << 30;
//默认长度
private static final int DEFAULT_CAPACITY = 16;
//负载因子
private static final float LOAD_FACTOR = 0.75f;
//链表转化为树的阈值
static final int TREEIFY_THRESHOLD = 8;
//树转化为链表的阈值
static final int UNTREEIFY_THRESHOLD = 6;
//存储数据
transient volatile Node<K,V>[] table;
//用来扩容或初始化的标志吧,,默认值为0,当在初始化的时候指定了大小,这会将这个大小保存在sizeCtl中,大小为数组的0.75
//当为负的时候,说明表正在初始化或扩张 -1表示初始化 -(1+n) n:表示活动的扩张线程
private transient volatile int sizeCtl;
//构造函数
public ConcurrentHashMap(int initialCapacity) {
if (initialCapacity < 0)
throw new IllegalArgumentException();
int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
MAXIMUM_CAPACITY :
tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
this.sizeCtl = cap;
}
//插入
public V put(K key, V value) {
return putVal(key, value, false);
}
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
//表为空,初始化表
tab = initTable();
//这个位置没有产生冲突
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
//cas插入元素
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
//正在转化,当前节点也会参与转化
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
synchronized (f) {
//如果已经冲突了,那么找到冲突位置的节点,并且是链表结构
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
//遍历寻找与待插入key和key的hash值相同的,如果有就替换
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
//如果没有找到,那么就插入到尾部
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
//如果是树结构
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
//添加到红黑树中
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
//判断是否需要将链表转化为红黑树
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
//取值操作
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
int h = spread(key.hashCode());
//找到当前的地址
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
if ((eh = e.hash) == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
else if (eh < 0)
return (p = e.find(h, key)) != null ? p.val : null;
while ((e = e.next) != null) {
//循环遍历找到节点
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}
//计算hash值,这里计算hash的方法与hashtable一致
static final int spread(int h) {
return (h ^ (h >>> 16)) & HASH_BITS;
}
//原子操作,获取指定位置节点
static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
}
//cas原子操作,在指定位置设置值
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
Node<K,V> c, Node<K,V> v) {
return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}
//原子操作,在指定为位置设置值
static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
}
ConcurrentHashMap的原子操作都是navicat方法,所以没法追综。
Set接口的“工具类”:模仿了数学中集合的概念,不允许重复,只能由一个null
HashSet:底层实际上是一个hashmap的实例,是线程不安全的。
//存储对象
private transient HashMap<E,Object> map;
//作为键值对的object
private static final Object PRESENT = new Object();
//构造方法
public HashSet() {
map = new HashMap<>();
}
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
//添加函数
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
//删除函数
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
LinkedHashMap:hashset的子类,使用链表维护插入顺序;
这个源码很简单,但是他的原理在他的源码中体现不出来。在这给个链接,大家自己看吧。
https://segmentfault.com/a/1190000012964859
TreeSet:是一个有序集合,基于Treemap实现,使用时需要指定排序方式。
//存储对象
private transient NavigableMap<E,Object> m;
//存储时候作为键值对的值
private static final Object PRESENT = new Object();
//存值
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
//取值
public boolean remove(Object o) {
return m.remove(o)==PRESENT;
}
哎,我考试都没这么准备过。
来源:CSDN
作者:骚小孩呀
链接:https://blog.csdn.net/huaixiaohai_1/article/details/104167210