天天看點

得物(毒)APP,8位抽獎碼需求,這不就是産品給我留的數學作業!

得物(毒)APP,8位抽獎碼需求,這不就是産品給我留的數學作業!

作者:小傅哥

部落格:https://bugstack.cn

Github:https://github.com/fuzhengwei/CodeGuide/wiki

沉澱、分享、成長,讓自己和他人都能有所收獲!😄

一、前言

得物(毒)APP,8位抽獎碼需求,這不就是産品給我留的數學作業!

為什麼你的代碼一坨坨?其實來自你有那麼多為什麼你要這樣寫代碼!

  • 為什麼你的代碼那麼多for循環?因為沒有合理的資料結構和算法邏輯。
  • 為什麼你的代碼那麼多ifelse?因為缺少設計模式對業務場景的運用。
  • 為什麼你的程式應用複雜對接困難?因為沒有良好的系統架構拆分和規劃。
  • 為什麼你的程式邏輯開發傳遞慢返工多?因為不具備某些業務場景的開發經驗。
  • 為什麼你的程式展現都是看上去不說人話?因為沒有産品思維都是程式員邏輯的展現。

最終,所有的這些不合理交織在一起,就是你能看到的一坨坨的代碼!是以,要想把代碼寫好、寫美,寫到自己願意反複欣賞,那麼基本需要你有一定的:基礎能力(資料結構、算法邏輯、設計模式)、應用能力(系統架構、開發經驗)、拓展能力(産品思維),這三方面綜合起來才能更好的開發程式。

但可能杠精會喊,我就寫個CRUD要什麼邏輯、什麼資料結構,還算法?

但寫CRUD并不一定業務需求是CRUD,隻是你的知識面和技術深度隻能把它設計成CRUD,用ifelse和for循環在一個類裡反複粘貼複制罷了。

可能同樣的需求交給别人手裡,就會想的更多搭建的更加完善。就像:樹上10隻鳥開一槍還剩下幾隻,你會想到什麼?比如:

  • 手搶是無聲的嗎?
  • 槍聲大嗎?
  • 這個城市打鳥犯不犯法?
  • 确定那隻鳥被打死了?
  • 樹上的鳥有沒有聾子?
  • 有沒有被關在籠子裡或者綁在樹上的鳥?
  • 旁邊還有其他樹嗎?
  • 有殘疾或者飛不動的鳥嗎?
  • 有懷孕肚子裡的鳥嗎?
  • 打鳥的人眼睛花沒花?
  • 保證是10隻嗎?
  • 有沒有那種不怕死的鳥?
  • 會不會一槍打死兩隻或者更多?
  • 所有的鳥都可以自由活動飛離樹以外嗎?
  • 打死以後挂在樹上還是掉下來了?

是以,你還相信寫程式隻是簡簡單單的搞CRUD嗎?接下來小傅哥再帶着你搞幾個例子看一看!

二、代碼就是對數學邏輯的具體實作

資料結構:數組、連結清單、紅黑樹

算法邏輯:哈希、擾動函數、負載因子、拉鍊尋址、

其實我們所開發的業務程式,哪怕是CRUD也都是對數學邏輯的具體實作過程。隻不過簡單的業務有簡單的數學邏輯、複雜的業務有複雜的數學邏輯。數學邏輯是對資料結構的使用,(

例如:把大象裝進冰箱分幾步

)合理的資料的結構有利于資料邏輯的實作和複雜程度。

在我們常用的API中,HashMap 就是一個非常好的例子,既有非常好的資料結構的使用,也有強大的數學邏輯的實作。為此也讓 HashMap 成為開發過程中非常常用的API,當然也成為面試過程中最常問的技術點。

得物(毒)APP,8位抽獎碼需求,這不就是産品給我留的數學作業!

重點,HashMap 中涉及的知識點非常多,包括資料結構的使用、數組、連結清單、紅黑樹,也包括算法邏輯的實作:哈希、擾動函數、負載因子、拉鍊尋址等等。而這些知識如果可以深入的搞清楚,是完全不需要死記硬背的,也不需要為了面試造火箭。就像如下問題:

  • HashMap 怎麼來的?因為有非常多業務開發中需要key、value的形式存放擷取資料。
  • 為什麼要用哈希計算下标呢?因為哈希值求計算出的 key 具有低碰撞性。
  • 為什麼還要加擾動函數呀?因為擾動函數可以讓資料散列的均勻,如果HashMap中的資料都碰撞成短連結清單,就會大大降低HashMap的索引性能。
  • 為什麼會有連結清單呢?因為無論如何都有會有節點碰撞的可能,碰撞後HashMap選擇拉鍊尋址的方式存放資料。當然在 ThreadLocal 中采用的是斐波那契(Fibonacci)散列+開放尋址,感興趣也可以看看。
  • 為什麼連結清單會轉換樹呢?因為時間複雜度問題,連結清單的時間複雜度是O(n),越長越慢。
  • 為什麼樹是紅黑樹呢?紅黑樹具有平衡性,也就是黑色節點是平衡的,平衡帶來的效果就是控制整體樹高,讓時間複雜度最終保持在O(logn),否則都是一丿的樹就沒意義了。
  • 為什麼有個負載因子呢?負載因子決定HashMap的高矮胖瘦,負載你可以了解成一輛卡車能裝多少貨,裝的越多這一趟賺的也閱讀風險也越高,裝的越少跑的越快賺的也少。是以選擇了适當大小0.75。
  • 為什麼JDK8優化了資料擴容時遷移?那不就是因為計算哈希值求下标耗費時間嗎,已經找到了數學規律,直接遷移就可以了,提高性能。

看到了嗎? HashMap完全就是對資料結構的綜合使用,以及對數學邏輯的完美結合,才讓我們有了非常好用的HashMap。這些知識的學習就可以技術遷移到我們自己業務開發中,把有些業務開發優化到非常不錯的性能展現上。同時你的代碼也值得加薪!

哈希下标

圖 15-2 中涉及到的下标位置存放的資料,不是胡亂寫的。是按照 HashMap 中的計算邏輯找到的固定位置值。代碼如下:

for (int i = 1; i < 1000; i++) {
    String key = String.valueOf(i);
    int hash = key.hashCode() ^ (key.hashCode() >>> 16);
    int idx = (64 - 1) & hash;
    
    if (idx == 2) {
       // System.out.println(i + " Idx:" + idx);
    }
    if (idx == 62) {
        System.out.println(i + " Idx:" + idx);
    }
}
           

如果你需要英文的,那麼可以跑10萬單詞的字典表。關于HashMap的内容小傅哥已經整理到面經手冊中,連結:面經手冊 • 拿大廠Offer

三、得物(毒) 8位随機抽獎碼設計

1. 需求描述

得物(毒)APP,8位抽獎碼需求,這不就是産品給我留的數學作業!

圖 15-3 是我們模拟得物APP中關于抽獎碼需求的樣式圖,核心技術點包括:

  1. 需要一個8位的随機碼,全局唯一。
  2. 每個人可以獲得多個這樣的随機碼,随機碼閱讀中獎機率越大。
  3. 随機碼我們這裡的設計與毒App的展現形式略有不同,組成包括:大寫字母、小寫字母和數字。

在你沒有看實作方案前,你可以先考慮下這樣的唯一的随機碼該怎樣去生成。

2. 實作方案

2.1 基于Redis生成

int codeId = RedisUtil.incr("codeUUID");
String UUID = String.format("%08d", codeId);
System.out.println(UUID);

// 測試結果
00000001
00000002
00000003
           
  • 評分:⭐
  • 方案:基于 Redis 的 incr 方法,全局自增從0開始,以上是僞代碼。
  • 點評:以上方案不可用,除了并不一定能保證全局自增和可靠性外,有一個很大的問題是你的順序自增,把APP有多少人參加活動的資料暴露了。

2.2 随機數生成

Random random = new Random();
StringBuffer code = new StringBuffer();
for (int i = 0; i < 8; i++) {
    int number = random.nextInt(3);
    switch (number) {
        case 0:
            code.append((char) (random.nextInt(26) + 65)); // 65 ~ 90
            break;
        case 1:
            code.append((char) (random.nextInt(26) + 97)); // 97 ~ 122
            break;
        case 2:
            code.append((char) (random.nextInt(9) + 48)); // 48 ~ 97
            break;
    }
}
System.out.println(code.toString());

// 測試結果
qvY0Fqrk
8uyehK3H
U7z2v4qK
           
  • 評分:⭐⭐
  • 方案:基于随機數生成8位随機碼,相當于62^8次幂,有将近百萬億的随機數。
  • 點評:此方案在很多業務場景中都有使用,但這裡的實作還有一個問題,就是随性後的不唯一性,雖然我們知道這麼大體量很難出現兩個相同的。但如果随着業務營運日積月累的使用,終究會有兩個一樣的随機數,隻要出現就會是客訴。是以還需要保證唯一性,可以在随機數中加入年或者月的标記,按照這個體量落庫用防重方式保證唯一。當然你還可以有其他的方式來保證唯一

2.3 基于雪花算法

final static char[] digits = { '0', '1', '3', '2', '4', '7', '6', '5', '8',
        'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
        '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
        'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y',
        'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
        'Z', '0', '1', };
        
public static void main(String[] args) {
    SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0);
    System.out.println(idWorker.nextId());
    long code = idWorker.nextId();
    char[] buf = new char[64];
    int charPos = 64;
    int radix = 1 << 6;
    long mask = radix - 1;
    do {
        buf[--charPos] = digits[(int) (code & mask)];
        code >>>= 6;
    } while (code != 0);
    System.out.println(new String(buf, charPos, (64 - charPos)));
}

// 測試結果
uxdDQOG001
uxd8Uoj001
uxdERuG000
           
  • 評分:⭐⭐⭐
  • 方案:基于雪花算法的核心目的是,生成随機串的本身就是唯一值,那麼就不需要考慮重複性。隻需要将唯一值轉換為對應64進制的字元串組合就可以了。
  • 點評:這裡的思路很好,但有幾個問題需要解決。首先是雪花算法的長度是18位,在轉換為64位時會會有10位長的随機字元串組合,不滿足要求。另外大寫字母、小寫字母和數字組合是62個,還缺少2個不滿足64個,是以需要後面補充兩位,但這兩位生成的組合數需要廢棄。那麼,如果按照這個生成随機串且保證唯一的思路,就需要完善雪花算法,降低位數,在滿足業務自身的情況下,控制生成長度。

實作方案,終究不會一次就完美,還需要不斷的優化完善。除此之外也會有很多其他的思路,例如電商生成訂單号的方案也可以考慮設計,另外你以為這就完事了?當你已經工作多年,那麼你每一天其實都在解決技術問題也是數學問題,産品的需求也更像是數學作業!

加油數學老師!

四、總結

  • 好的程式實作離不開資料結構的設計、邏輯算法的完善、設計模式的考量,再配合符合業務發展和程式設計的架構才能搭建出更加合理的程式。
  • 在學習的過程中不要刻意去背答案、背套路,那不是理科内容的學習方式。隻有你更多的去實踐、去驗證,讓懂了就是真的懂,才更加舒心!
  • 本篇又扯到了這,想問一句你是害怕35歲,還是害怕自己能力不及年齡增長?想學就把知識學透,你騙不了面試官,隻能騙自己!

五、系列推薦

  • 握草,你竟然在代碼裡下毒!
  • 一次代碼評審,差點過不了試用期!
  • 資料結構,HashCode為什麼使用31作為乘數?
  • HashMap核心知識,擾動函數、負載因子、擴容連結清單拆分,深度學習
  • HashMap資料插入、查找、删除、周遊,源碼分析

公衆号:bugstack蟲洞棧 | 作者小傅哥多年從事一線網際網路 Java 開發的學習曆程技術彙總,旨在為大家提供一個清晰詳細的學習教程,側重點更傾向編寫Java核心内容。如果能為您提供幫助,請給予支援(關注、點贊、分享)!

繼續閱讀