java中counter什么意思_java容器中的⼏种计数⽅法浅谈本⽂讨论java集合容器中的⼏种元素数量获取的⽅式,命题很⼩,但是也⾜以让我们思考⼀些东西。
所谓计数:即是给出所在容器的元素总数的⽅式。⼀般能想到的就是两种⽅式:⼀是使⽤某个字段直接存储该计数值,⼆是在请求计数值时临时去计算所有元素数量。貌似本⽂的答案已经出来了。好吧,那我们还是从源码的⾓度来验证下想法吧:
⼀般在java的集合容器中,可以分为普通容器和并发容器,我们就姑且以这种⽅式来划分下,验证下其实现计数的⽅式吧!
1. 普通容器 --HashMap
⼀般⾮并发容器在进⾏增删改时,都会同时维护⼀个count值,如hashmap中的实现:
//HashMap 增加和修改都在此实现
java中index是什么意思/*** Implements Map.put and related methods
*
*@paramhash hash for key
*@paramkey the key
*@paramvalue the value to put
*@paramonlyIfAbsent if true, don't change existing value
*@paramevict if false, the table is in creation mode.
*@returnprevious value, or null if none*/
final V putVal(int hash, K key, V value, booleanonlyIfAbsent,booleanevict) {
Node[] tab; Node p; intn, i;if ((tab = table) == null || (n = tab.length) == 0)
n= (tab =resize()).length;if ((p = tab[i = (n - 1) & hash]) == null)
tab[i]= newNode(hash, key, value, null);else{
Nodee; K k;if (p.hash == hash &&((k= p.key) == key || (key != null &&key.equals(k))))
e=p;else if (p instanceofTreeNode)
e= ((TreeNode)p).putTreeVal(this, tab, hash, key, value);else{for (int binCount = 0; ; ++binCount) {if ((e = p.next) == null) {
<= newNode(hash, key, value, null);if (binCount >= TREEIFY_THRESHOLD - 1) //-1 for 1st
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);returnoldValue;
}
}++modCount;//直接对size进⾏增加1即可, 如果是更新key的值,则不会运⾏到此处,即不会进⾏相加
if (++size >threshold)
resize();
afterNodeInsertion(evict);return null;
}//删除元素的实现,同时维护 size ⼤⼩
/*** ve and related methods
*
*@paramhash hash for key
*@paramkey the key
*@paramvalue the value to match if matchValue, else ignored
*@parammatchValue if true only remove if value is equal
*@parammovable if false do not move other nodes while removing
*@returnthe node, or null if none*/
final Node removeNode(inthash, Object key, Object value,boolean matchValue, booleanmovable) {
Node[] tab; Node p; intn, index;if ((tab = table) != null && (n = tab.length) > 0 &&(p= tab[index = (n - 1) & hash]) != null) {
Node node = null, e; K k; V v;//先查node所在的位置
if (p.hash == hash &&((k= p.key) == key || (key != null &&key.equals(k))))
node=p;else if ((e = p.next) != null) {if (p instanceofTreeNode)
node= ((TreeNode)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 instanceofTreeNode)
((TreeNode)node).removeTreeNode(this, tab, movable);else if (node ==p)
tab[index]=;=;++modCount;//直接减⼩size即可
--size;
afterNodeRemoval(node);returnnode;
}
}return null;
}
因为有了增删改时对计数器的维护,所以在想要获取总数时,就容易许多了。只需把size字段返回即可。
//HashMap.size()
/*** Returns the number of key-value mappings in this map.
*
*@returnthe number of key-value mappings in this map*/
public intsize() {returnsize;
}
所以,在这种情况下,获取计数值的⽅式⾮常简单。但是不管怎么样,size字段对外部是不可见的,因为它是容器内部的⼀个实现逻辑,它完全在将来的某个时刻改变掉。即 size() != size .
2. 普通容器 --LinkedList
看完hash类的计数实现,咱们再来看另外⼀个类型的容器LinkedList:
//LinkedList.add(E) 添加⼀个元素
public booleanadd(E e) {
linkLast(e);return true;
}/*** Links e as last element.*/
voidlinkLast(E e) {final Node l =last;final Node newNode = new Node<>(l, e, null);
last=newNode;if (l == null)
first==newNode;//同样,直接使⽤⼀个 size 计数器统计即可
size++;
modCount++;
}//删除⼀个元素, 同时维护 size 字段
publicE removeFirst() {final Node f =first;if (f == null)throw newNoSuchElementException();returnunlinkFirst(f);
}/*** Unlinks non-null first node f.*/
private E unlinkFirst(Nodef) {//assert f == first && f != null;
final E element =f.item;final Node next =f.next;
f.item= null;
<= null; //help GC
first =next;if (next == null)
last= null;elsenext.prev= null;//元素计数减1
size--;
modCount++;returnelement;
}//同样,统计元素数量时,直接返回size即可
public intsize() {returnsize;
}
可见,LinkedList 也同样是简单地维护⼀个计数器字段,从⽽实现了⾼效地计数⽅法。⽽这简单地实现,则是基于单线程的访问的,它同时维护⼀个计数字段,基本没有多少开销,却给取值时带来了便利。
总结: 普通容器直接维护⼀个计数器字段,可以很⽅便地进⾏⼤⼩统计操作。
3. 并发容器 --ConcurrentHashMap
⽽对于并发容器,则可能会不⼀样些,但也有⼀些情况是⼀样的。⽐较,HashTable, 直接使⽤ synchronized 来保证线程安全,则它也同样可以直接使⽤⼀个size即可完成元素⼤⼩的统计。事实上,有些版本的HashTable仅仅是在HashMap的上⾯加上了synchronizd锁⽽已(有些版本则是 不⼀样的哦),细节咱们⽆需再看。
⽽稍微有点不⼀样的如: ConcurrentHashMap.size(), 早期的 ConcurrentHashMap 使⽤分段锁,则需要统计各segement的元素,相加起来然后得到整体元素⼤⼩. ⽽jdk1.8中,已经放弃使⽤分段锁来实现⾼性能安全的hash容器了,⽽是直接使⽤ synchronized + CAS + 红⿊树实现. 那么,我们来看看其实现
元素统计这⼀功能的实现有何不同吧!
//ConcurrentHashMap.putVal() 新增或修改⼀个元素
/**Implementation for put and putIfAbsent*/
final V putVal(K key, V value, booleanonlyIfAbsent) {if (key == null || value == null) throw newNullPointerException();int hash =spread(key.hashCode());int binCount = 0;for (Node[] tab =table;;) {
Node f; intn, i, fh;if (tab == null || (n = tab.length) == 0)
tab=initTable();else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {if (casTabAt(tab, i, null,new Node(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 e = f;; ++binCount) {
K ek;if (e.hash == hash &&((ek= e.key) == key ||(ek!= null &&key.equals(ek)))) {
oldVal=e.val;if (!onlyIfAbsent)
e.val=value;break;
}
Node pred =e;if ((e = e.next) == null) {
<= new Node(hash, key,
value,null);break;
}
}
}else if (f instanceofTreeBin) {
Nodep;
binCount= 2;if ((p = ((TreeBin)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)returnoldVal;break;
}
}
}//主要是在进⾏新增成功时,再进⾏计数器的操作, 看起来不是 ++size 这么简单了
addCount(1L, binCount);return null;
}//这个计数的相加看起来相当复杂
/*** Adds to count, and if table is too small and not already
* resizing, initiates transfer. If already resizing, helps
* perform transfer if work is available. Rechecks occupancy
* after a transfer to see if another resize is already needed
* because resizings are lagging additions.
*
*@paramx the count to add
*@paramcheck if <0, don't check resize, if <= 1 only check if uncontended*/
private final void addCount(long x, intcheck) {
CounterCell[] as;longb, s;//使⽤ CounterCell 来实现计数操作//使⽤ CAS 保证更新计数时只会有⼀个线程成功
if ((as = counterCells) != null ||
!UpareAndSwapLong(this, BASECOUNT, b = baseCount, s = b +x)) {
CounterCell a;long v; intm;boolean uncontended = true;if (as == null || (m = as.length - 1) < 0 ||
//使⽤⼀个类似随机负载均衡的⽅式,将计数值随机添加到 CounterCell 的某个值下⾯,减少多线程竞争的可能性
(a = Probe() & m]) == null ||
//通过cas将计数值x添加到 CounterCell 的 value 字段中
!(uncontended =UpareAndSwapLong(a, CELLVALUE, v= a.value, v +x))) {//如果上⾯添加失败,
则使⽤ fullAddCount 进⾏重新添加该计数
fullAddCount(x, uncontended);return;
}if (check <= 1)return;//基于 CounterCell 做⼀此汇总操作
s =sumCount();
}//在进⾏put值时, check的值都是⼤于等于0的
if (check >= 0) {
Node[] tab, nt; intn, sc;//rehash 处理
while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&(n= tab.length) >> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1
||sc== rs + MAX_RESIZERS || (nt = nextTable) == null ||transferIndex<= 0)break;if (UpareAndSwapInt(this, SIZECTL, sc, sc + 1))
transfer(tab, nt);

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。