日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

java 中counter什么意思_方便适用的计数器Counter

發(fā)布時(shí)間:2023/12/18 编程问答 44 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java 中counter什么意思_方便适用的计数器Counter 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

為什么要使用計(jì)數(shù)器?

在游戲程序的開發(fā)中,我們會遇到很多跟計(jì)數(shù)相關(guān)的需求,比如玩家領(lǐng)取了多少次獎勵、成就的任務(wù)進(jìn)度、一場比賽中的得分等等。然而在很多的API里,很少提供我們不用關(guān)心邊界值或中間操作的計(jì)數(shù)器,特別是對于服務(wù)器來講,基本會使用有鍵值對的計(jì)數(shù)器,因?yàn)槲覀児芾淼氖且蝗和婕?#xff0c;并不是一個,當(dāng)然可能還會有更多的層級,比如一群玩家的任務(wù)進(jìn)度計(jì)數(shù),就是一個3維數(shù)組。要實(shí)現(xiàn)這種看似簡單的功能,我們就會想到Java里Map這個東西,但是很遺憾的是,Map的處理太過多余了,他并不是為計(jì)數(shù)而生,我們需要改造一下,這里先從簡單計(jì)數(shù)器說起。

AtomicInteger

這個AtomicInteger好啊,又是線程安全的,又有方便的計(jì)數(shù)API,我們就從他出發(fā)吧。

計(jì)數(shù)器的API總共就幾個,獲取當(dāng)前值(get),直接設(shè)置當(dāng)前值(set),增加多少值(getAndAdd和addAndGet),其實(shí)就相當(dāng)于操作符++,一個是a++,一個是++a。在增加值的基礎(chǔ)上再提供++1、- -1、1- -、1++這種操作,基本上就夠用了,對于AtomicInteger的原理這里就不贅述。

IntCounter

既然有線程安全的了,為啥要有一個非安全的,嘛。。畢竟線程安全的有那么點(diǎn)點(diǎn)以犧牲性能為代價(jià)。這里提一下游戲服務(wù)器里的線程模型,假設(shè)是一個以地圖為基礎(chǔ)的RPG游戲,通常來講,我們會以地圖來分線程,保證同一個地圖的玩家是在同一個線程上的(這就是為什么有些游戲交易必須同地圖,甚至更老的游戲,功能都綁定在了NPC身上,題外話不多說)。如果該功能對于玩家是單機(jī)性質(zhì)(自己的操作不影響他人)的或者說即使交互(與其他玩家發(fā)生行為)也是同地圖玩家的交互,計(jì)數(shù)操作是沒必要線程安全的。RPG游戲是高反饋低延時(shí)的游戲,所以扣點(diǎn)性能也沒什么大驚小怪的(雖然現(xiàn)在硬件已經(jīng)很變態(tài)了)。

IntCounter的實(shí)現(xiàn)其實(shí)非常簡單,就是對int的再次封裝(LongCounter同理),想必我這里不說,大家都明白怎么寫這個代碼了。舉個栗子,代碼沒什么好講的,大家可以實(shí)現(xiàn)更多方便的API,雖然這些API總共就沒幾行,但是積少成多,對于項(xiàng)目的開大有著很大的幫助,不要小看他了。

private int count;

/*** 歸零*/

public void zero() {

setCount(0);

}

/*** 設(shè)置為最大值*/

public void setHigh() {

setCount(high());

}

/*** 設(shè)置為最小值*/

public void setLow() {

setCount(low());

}

/*** 最大限制** @return*/

public int high() {

return Integer.MAX_VALUE;

}

/*** 最小限制** @return*/

public int low() {

return 0;

}

/*** 獲取當(dāng)前數(shù)值** @return*/

public int getCount() {

return this.count;

}

/*** 直接設(shè)置當(dāng)前值** @param value* @return*/

protected int setCount(int value) {

return this.count = GameMathUtil.fixedBetween(value, low(), high());

}

/*** +1并獲取** @return*/

public int incrementAndGet() {

return addAndGet(1);

}

/*** -1并獲取** @return*/

public int decrementAndGet() {

return addAndGet(-1);

}

/*** 增加并獲取** @param delta* @return*/

public int addAndGet(int delta) {

return setCount(getCount() + delta);

}

/*** 獲取并+1** @return*/

public int getAndIncrement() {

return getAndAdd(1);

}

/*** 獲取并-1** @return*/

public int getAndDecrement() {

return getAndAdd(-1);

}

/*** 獲取并增加** @param delta* @return*/

public int getAndAdd(int delta) {

int old = getCount();

setCount(GameMathUtil.safeAdd(old, delta));

return old;

}

鍵值對Map類型計(jì)數(shù)器的包裝

剛才說了,不管是對于服務(wù)器本身的性質(zhì)也好還是對于需求本身也好,都會存在同類型復(fù)數(shù)個計(jì)數(shù)器,我們就自然想到了Map(2維映射)類型甚至Table(3維映射)來處理這個事情。由于原本自帶的Map并不是專門做這種事情的,所以我們針對計(jì)數(shù)器的需求特別優(yōu)化一下API的友好度。

如果我們直接使用Map的話,我們必須要處理

1.不管在放入計(jì)數(shù)或者是獲取計(jì)數(shù)的時(shí)候是否存在一個鍵值對,如果不存在我們會初始化他

2.如果大部分的計(jì)數(shù)在常規(guī)狀態(tài)下都為初始值(這里假設(shè)為0),那么我們會初始化一堆沒有用的數(shù)據(jù)

3.每次計(jì)數(shù)改變的操作,都會先取出數(shù)據(jù)(取出的時(shí)候還要做第1步的檢查),然后更改數(shù)值再放回,這些代碼重復(fù)太多會讓寫代碼的人不能直接關(guān)注需求本身而產(chǎn)生混亂從而導(dǎo)致很多BUG。

下面的代碼簡單的演示下上面的痛處

//聲明一個MapMap tasks = new HashMap<>();

//現(xiàn)在獲取任務(wù)"kill monster"的進(jìn)度String taskName = "kill monster";

Integer taskProccess = tasks.get(taskName);

//如果任務(wù)進(jìn)度為空則初始化任務(wù)進(jìn)度為0if(taskProccess == null){

taskProccess = 0;

tasks.put(taskName,taskProccess);

}

//任務(wù)進(jìn)度+1taskProccess+=1;

//這里由于慣性思維,在寫很多復(fù)雜邏輯的時(shí)候很有可能會不做put操作而導(dǎo)致bugtasks.put(taskName,taskProccess);

上面的操作簡直讓人蛋疼無比!那么我們先看看經(jīng)過優(yōu)化過的IntMap是怎么寫代碼的吧!

IntMap tasksNew = IntMap.empty();

tasksNew.incrementAndGet(taskName);

兩行代碼解決,是不是輕松多了,不算上聲明,1行代碼解決,本來計(jì)數(shù)這種簡單操作就應(yīng)該是一行代碼操作的事情,對吧。

針對上面的傷痛,我們看看是怎么實(shí)現(xiàn)一個自己的IntMap。計(jì)數(shù)器的API都是大同小異的

/*** 獲取計(jì)數(shù)** @param key* @return*/

int getCount(K key);

/*** 設(shè)置計(jì)數(shù)** @param key* @param newValue* @return*/

int putCount(K key, int newValue);

/*** 總數(shù)** @return*/

int sum();

/*** 加1并獲取** @param key* @return*/

int incrementAndGet(K key);

/*** 減1并獲取** @param key* @return*/

int decrementAndGet(K key);

/*** 增加并獲取** @param key* @param delta* @return*/

int addAndGet(K key, int delta);

/*** 獲取并加1** @param key* @return*/

int getAndIncrement(K key);

/*** 獲取并減1** @param key* @return*/

int getAndDecrement(K key);

/*** 獲取并增加** @param key* @param delta* @return*/

int getAndAdd(K key, int delta);

我們使用Java8中Map的新API(Map.compute)可以非常方便的實(shí)現(xiàn)剛才Map中冗余的操作

/*** 獲取并更新** @param key* @param updaterFunction* @return*/

private int getAndUpdate(K key, IntUnaryOperator updaterFunction) {

AtomicInteger holder = new AtomicInteger();

map.compute(key, (k, value) -> {

// 如果獲取key的value為空,則直接返回0 int oldValue = (value == null) ? 0 : value;

holder.set(oldValue);

return updaterFunction.applyAsInt(oldValue);

});

return holder.get();

}

獲取并更新實(shí)現(xiàn)了,更新并獲取就大同小異了,其余的API也只是對這個基礎(chǔ)方法進(jìn)行包裝而已

鍵值對更特殊的優(yōu)化-枚舉計(jì)數(shù)器EnumIntCounter

枚舉是個非常好的東西,讓代碼看起來非常簡潔,不混亂,有明確定義,主要是有一種限定作用,避免產(chǎn)生參數(shù)值的錯誤。我們來想象一個需求,統(tǒng)計(jì)星期1-7當(dāng)中,哪個星期玩家殺怪的數(shù)量最多,這里我們就可以把星期1-7做成一個枚舉

public enum WEEK{

W_1,

W_2,

W_3,

W_4,

W_5,

W_6,

W_7,

}

這里不用星期的英文是因?yàn)槲覒械萌ゲ榱?屬于說得出來拼不出來,看見又認(rèn)識的那種,哈哈,野生英語水平)。既然枚舉叫枚舉,那在代碼運(yùn)行期間,他的數(shù)量肯定是一定的,所以我們在表示這種結(jié)構(gòu)的時(shí)候不會像Map那樣復(fù)雜,單純的用一個int數(shù)組(int[])就行了,至于大小,剛才不是說了嗎,枚舉是固定的,所以我們就這樣聲明

private int[] counts;

public static > EnumIntCounter create(Class enumClass) {

return new EnumIntCounter<>(enumClass);

}

public EnumIntCounter(Class enumClass) {

counts = new int[EnumUtil.length(enumClass)];

}

這里獲取枚舉長度用的EnumUtil.length其實(shí)就是c.getEnumConstants().length,c是枚舉Class。

實(shí)現(xiàn)get和put對于數(shù)組來說就非常簡單了,只需要提供數(shù)據(jù)的index去做更改就行了。至于默認(rèn)值為0,數(shù)據(jù)本身new出來就全部默認(rèn)是0了。有時(shí)候還是需要通過枚舉的編號去獲取計(jì)數(shù)的,所以我們還得為get和put分別提供一個傳int編號過來查找計(jì)數(shù)的重載方法

/*** 獲取計(jì)數(shù)** @param e* @return*/

public int getCount(E e) {

return getCount(e.ordinal());

}

private int getCount(int ordinal) {

if (ordinal > counts.length - 1 || ordinal < 0) {

return 0;

}

return counts[ordinal];

}

/*** 放置計(jì)數(shù)** @param e* @param value* @return*/

public int putCount(E e, int value) {

return putCount(e.ordinal(), value);

}

private int putCount(int ordinal, int value) {

// 容錯 if (ordinal > counts.length - 1) {

int[] temp = new int[ordinal + 1];

System.arraycopy(counts, 0, temp, 0, counts.length);

counts = temp;

}

int old = counts[ordinal];

counts[ordinal] = value;

return old;

}

同樣的,實(shí)現(xiàn)了get和put,什么增加、減少、加一、減一我相信你自己就能搞定,就不贅述了??傊?#xff0c;這些東西雖然看起來非常簡單,感覺人人都能想到,但是真正跑過去優(yōu)化的人不多,自己總是抱怨寫起煩躁,但是就是不動手搞一搞。我在項(xiàng)目上用到的這3種計(jì)數(shù)器讓我寫復(fù)雜邏輯的時(shí)候不再關(guān)心如何計(jì)數(shù),身心健康,心曠神怡,再也不再想錘策劃人員一頓了~好處就是這么多。

如果你也實(shí)現(xiàn)完了,我們來看看怎么用吧,巨簡單!

EnumIntCounter tasksNewAgain = new EnumIntCounter<>(WEEK.class);

tasksNewAgain.incrementAndGet(WEEK.W_1);

哎呀!我去,舒服!

告辭!

總結(jié)

以上是生活随笔為你收集整理的java 中counter什么意思_方便适用的计数器Counter的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。