JAVA集合总结笔记_Java集合笔记

Java集合分为四类分别为

Map:无序,具有key-value映射的集合,里面有一个entry类,里面有key和value

Set:无序,不可重复的集合

List:有序,可重复的集合

Queue:队列,先进先出

Map与Set集合、List集合的关系

1.与Set的关系,如果把Map中的所有key放到一起看,他就组成了一个set(无序且不可重复),其中hashSet的底层就是用hashMap实现的

2.与List的关系:如果把Map的所有value放到一起来看,他就组成了一个list(根据索引查找、可重复)

HashMap月HashSet的区别

1.接口不同:HashMap实现Map接口,HashSet实现Set接口

2.存储对象不同:HashMap存储键值对,HashSet存储一个对象

3.增加数据方式不同:HashMap使用put,HashSet使用add

4.hashcode对象不同:hashmap是key,hashSet使成员对象

5.hashmap较快,hashset较慢

6.hashset的底层是使用hashmap来实现的,就是add添加的value设置为键,hashmap的value为一个final static的object对象

数组和集合的区别

1.数组定长,集合不定长,数组实现的集合有自动扩容机制,集合可以保存具有映射关系的数据

2.数组可以是基本数据类型,也可以是对象。集合只能存储对象

ArrayList

已数组实现的集合,初始大小为10,当超出限制时会增加50%的容量,在进行扩容、删除时使用System.arrayCopy()来进行数据的而移动,大量移动性能不太高

自动扩容的代码:

private void ensureCapacityInternal(int minCapacity) {

if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {

minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); }

ensureExplicitCapacity(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;

// 扩展为原来的1.5倍

int newCapacity = oldCapacity + (oldCapacity >> 1);

// 如果扩为1.5倍还不满足需求,直接扩为需求值

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);

}

LinkedList

已双向链表实现,容量没有空间限制,需要用指针来操作

里面主要的方法是node,set和get都利用了这个方法

Node node(int index) {

// assert isElementIndex(index);

if (index < (size >> 1)) {

Node x = first;

for (int i = 0; i < index; i++)

x = x.next; return x;

} else {

Node x = last;

for (int i = size - 1; i > index; i--)

x = x.prev;

return x;

}

}

这个方法将链表的时间复杂度从O(n)降到O(n/ 2)

HashMap

hashMap是由数组(默认为16)、链表、红黑树构成的集合,他存储的是具有映射关系的数据,其键不重复,loadfactor = 0.75,属于非同步的

1.工作原理:当put和get时,对key的hashcode做hash,即(key的hashcode高16位异或低16位),之后&上bucket的长度-1,(hash % length == (length -1) & hash),通过hash得到bucket的位置,取余的操作是使bucket平衡,下一步确定值通过equals来确定值

2.hash实现: h = (h.hashCode() ^ h >>>16);这样的目的是为了减少碰撞,

3.hashMap超过负载因子(load factor * 容量)大小时,将会resize一个原来长度的hashMap,就是bucket变为原来的两倍

put函数的代码

public V put(K key, V value) {

// 对key的hashCode()做hash

return putVal(hash(key), key, value, false, true);

}

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,

boolean evict) {

Node[] tab; Node p; int n, i;

// tab为空则创建

if ((tab = table) == null || (n = tab.length) == 0)

n = (tab = resize()).length;

// 计算index,并对null做处理

if ((p = tab[i = (n - 1) & hash]) == null)

tab[i] = newNode(hash, key, value, null);

else {

Node e; K k;

// 节点存在

if (p.hash == hash &&

((k = p.key) == key || (key != null && key.equals(k)

)))

e = p;

// 该链为树

else if (p instanceof TreeNode)

e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);

// 该链为链表

else {

for (int binCount = 0; ; ++binCount) {

if ((e = p.next) == null) {

p.next = newNode(hash, key, value, null);

if (binCount >= TREEIFY_THRESHOLD - 1) // -1

treeifyBin(tab, hash);

break;

}

if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))

break;

p = e;

}

}

// 写入

if (e != null) { // existing mapping for key

V oldValue = e.value;

if (!onlyIfAbsent || oldValue == null)

e.value = value;

afterNodeAccess(e);

return oldValue;

}

}

++modCount;

// 超过load factor*current capacity,resize

if (++size > threshold)

resize();

afterNodeInsertion(evict);

return null;

整体的过程就是先判断数组是否为空,如果为空则创建,之后判断数组中是否存在该元素,如果不存在就存入数组,如果数组存在就判断是否为树,是树就调用putTreeval方法,如果是链表就经过for循环得到得到插入的位置,如果插入的个数达到转化为红黑树的数量就存为二叉树,如果已经存在则取代以前的值

get函数的代码

final Node getNode(int hash, Object key) {

Node[] tab; Node 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) { // 在树中get

if (first instanceof TreeNode)

return ((TreeNode)first).getTreeNode(hash,

// 在链表中get

do {

if (e.hash == hash &&

((k = e.key) == key || (key != null && key.e

return e;

} while ((e = e.next) != null);

}

}

return null;

}

总的来说流程就是先找该bucket位置的,如果不是再去树种找,看是否是树,如果不是则去链表中找,如何后面都没有则返回null;

concurrentHashMap

为了解决hashmap在多线程下使用put操作会引起死循环

hashTable使用synchronized来保证线程安全的,但是在线程竞争激烈的情况下hashtable的效率低下,当一个线程访问hashtable的同步方法时,其他线程会进入阻塞状态

分段锁:

jdk1.7:

ConcurrrentHashmap是segment数组和hashEntry数组构成,setgment继承自reentrantLock,其中一个segmen里面包含一个 hashEntry数组,一个hashEntry数组里面包含多个hashEntry

HashTable容器在竞争激烈的并发环境下表现出效率低下的原因,是因为所有访问HashTable的线程都必须竞争同一把锁,那假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问,有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁。这里“按顺序”是很重要的,否则极有可能出现死锁,在ConcurrentHashMap内部,段数组是final的,并且其成员变量实际上也是final的,但是,仅仅是将数组声明为final的并不保证数组成员也是final的,这需要实现上的保证。这可以确保不会出现死锁,因为获得锁的顺序是固定的。(简书其他作者的理解)

put的流程:得到k的hash计算值 & segment数组,得到segment,之后在segment里面的put是hash值得到在segment里面的位置,然后为调用put方法,其中put方法调用tryLock方法,如果获取锁失败则地调用scanAndForput方法来就是循环调用tryLock,如果超过了我们设置的最大循环次数,就执行lock方法,阻塞等待,直到获取segment锁为止

public V put(K key, V value) {

Segment s;

//计算hash key值

int hash = hash(key);

//通过hash key值计算出存入Segment的位置

int j = (hash >>> segmentShift) & segmentMask;

if ((s = (Segment)UNSAFE.getObject

(segments, (j << SSHIFT) + SBASE)) == null)

//初始化Segment

s = ensureSegment(j);

//添加

return s.put(key, hash, value, false);

}

final V put(K key, int hash, V value, boolean onlyIfAbsent) {

//segment操作加锁,使用尝试获取锁方式。如果获取失败,进入scanAndLockForPut方法

HashEntry node = tryLock() ? null :

scanAndLockForPut(key, hash, value);

V oldValue;

try {

HashEntry[] tab = table;

int index = (tab.length - 1) & hash;

HashEntry first = entryAt(tab, index);

for (HashEntry e = first;;) {

if (e != null) {

K k;

if ((k = e.key) == key ||

(e.hash == hash && key.equals(k))) {

oldValue = e.value;

if (!onlyIfAbsent) {

e.value = value;

++modCount;

}

break;

}

e = e.next;

}

else {

if (node != null)

node.setNext(first);

else

node = new HashEntry(hash, key, value, first);

int c = count + 1;

if (c > threshold && tab.length < MAXIMUM_CAPACITY)

//扩容, 这是后续做解释

rehash(node);

else

setEntryAt(tab, index, node);

++modCount;

count = c;

oldValue = null;

break;

}

}

} finally {

//释放锁

unlock();

}

return oldValue;

}

get方法类似

总结:get方法不需要加锁,因为get不会改变map中数据

jdk1.8

采用Node + CAS + synchronizeds设计

public class ConcurrentHashMap extends AbstractMap

implements ConcurrentMap, Serializable {

transient volatile Node[] table;

}

static class Node implements Map.Entry {

final int hash;

final K key;

volatile V val;

volatile Node next;

}

总结:

Hashtable的任何操作都会把整个表锁住,是阻塞的。好处是总能获取最实时的更新,比如说线程A调用putAll写入大量数据,期间线程B调用get,线程B就会被阻塞,直到线程A完成putAll,因此线程B肯定能获取到线程A写入的完整数据。坏处是所有调用都要排队,效率较低。

ConcurrentHashMap 是设计为非阻塞的。在更新时会局部锁住某部分数据,但不会把整个表都锁住。同步读取操作则是完全非阻塞的。好处是在保证合理的同步前提下,效率很高。坏处是严格来说读取操作不能保证反映最近的更新。例如线程A调用putAll写入大量数据,期间线程B调用get,则只能get到目前为止已经顺利插入的部分数据。

应该根据具体的应用场景选择合适的HashMap。

TreeMap

这个是一个有顺序的map,可以根据key大小进行排序

put函数:如果存在的话,old value被替换,如果不存在,则新添一个节点,然后做红黑树的平衡操作

public V put(K key, V value) {

Entry 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 parent;

// split comparator and comparable paths

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();

@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 = new Entry<>(key, value, parent); if (cmp < 0)

parent.left = e;

else

parent.right = e;

// 红黑树平衡调整 fixAfterInsertion(e); size++;

modCount++;

return null;

}

get函数

final Entry getEntry(Object key) {

// Offload comparator-based version for sake of performance if (comparator != null)

return getEntryUsingComparator(key);

if (key == null)

throw new NullPointerException();

@SuppressWarnings("unchecked")

Comparable super K> k = (Comparable super K>) key;

Entry 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;

}

public V get(Object key) {

Entry p = getEntry(key); return (p==null ? null : p.value);

}

successor

static TreeMap.Entry successor(Entry t) {

if (t == null)

return null;

else if (t.right != null) {

// 有右子树的节点,后继节点就是右子树的“最左节点”

// 因为“最左子树”是右子树的最小节点

Entry p = t.right;

while (p.left != null)

p = p.left;

return p;

} else {

// 如果右子树为空,则寻找当前节点所在左子树的第一个祖先节点

// 因为左子树找完了,根据LDR该D了

Entry p = t.parent;

Entry ch = t;

// 保证左子树

while (p != null && ch == p.right) {

ch = p;

p = p.parent;

}

return p;

}

}

successor就是中序遍历

LinkedHashMap

继承于HashMap的集合类,可以维持插入时的顺序和访问顺序

1.afterNodeAccess

void afterNodeAccess(Node e) { // move node to last

LinkedHashMap.Entry last;

// 如果定义了accessOrder,那么就保证最近访问节点放到最后

if (accessOrder && (last = tail) != e) { //将尾指针赋值给last

//b 为前一个元素 ,a为后一个元素

LinkedHashMap.Entry p = (LinkedHashMap.Entry)e, b = p.before, a = p.after;

//使访问后的元素为尾元素,即after为null

p.after = null;

//如果访问元素的前一个元素为null,那么他的后一个元素就为头指针

if (b == null)

head = a;

else

b.after = a; // 将前一个元素的下一个值指定为他的后一个元素

if (a != null) // 如果后一个元素不为空,那么后一个元素的前一个元素为b(访

问元素的前一个元素)

a.before = b;

else

last = b; //如果是空,则将last设置为b,即访问元素的前一个为尾指针

if (last == null)

head = p; //如果last的空,这样的情况是链表只存在一个数据,但是数据被访

问 了,则将head 设置成最后那个数据

else {

//否则将p的前指针指向尾指针,将尾指针的下一个元素指向访问的元素

p.before = last;

last.after = p;

}

tail = p;

++modCount;

}

}

afterNodeInsertion

void afterNodeInsertion(boolean evict) { // possibly remove elde

st

LinkedHashMap.Entry first;

// 如果定义了溢出规则,则执行相应的溢出

if (evict && (first = head) != null && removeEldestEntry(fir

st)) {

K key = first.key;

removeNode(hash(key), key, null, false, true);

}

}

afterNodeRemoval

void afterNodeRemoval(Node e) { // unlink // 从链表中移除节点

LinkedHashMap.Entry p =

(LinkedHashMap.Entry)e, b = p.before, a = p.after; p.before = p.after = null;

if (b == null)

head = a;

else

b.after = a;

if (a == null)

tail = b;

else

a.before = b;

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值