2020年6月社招總結復盤


全部寫的是技術面試,hr面就沒寫了

1. 字節跳動

字節面試提前和內推人溝通好了,給安排6.23下午2點到5點面完技術面

1.1 一面(60分鍾)

面試時間:6.23 14:00 - 15:00
1. 自我介紹
這一部分提前要准備好,主要是講自己擅長什么技術,負責過哪些項目,抓住重點,什么平時的興趣愛好就不要說了,面試官不會care的

2. 用過哪些Java容器

3. ArrayList和LinkedList特點及各自應用場景

4. 說下TreeMap和LinkedHashMap

5. TreeMap怎么按照自己想要的順序排序

6. 說下HashMap

7. ConcurrentHashMap怎么實現的,1.7和1.8分別說下

8. ConcurrentHashMap怎么取的size值

9. 一個非常多元素的HashMap,rehash非常耗時,所以需要在它rehash過程中還能get、put,你有什么解決方案或者思路,談談你的理解
這題我一開始沒了解面試官說的什么意思,然后扯了很多相關的東西,比如:
針對大容量,帶參初始化,避免頻繁擴容
ConcurrentHashMap是fail-safe容器,遍歷時會在副本上遍歷,后續的寫操作對當前遍歷不可見

然后面試官又說那這個數據量特別大,rehash很耗時怎么辦,還在rehash后面的get、put怎么辦呢?
我說可以借鑒Redis的漸進式hash,hash操作分攤開

然后面試官說正在rehash,這時get元素怎么取值、put元素怎么插入?
我說在ConcurrentHashMap中當另一個線程put元素時,發現當前狀態為擴容時會協助其擴容,這樣提高性能

面試官說你不一定要去想已有的實現,可以自己想想有什么方案
我說由於正在rehash,比如正在從16擴容到32,那么對輸入key進行兩次hash,分別定位擴容前和擴容后的table索引位置,然后再在鏈表上遍歷

10. 怎么防止惡意請求刷接口

11. 說下ES的倒排索引

12. 那ES怎么切詞的呢,有寫過切詞插件嗎

13. 你在項目中用Redis的場景

14. 說下Redis有哪些數據類型

15. 說說你在項目中怎么用的這些數據類型

16. 一次請求拿出來所有的數據有什么問題,那怎么改進
Redis單線程阻塞后面請求,類似線上服務不要執行keys *,可以分批取數據

17. Redis怎么分片的

18. Redis的刪除策略

19. 寫代碼,很簡單的斐波那契數列
面試官后面跟我說一般前面回答的好,算法題就不會為難你的,如果你前面答的一般,就需要難的算法題來找你的亮點了
遇到這些簡單的算法題一定不能大意,這種寫出瑕疵了會非常的減分,各種邊界比如傳的參數小於0可以拋異常,還有注意命名規范
面試寫算法題要多和面試官交流,有的面試官故意不把題目說清楚,等着你去問,考察你的溝通交流能力,不要上來不交流就蒙頭寫代碼

20. 手撕代碼,leetcode98驗證二叉搜索樹

private List<Integer> res = new ArrayList<>();

public boolean isValidBST(TreeNode root) {
    inorder(root);
    for (int i = 1; i < res.size(); i++) {
        if (res.get(i) <= res.get(i - 1)) return false;
    }
    return true;
}

private void inorder(TreeNode root) {
    if (root == null) return;
    inorder(root.left);
    res.add(root.val);
    inorder(root.right);
}

寫完和面試官聊了聊優化點,其實可以不用全部中序遍歷完再判斷,不然樹比較大的話性能不夠好

21. 寫SQL
一個video表,里面有三個字段user_id、video_id、start_time
表里面的一行記錄表示的是某個user在在某個time看了某個video
要求:今天看不同視頻數量超過100個的用戶id的前三名

SELECT user_id, count(distinct(video_id)) as c
FROM video
WHERE start_time = '2020-6-23'
GROUP BY user_id
HAVING c > 100
ORDER BY c DESC
LIMIT 0, 3

SQL渣渣,自己很虛也不知道寫的對不對,面試官說沒什么問題

22. 你有什么想問我的嗎

面試官誇我基礎扎實,挺開心,這些天的復習刷lc總算沒有白費

1.2 二面(60分鍾)

面試時間:6.23 15:00 - 16:00
1. 自我介紹

2. 為什么還不到一年就出來看機會了
得不到成長,有一些重復性的工作,注意不要說前東家的壞話

3. 你希望你處於一個什么樣的工作環境
做的事有挑戰性,周圍都是優秀的人push自己更快的成長

4. 你的職業規划
計划趕不上變化,短期三年內主要是穩扎穩打學技術,提高實戰能力,提升快速把業務抽象成代碼的能力,拓寬技術廣度,部分技術深度達到top

5. 怎么根據0-5隨機函數得到0-8隨機函數

6. 緩存和DB之間怎么保證數據一致性
緩存預留模式
讀操作:先讀緩存,緩存沒有的話讀DB,然后取出數據放入緩存,最后響應數據
寫操作:先更新DB,再刪除緩存
為什么是刪除而不是更新呢?
原因很簡單,復雜場景下緩存不單單是DB中直接取出來的值,此外更新緩存的代價是很高的,頻繁更新的緩存到底會不會被頻繁訪問到?可能更新到緩存里面的數據都是冷數據,頻繁失效,所以一般用到再去加載緩存,lazy加載的思想

先更新DB,再刪除緩存的問題,如果更新DB成功,刪除緩存失敗會導致數據不一致
所以一般是先刪除緩存,再更新DB

還是有問題,A先刪除了緩存,但還沒更新DB,這時B過來請求數據,發現緩存沒有,去請求DB拿到舊數據,然后再寫到緩存,等A更新完了DB之后就會出現緩存和DB數據不一致的情況了

解決方案:
更新數據時,根據數據的唯一標識路由到隊列中,讀取數據時,如果發現數據不再緩存中,那么把讀取數據+更新緩存的操作,根據唯一標識路由之后,也發送到相應隊列中。一個隊列對應一個工作線程,線程串行拿到隊列里的操作一一執行

帶來的新問題:
可能數據更新頻繁,導致隊列中積壓了大量的更新操作,讀請求長時間阻塞,所以要壓測

7. 延時消息隊列怎么設計
Redis的zset

8. zset做延時隊列會有什么問題
死循環輪詢耗時

9. zset區間查詢怎么做的,時間復雜度多少
底層跳表,lgN的查詢,定位到起始位置順序遍歷即可

10. 你最擅長的技術有哪些
Java的集合、並發、虛擬機
MySQL和Redis的底層原理

11. 說下索引
二八原理、提升讀性能犧牲寫性能的數據結構
一個索引對應一顆B+樹
哈希、有序數組、二叉樹查詢的優缺點
那為什么不用跳表呢?

回表、索引覆蓋

最左前綴
哪些字段適合建索引、索引缺點

12. 為什么疫情期間要展示健康碼,別人又不看,又不掃描這個健康碼,那展示出來有什么用呢
還有就是為什么你最近去了疫情多發區,再掃健康碼就是紅色的了

其實就是拿出健康碼時你的位置信息會push上去

13. 火車票區間查詢怎么設計數據結構
比如上海去武漢,途經南京、合肥
現在要快速查詢出兩點之間票的庫存

我說借鑒跳表的思路可以實現

14. 手撕代碼,leetcode54螺旋矩陣

public List<Integer> func(int[][] matrix) {
    List<Integer> res = new ArrayList<>();
    int row = matrix.length;
    if (row == 0) return res;
    int col = matrix[0].length;
    int k = (Math.min(row, col) + 1) / 2;

    for (int i = 0; i < k; i++) {
        for (int a = i; a < col - i; a++) res.add(matrix[i][a]);
        for (int b = i + 1; b < row - i; b++) res.add(matrix[b][col - 1 - i]);
        for (int c = col - 2 - i; (c >= i) && (row - i - 1 != i); c--) res.add(matrix[row - 1 - i][c]);
        for (int d = row - 2 - i; (d > i) && (col - i - 1 != i); d--) res.add(matrix[d][i]);
    }

    return res;
}

15. 你有什么想問我的嗎

1.3 三面(60分鍾)

面試時間:6.23 16:00 - 17:00
1. 手撕代碼,模擬微信群隨機紅包,輸入金額、人數,返回金額數組;注意最小單位分;

private static final float MIN_COUNT = 0.01f;

public List<Float> func(float total, int k) {
    List<Float> res = new ArrayList<>(k);
    if (total <= 0 || k <= 0 || k * MIN_COUNT > total) return res;

    Random random = new Random();
    while (k != 0) {
        if (k == 1) {
            res.add(total);
        } else {
            float cur = MIN_COUNT + random.nextFloat(total - k * MIN_COUNT);
            res.add(cur);
            total -= cur;
        }
        k--;
    }

    return res;
}

nextFloat好像不能傳參數,不過這種白板寫代碼主要也是看思路

2. 寫SQL

找出所有語文考及格但是數學沒有考及格的學生

SELECT sno
FROM student s1, student s2
WHERE s1.sno = s2.sno
AND s1.subject = '語文'
AND s1.score = 

這題我寫了一半,面試官說思路對的就不用寫了

3. 聊項目,項目中的難點、模塊
然后還問了一些依賴模塊的底層實現

4. 項目的數據量以及QPS能達到多少

5. 你最近主要學了什么,怎么學的

6. 看過哪些MySQL書,名字叫什么

7. 說下RPC,與HTTP的區別

8. 你來字節最想得到什么
我說希望技術能突飛猛進,面試官說你別說的太虛,實實在在的說...

9. 你有什么想問我的嗎

2. Paypal

2.1 一面(60分鍾)

這一輪面我的面試官臨時有事情,讓他同事來面的
面試時間:6.17 9:30 - 10:30
1. 自我介紹

2. 聊項目,說項目中的模塊、技術難點

3. 聊下ES內部的一些機制

4. ForkPoolJoin相對於線程池的優點,及底層實現

5. 你最熟悉的技術有哪些

6. 詳細說下CMS和G1收集器

7. CMS怎么處理垃圾碎片的

8. GC Root有哪些

9. String的intern方法有什么用

10. 說下公平鎖、非公平鎖,為什么非公平鎖性能更高

11. 說下樂觀鎖、悲觀鎖

12. CAS的三個問題及解決方案

13. 手撕代碼,字符串反轉
這面試官是臨時客串的,所以就給了個很簡單的題

public String reverse(String str) {
    if (str == null || str.length() == 0) return str;
    char[] chars = str.toCharArray();

    for (int i = 0, j = str.length() - 1; i < j; i++, j--) {
        char ch = chars[i];
        chars[i] = chars[j];
        chars[j] = ch;
    }

    return new String(chars);
}

14. 你有什么想問我的嗎

2.2 二面(60分鍾)

面試時間:6.18 14:30 - 15:30
1. 自我介紹

2. 說下項目中的難點

3. 說下多線程中有哪些鎖

4. volatile關鍵字原理
無法保證原子性,對於i++這種讀改寫操作無法保證線程安全,可用Atomic

保證可見性,什么是可見性?
Java內存模型
內存屏障,讀取前插入load指令,拉取主內存最新值到工作內存
寫入后插入store指令,把工作內存最新值更新到主內存

保證有序性,禁止指令重排序
比如單例的雙檢鎖模式
new對象分三步:分配內存、初始化對象、引用指向分配的內存
禁止第2步第3步重排序,保證單例正確

5. 說下ES的底層實現

6. 大數據Spark、Hadoop、MapReduce有了解嗎

7. 100萬的數組怎么求最小的100個數字和最大的100個數字
最大堆和最小堆
或者用快排的partition

8. 手撕代碼,leetcode378有序矩陣中第K小的元素

public int kthSmallest(int[][] matrix, int k) {
    // 邊界判斷
    if (matrix == null || matrix.length == 0
            || k < 1 || k > matrix.length * matrix[0].length) return -1;

    // 容量為K的最大堆
    Queue<Integer> maxHeap = new PriorityQueue<>(k, Comparator.reverseOrder());

    for (int i = 0; i < matrix.length && i < k; i++) {
        for (int j = 0; j < matrix[0].length && j < k; j++) {
            if ((i + 1) * (j + 1) > k) break; // 根據數組特性剪枝

            if (maxHeap.size() != k) { // 堆沒放滿直接放進去
                maxHeap.offer(matrix[i][j]);
            } else if (maxHeap.peek() > matrix[i][j]) { // 堆放滿了則比較插入元素與堆頂元素
                maxHeap.poll();
                maxHeap.offer(matrix[i][j]);
            }
        }
    }

    return maxHeap.peek(); // 最后堆里面的元素就是前k小的元素,堆頂元素為第k小
}

2.3 三面(60分鍾)

面試時間:6.18 15:30 - 16:30
1. 自我介紹

2. 手撕代碼,leetcode15三數之和

public List<List<Integer>> threeSum(int[] arr) {
    List<List<Integer>> res = new ArrayList<>();
    if (arr == null || arr.length == 0) return res;
    Arrays.sort(arr);

    for (int i = 0; i < arr.length; i++) {
        if (arr[i] > 0) break; // 最小的數字都大於0直接break
        if (i > 0 && arr[i] == arr[i - 1]) continue; // 重復的數字跳過
        int l = i + 1;
        int r = arr.length - 1;

        while (l < r) {
            int sum = arr[i] + arr[l] + arr[r];
            if (sum == 0) { // 如果等於0 接着l++ r--找可能解
                res.add(Arrays.asList(arr[i], arr[l], arr[r]));
                while (l < r && arr[l] == arr[l + 1]) l++;
                while (l < r && arr[r] == arr[r - 1]) r--;
                l++;
                r--;
            } else if (sum < 0) l++; // 三數和小於0,左指針右移
            else r--; // 三數和大於0,右指針左移
        }
    }

    return res;
}

3. leetcode121買賣股票的最佳時機
三數之和寫的比較快,這題就沒讓寫了直接讓說思路
數據作差預處理后和leetcode53最大子序和就是一道題

4. JVM調優
總體思路:不同的場景選擇不同的參數(貌似是句廢話,但就是這樣)
比如線程競爭激烈的場景禁用偏向鎖可以提高性能
CMS收集器可以根據需要調節觸發GC的閾值

5. 詳細說說偏向鎖、輕量級鎖、重量級鎖
問:這幾個鎖是在什么地方有這樣的概念
答:synchronized的鎖升級過程
問:輕量級鎖是怎么實現的
答:CAS實現的
問:CAS的什么
答:類似AQS的CAS state狀態的過程
問:Mark Word的00、01、10、11放在什么地方
答:對象的對象頭
問:什么對象
答:線程搶鎖的對象
對於方法,搶占的是調用方法的對象的鎖;
對於靜態方法,搶占的是類鎖,也就是JVM方法區中class對象對應的鎖;
對於代碼塊,搶占的是synchronized括號里面對象的鎖;
問:輕量級鎖一般都是在線程競爭不激烈時用,那怎么判斷線程競爭不激烈呢
答:...
問:鎖怎么知道被哪個線程搶占的呢
答:owner區域,wait set區域
問:那偏向鎖是怎么實現的
答:完全沒有線程競爭的情況下,連CAS也不用了
問:你說偏向鎖和輕量級鎖線程ID都是在對象頭的Mark Word中,那他們之間有什么區別呢
答:...

6. 新生代配合CMS收集器用的什么收集器

7. CMS垃圾收集可以進行內存的整理,那內存整理的過程中對象地址發生了變動,JVM不可能去通知所有的引用這些對象的線程地址變了,那是通過什么方式保證了引用對象的線程還能找到這些對象的呢
面試官說你不要局限於JVM是怎么實現的,就假設你遇到這個問題,你怎么解決
JVM里面有一張表,這張表記錄了所有對象引用的地址和他被引用的地址,內存整理時會更新這張表,和操作系統里面的內存頁,地址映射類似

8. JVM里面會有幾個棧
幾個線程就有幾個棧,棧是線程私有的
問:棧里面存放了什么
答:棧幀
問:什么時間入棧,什么時間出棧
答:方法調用入棧,方法返回出棧
問:除了返回還有什么情況會出棧
答:拋出異常

面試官說我記得不錯那個輕量級鎖里面存放的鎖的標記位其實是在棧里面,有個引用從對象頭里面到棧里面

9. 為什么synchronized演變成重量級鎖后性能會下降
偏向鎖和輕量級鎖都是在用戶態
重量級鎖實現
synchronized——monitorenter/monitorexit——lock/unlock——mutex lock
重量級鎖需要到OS的內核態,很耗性能

問:那為什么要到內核態
答:保護OS,有的指令不能讓用戶執行
問:計算機通過什么來區分什么是高優先級的,或者說需要在內核態執行的
答:指令會分級,Intel的x86會有R0、R1、R2、R3指令等級

10. 說說volatile關鍵字
沒辦法保證線程安全
線程安全主要體現在三個方面:原子性、可見性、有序性
volatile只能保證可見性和有序性,保證不了原子性
比如i++,典型的讀改寫操作,volatile無法保證i的原子性,如果想要保證可以用Atomic代替
volatile主要保證可見性,可見性就是多個線程在同時修改一個變量時,A線程對變量的修改,B線程馬上就能知道,有點像數據庫里面臟讀的那種感覺
底層實現:加入內存屏障,JVM的內存模型,線程有自己的工作內存,然后有主內存,被volatile修飾的變量被讀取時,在讀取前插入load指令,把主內存中的最新值拉到工作內存中,被寫入時,在寫入后插入store指令,把工作內存中的最新值刷新到主內存中
還可以保證有序性,也就是volatile修飾的變量會禁止指令重排序,典型的應用就是單例模式的DCL,singleton變量是需要被volatile修飾的,具體過程:
先判斷singleton變量是不是等於null,然后synchronized鎖住類鎖,然后接着判斷singleton變量是不是等於null,等於null則new出對象,但是new出對象是需要三個步驟的
第一,為對象分配內存
第二,對象初始化
第三,對象引用指向第一步分配好的內存
那其實不加入volatile關鍵字修飾的話,第二步和第三步是可以重排序的,可以亂序執行
那如果這樣第一個線程過來時只執行到第二步,就是說還沒有初始化對象就已經引用上了,第二個線程再過來時,因為已經有了引用,所以直接返回對象,但對象還沒有初始化,所以用起來會報錯

問:那指令為什么會重排序呢
為了執行指令更快,指令間沒有相互依賴(讀后寫、寫后寫、寫后讀)的話,可以亂序執行

問:從硬件架構來說,CPU為什么會重排序
答:前面指令如果依賴的數據發生緩存缺失,那么需要去內存磁盤讀取數據,這個過程很耗時,如果不亂序執行的話,后面所有的指令都會被block住,這樣CPU的吞吐量上不去,所有會有亂序執行機制,讓后面沒有數據依賴關系的指令可以不用等前面指令執行完了再執行(IPC,指令級並發)

11. kafka有沒有用過

12. Redis怎么保證高可用
主從機制,哨兵機制

13. 你有什么想問我的嗎

2.4 四面(60分鍾)

面試時間:6.18 16:30 - 17:30
1. 為什么在百度不到一年就出來看機會
所在的組加班多,沒時間思考

2. 如果你來了pp發現這邊太閑了,學不到東西怎么辦
成長是個人的事情

3. 那要是你來了發現這邊加班也很嚴重怎么辦
可能外企對加班嚴重有誤解,八點就算加班嚴重了

4. 你這會都面了哪些公司
我說字節、微信、螞蟻
他說那看來加班還不算是個問題(手動狗頭.jpg

5. 面試官接着問我為什么想來我們公司

6. 面試官跟我說了不少他這么多年的工作體會
工作要超出預期,面對簡單無聊的需求也要保持熱情
保持對技術的追求,三四年之后再后頭看自己的競爭力在哪
有的人不知道怎么去提高
有的人只完成手頭上的事情,不願意去了解系統上下游各個模塊的細節,不願意去突破
在任何公司都不可能讓你只做自己喜歡的事情
在所有公司,你想要做有價值的事情而不是修修補補的工作,都是要靠你自己掙的,你作為新人來到一個公司,必須要學會給自己掙信用分,不然憑什么一個好的事情會交給你做
必須足夠專業,比如接觸到k8s,那這塊知識你都要熟悉,不能有什么含糊不清的地方,當然也不要走的太偏,需要學習的東西很多
要有野心,要有長期的計划和短期的執行
不管在哪都要抽出時間留給自己去學習去思考,哪怕你時間少點,慢點,但必須走在這條路上
多分享,多向別人學習

7. 問我機器學習和大數據這塊熟不熟悉
答不會
面試官說那我問你些java相關的問題

8. Spring Boot內部怎么實現像tomcat那樣直接把war包扔到某個目錄然后運行起來整個項目的

9. Spring Boot很大的jar包里面比如說有個lib目錄,那這個lib如果讓你去加載,怎么加載

10. 你平時在開發中怎么解決遇到的問題的

11. 你怎么深入的去學習JVM的

12. 你怎么去看的虛擬機的內存

13. Jconsole和VisualVM會拿到內存占用的一個趨勢,那你覺得什么樣的趨勢才是合理的

14. Full GC和OOM時,我怎么知道是哪一段代碼引起的內存溢出和泄漏

15. G1收集器有沒有Full GC
線上服務GC日志有沒有看過,G1 GC會有什么關鍵詞

16. 你有什么想問我的嗎

3. 微信支付

3.1 一面(75分鍾)

面試時間:6.15 19:00 - 20:15
1. 自我介紹

2. 操作系統中的大端和小端

3. 哈希和紅黑樹的特點和應用場景
哈希:查找插入刪除在沒有哈希碰撞的情況下都是O(1),性能高,以空間換時間
紅黑樹:BST樹 -> 更新操作導致樹左右不平衡,影響性能 -> 引入AVL樹 -> 維護成本高 -> 引入紅黑樹
紅黑樹O(lgN)的查找性能

紅黑樹的五個性質保證左右高度不超過2倍
所有節點到葉子節點具有相同的黑節點,
紅節點的子節點是黑節點,根節點和葉子節點都是黑節點
所以紅節點數一定小於黑節點數,所以一定不超過2倍

4. 排序
快排:最優平均時間復雜度O(lgN),最差時間復雜度O(N),最差出現在基本有序時,空間復雜度可以說是O(1),也可以說是O(N),不穩定排序
堆排:最優最差平均時間復雜度都是O(lgN),適合處理相對有序的數據,快排適合處理相對無序的數據,不穩定排序,空間復雜度O(1)
歸並:額外開個數組,空間復雜度O(N),最優最差平均時間復雜度都是O(lgN),穩定排序

5. 說下三次握手

6. 說下time wait,出現在哪一端,什么原因會導致time wait過多,怎么解決

7. TCP和UDP的區別

8. TCP收發包的方式,粘包拆包怎么解決

9. 網絡socket連接的過程

10. 說下Netty

11. select、poll、epoll區別

12. 說下Java的線程池,為什么要有線程池

13. 線程池內部有什么數據結構,ForkJoinPoll工作竊取怎么保證任務只被一個線程消費

14. 用過什么微服務的框架

15. 項目中怎么實現負載均衡的
Nginx
F5

16. 為什么一年不到就出來看機會

17. 說下項目中的難點、各個模塊功能

18. 你最擅長的技術有哪些

19. 項目用到了哪些存儲

20. MySQL怎么保證主從同步

21. 為什么不用MySQL的分庫分表,直接用ES

22. 項目的數據量多大,設計指標呢

23. ES索引里面都存儲了哪些字段

24. ES部署了幾個節點,怎么保證高可用

25. 調度平台模塊是怎么調度的,什么時間調度,讓你設計怎么實現

26. 項目中的超時機制

27. 任務處理失敗怎么處理

28. 手撕代碼,堆排
微信這邊是要求自己共享屏幕,在本地的IDEA里面寫,自己寫case跑通
並且寫完之后是需要把所有的代碼在騰訊會議上發送給面試官的

public class HeapSort {

    public static void main(String[] args) {
        int[] arr = {2, 3, 1, 4, 7, 5};
        new HeapSort().sort(arr);
        for (int i : arr) { // 1 2 3 4 5 7
            System.out.print(i + " ");
        }
    }

    public void sort(int[] arr) {
        int n = arr.length;

        for (int i = n / 2 - 1; i >= 0; i--) {
            heapify(arr, n, i);
        }

        for (int i = n - 1; i >= 0; i--) {
            int tmp = arr[i];
            arr[i] = arr[0];
            arr[0] = tmp;

            heapify(arr, i, 0);
        }
    }

    private void heapify(int[] arr, int n, int i) {
        int max = i;
        int left = 2 * i + 1;
        int right = 2 * i + 2;

        if (left < n && arr[left] > arr[max]) max = left;
        if (right < n && arr[right] > arr[max]) max = right;

        if (max != i) {
            int tmp = arr[max];
            arr[max] = arr[i];
            arr[i] = tmp;

            heapify(arr, n, max);
        }
    }
}

29. 手撕代碼,實現HashMap

public class MyHashMap {

    public static void main(String[] args) {
        MyHashMap hm = new MyHashMap();
        hm.put(1, 1);
        hm.put(2, 2);
        System.out.println(hm.get(1)); // 1
        hm.remove(1);
        System.out.println(hm.get(1)); // -1
    }

    private final int N = 100000; // 靜態數組長度100000

    private Node[] arr;

    public MyHashMap() {
        arr = new Node[N];
    }

    public void put(int key, int value) {
        int idx = hash(key);

        if (arr[idx] == null) { // 沒有發生哈希碰撞
            arr[idx] = new Node(-1, -1); // 虛擬頭節點
            arr[idx].next = new Node(key, value); // 實際頭節點
        } else {
            Node prev = arr[idx]; // 從虛擬頭開始遍歷

            while (prev.next != null) {
                if (prev.next.key == key) {
                    prev.next.value = value; // 直接覆蓋value
                    return;
                }
                prev = prev.next;
            }
            prev.next = new Node(key, value); // 沒有鍵則插入節點
        }
    }

    public int get(int key) {
        int idx = hash(key);

        if (arr[idx] != null) {
            Node cur = arr[idx].next; // 從實際頭節點開始尋找

            while (cur != null) {
                if (cur.key == key) {
                    return cur.value; // 找到
                }
                cur = cur.next;
            }
        }
        return -1; // 沒有找到
    }

    public void remove(int key) {
        int idx = hash(key);

        if (arr[idx] != null) {
            Node prev = arr[idx];

            while (prev.next != null) {
                if (prev.next.key == key) { // 刪除節點
                    Node delNode = prev.next;
                    prev.next = delNode.next;
                    delNode.next = null;
                    return;
                }
                prev = prev.next;
            }
        }
    }

    // 哈希函數
    private int hash(int key) {
        return key % N;
    }

    // 鏈表節點
    private class Node {
        int key;
        int value;
        Node next;

        Node(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }
}

3.2 二面(60分鍾)

面試時間:6.17 19:00 - 20:00
1. 自我介紹

2. 為什么不到一年就出來看機會

3. 一年工作對你來說最大的收獲是什么
非科班
之前學習沒有目的性,效果可能不好
工作后可以理論結合實踐

4. ES和Solr的區別

5. ES的倒排索引

6. 說下Java的反射

7. 了解分布式事務嗎
確實沒用過

8. 最近學了哪些技術

9. 這邊CPP你能接受嗎
當然可以接受了(手動狗頭.jpg

10. MySQL的事務隔離級別

11. MySQL的主從備份機制

12. 數據庫的表結構設計有哪些經驗

13. 數據庫的分庫分表

14. 說下項目中的某個功能怎么實現的

15. 手撕代碼,leetcode199二叉樹的右視圖

public class Main {

    public static void main(String[] args) {
        TreeNode t1 = new TreeNode(1);
        TreeNode t2 = new TreeNode(2);
        TreeNode t3 = new TreeNode(3);
        TreeNode t4 = new TreeNode(4);
        TreeNode t5 = new TreeNode(5);
        t1.left = t2;
        t1.right = t3;
        t2.left = t4;
        t2.right = t5;

        List<Integer> res = rightSideView(t1);
        for (Integer t : res) {
            System.out.print(t + " "); // 1 3 5
        }
    }

    public static List<Integer> rightSideView(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        if (root == null) return res;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);

        int cur = 1;
        int next = 0;
        List<Integer> in = new ArrayList<>();

        while (!queue.isEmpty()) {
            TreeNode t = queue.poll();
            in.add(t.val);
            cur--;

            if (t.left != null) {
                queue.offer(t.left);
                next++;
            }
            if (t.right != null) {
                queue.offer(t.right);
                next++;
            }
            if (cur == 0) {
                res.add(in.get(in.size() - 1));
                in = new ArrayList<>();
                cur = next;
                next = 0;
            }
        }

        return res;
    }

    static class TreeNode {
        int val;
        TreeNode left;
        TreeNode right;

        TreeNode(int val) {
            this.val = val;
        }
    }
}

16. 說下項目中的難點

17. 關鍵幀提取的原理

18. 限流怎么實現的

19. Redis分布式鎖

20. DB和緩存怎么保證數據的一致性

21. 平時怎么學習這些知識的

22. 你有什么想問我的嗎

23. 你家哪里的,來深圳有問題嗎

3.3 三面(40分鍾)

面試時間:6.23 11:20 - 12:00
1. 為什么一年出來看機會

2. 手撕代碼,二分查找,共享屏幕,自己寫case

public class Main {

    public static void main(String[] args) {
        int[] arr = {1, 3, 5, 7, 9};
        System.out.println(binarySearch(arr, 1) == 0);
        System.out.println(binarySearch(arr, 3) == 1);
        System.out.println(binarySearch(arr, 7) == 3);
        System.out.println(binarySearch(arr, 9) == 4);
        System.out.println(binarySearch(arr, 2) == -1);
    }

    public static int binarySearch(int[] arr, int target) {
        int lo = 0, hi = arr.length - 1;

        while (lo <= hi) {
            int mid = lo + (hi - lo) / 2;

            if (arr[mid] == target) return mid;
            else if (arr[mid] > target) hi = mid - 1;
            else lo = mid + 1;
        }

        return -1;
    }
}

3. 手撕代碼,leetcode226翻轉二叉樹

public class Main {

    static class TreeNode {
        int val;
        TreeNode left;
        TreeNode right;

        TreeNode(int val) {
            this.val = val;
        }
    }

    public static void main(String[] args) {
        TreeNode t1 = new TreeNode(1);
        TreeNode t2 = new TreeNode(2);
        TreeNode t3 = new TreeNode(3);
        TreeNode t4 = new TreeNode(4);
        TreeNode t5 = new TreeNode(5);
        t1.left = t2;
        t1.right = t3;
        t2.left = t4;
        t2.right = t5;

        List<List<Integer>> res = printTree(t1);
        for (List<Integer> t : res) {
            System.out.println(t);
        }

        System.out.println("樹翻轉之后---");

        res = printTree(reverseTree(t1));
        for (List<Integer> t : res) {
            System.out.println(t);
        }
    }

    public static List<List<Integer>> printTree(TreeNode root) {
        List<List<Integer>> res = new ArrayList<>();
        if (root == null) return res;

        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);

        int cur = 1;
        int next = 0;

        List<Integer> in = new ArrayList<>();

        while (!queue.isEmpty()) {
            TreeNode t = queue.poll();
            cur--;
            in.add(t.val);

            if (t.left != null) {
                queue.offer(t.left);
                next++;
            }

            if (t.right != null) {
                queue.offer(t.right);
                next++;
            }

            if (cur == 0) {
                res.add(new ArrayList<>(in));
                in.clear();
                cur = next;
                next = 0;
            }
        }

        return res;
    }

    public static TreeNode reverseTree(TreeNode root) {
        if (root == null) return null;
        TreeNode right = reverseTree(root.left);
        TreeNode left = reverseTree(root.right);
        root.left = left;
        root.right = right;
        return root;
    }
}

4. 怎么保證緩存和DB之間的數據一致性

5. 緩存穿透、緩存擊穿、緩存雪崩區別及解決方案

6. 怎么預估熱點key,怎么解決熱點key問題

7. 緩存的淘汰策略

8. CPP會嗎
不會...

9. 為什么在百度還要自己寫搜索的項目,沒有搜索中台提供服務調用嗎

10. 你們組都有什么業務

11. 你有什么想問我的嗎

3.4 四面(170分鍾)

面試時間:6.28 19:00 - 21:50
這一輪是交叉面
1. CPP熟悉嗎

2. 面試官共享他的屏幕讓我寫試卷
試卷上面基本都是選擇題,讓我選答案
包括編程語言、數據結構、網絡、匯編、密碼學、正則表達式等知識

3. 手撕代碼,字符串移位

public class Main {

    public static void main(String[] args) {
        System.out.println(func2("abcdef", 2)); // "efabcd"
    }

    public static String func(String str, int m) {
        if (str == null || str.length() == 0 || m < 1) return str;
        int len = str.length();
        m = m % len;

        return new StringBuilder(str).append(str).substring(m, m + str.length());
    }

    public static String func2(String str, int m) {
        if (str == null || str.length() == 0 || m < 1) return str;
        int len = str.length();
        m = m % len;

        char[] chars = str.toCharArray();
        swap(chars, 0, len - 1);
        swap(chars, 0, m - 1);
        swap(chars, m, len - 1);
        return String.valueOf(chars);
    }

    private static void swap(char[] chars, int a, int b) {
        for (int i = a, j = b; i < j; i++, j--) {
            char ch = chars[i];
            chars[i] = chars[j];
            chars[j] = ch;
        }
    }
}

一開始我是用了額外的O(N)空間,寫完面試官又要求O(N)時間復雜度,O(1)空間復雜度完成

4. 手撕代碼
給定字符串str,返回第一個括號不匹配的索引,若沒有返回-1

public class Main {

    public static void main(String[] args) {
        System.out.println(isBalance("aa(dss)") == -1);
        System.out.println(isBalance("aa(dss))") == 7);
        System.out.println(isBalance("aa((dss)") == 2);
        System.out.println(isBalance("aa(d(s)s)(") == 9);
        System.out.println(isBalance("aaaaaa") == -1);
    }

    public static int isBalance(String str) {
        if (str == null || str.length() == 0) return -1;
        int len = str.length();
        char[] chars = str.toCharArray();

        Stack<Character> stack = new Stack<>(); // 存放括號
        List<Integer> index = new ArrayList<>(); // 存放括號的索引位置

        for (int i = 0; i < len; i++) {
            if (!Arrays.asList('(', ')').contains(chars[i])) continue; // 遇到不是括號的字符直接跳過
            if (chars[i] == '(') { // 遇到左括號壓入棧中,並記錄索引
                stack.push('(');
                index.add(i);
            } else if (chars[i] == ')') {
                if (stack.isEmpty() || stack.peek() != '(') { // 遇到右括號,判斷棧頂是不是左括號,如果不是直接return
                    return i;
                } else { // 否則彈出棧頂元素並移除index最后一個元素
                    stack.pop();
                    index.remove(index.size() - 1);
                }
            }
        }

        return stack.isEmpty() ? -1 : index.get(index.size() - 1);
    }
}

5. 求兩個數組的交集
這題沒讓寫,直接說思路
直接HashSet或者用bitmap

6. 哈希存在的問題

7. Redis有序集合底層實現

8. 為什么用跳表不用紅黑樹
實現簡單
區間查詢

9. MySQL索引為什么用B+樹不用紅黑樹

10. zset有什么應用場景

11. ES的倒排索引

12. 項目中的難點

13. 為什么用ES不用MySQL

14. 項目的數據規模

15. 為什么ES和MySQL都是基於磁盤,ES的查詢性能要高

16. 面試官讓我共享屏幕講項目,邊講邊問
因為在簡歷中寫了項目的文檔鏈接,所以讓共享屏幕講

17. 你有什么想問我的嗎

3.5 五面(0分鍾)

第一輪面試結束時,面試官加了微信
四面結束后,一面的面試官微信跟我說五面是微信支付的大Boss面,還給了我一個ppt模板
說面試形式是自己提前寫好ppt然后給面試官講ppt(果然ppt是最好的語言啊),主要是項目中的難點
面試官和我說微信支付這邊主要的技術面都面完了,五面以及后面的部門經理面、hr面基本不會卡人
但考慮到微信支付在深圳要換城市換語言,再加上家里有些事情,自己碩士畢業也不小了
就放棄了接下來的面試,可惜了微信完美的面試發揮

4. 螞蟻金服

4.1 一面(60分鍾)

面試時間:6.19 19:00 - 20:00
1. 自我介紹

2. 挑個你印象最深的項目

3. 項目包含哪些模塊

4. 項目中最大的難點是什么

5. 任務在多個模塊間異步回調,如果某個模塊掛了怎么處理

6. 失敗任務怎么處理

7. ES的倒排索引是怎么建的

8. HashMap與ConcurrentHashMap的區別
典型的Java八股文,都問爛了

9. 說下MySQL的索引結構

10. 微信搶紅包怎么設計系統

11. 怎么切分紅包金額

12. 手撕代碼
問題:定義一個鏈表結構如下:

struct ListNode {
int value;
struct ListNode *next;
};
請寫一個程序,實現鏈表中重復節點的刪除(同一值的節點只保留一個),且鏈表順序保持不變。如,
初始鏈表:3 -> 2 -> 3 -> 4 -> 4 -> 1 -> 7 -> 8
節點去重后的鏈表:3 -> 2 -> 4 -> 1 -> 7 -> 8

注意:初始鏈表是非排序的鏈表。

class ListNode {
    int val;
    ListNode next;

    ListNode(int val) {
        this.val = val;
    }
}

public ListNode func(ListNode head) {
    if (head == null || head.next == null) return head;
    ListNode dummy = new ListNode(-1);
    dummy.next = head;

    ListNode pre = head;
    ListNode cur = head.next;
    Set<Integer> exist = new HashSet<>();
    exist.add(head.val);

    while (cur != null) {
        if (exist.contains(cur.val)) {
            ListNode nxt = cur.next;
            cur.next = null;
            pre.next = nxt;
            cur = nxt;
        } else {
            exist.add(cur.val);
            pre = cur;
            cur = cur.next;
        }
    }

    return dummy.next;
}

13. 你有什么想問我的嗎

4.2 二面(40分鍾)

面試時間:7.1 20:00 - 20:40
因為二面基本都問的項目,所以也寫一下
1. 自我介紹

2. 項目的應用場景

3. 視頻搜視頻業務處理邏輯

4. 圖搜圖響應時間

5. 項目存在的問題及改進

6. 技術上有哪些需要優化的點

7. 圖譜構建是全量還是增量的

8. 切詞是每天都需要重新去切嗎

9. 性能瓶頸是在依賴的服務模塊上嗎

10. 一年出來看機會的考慮是什么

11. 這會還面了哪些公司,你會怎么選offer

12. 你對工作地和出差是怎么考慮的

13. 你有什么想問我的嗎

4.3 三面(30分鍾)

面試時間:7.4 10:50 - 11:20
1. 自我介紹

2. 項目有沒有什么性能方面的優化

3. 項目比較有挑戰性的點

4. 項目中學到了什么,之后還需要學什么

5. 最近在學什么技術

6. 有沒有什么收獲可以分享一下

7. 知識圖譜詳細說下

8. 你在百度一年就想出來看機會的原因是什么

9. 晉升對你當前來說是最重要的嗎
送命題,那當然是成長最重要了(手動狗頭.jpg

10. 你有考慮過來杭州嗎

11. 你有什么想問我的嗎

5. 總結

  1. 面試本質是一個自我優勢展示的過程,不要把面試變成面試官問一句自己回答一句,主動拋出一些可能的點等面試官來問,比如我基本都被問到了DB和緩存之間怎么保證數據的一致性,其實都是我自己刻意往上引的,比如面試官說,你用過Redis嗎,你可以說,用過,一般用來作為緩存配合MySQL提高性能,需要注意它們之間數據的一致性問題(不要太刻意,自己把握分寸),面試官大概率會接着問你那怎么保證的
  2. 刷leetcode,刷leetcode,刷leetcode!重要的事情說三遍,作為一個程序員,代碼寫的爛就是原罪,面試時前面答得再天花亂墜算法寫的捉急也沒用,只會讓面試官產生你是背面經的感覺,所以寫算法題還是要快准狠,快速無bug寫出最優解在面試官那里是非常亮眼的,這個是沒有捷徑的,我自己這次面試leetcode高頻300題刷了好幾遍,面試算法很順利,當然最主要的還是刷中等難度的題,hard題性價比太低,反正我沒怎么刷...
  3. 不要眼高手低,不少小伙伴看面經覺得自己啥都會,但是自己會與面試過程中能清晰有層次的說出來是兩回事,並且自己會到什么程度,有個說法很好,判斷你是不是真的掌握一個知識的一個點在於你能不能通過通俗易懂的語言教會一個完全沒有相關知識背景的人,如果這可以做到,那對知識的掌握一定是融會貫通的,面試過程中一定可以信手拈來。比如volatile關鍵字的原理,能不能說出點面試官眼前一亮的東西,和別的同學蜻蜓點水不一樣的感覺,這還是不容易的
  4. 面試得自信,聲音自信,給面試官一種你啥都會穩如狗得感覺(實際內心慌得不行...),然后表達流暢,吐字清晰,不卑不亢,說話要有邏輯性,不能吞吞吐吐半天說不明白
  5. 得總結自己的面經,形成自己的知識體系,別人的面經寫的再好也是別人的,自己刷面經總結自己不會的點整理出來才是最有用的


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM