面試記錄 2019-06-19 上午 (掛)

中軟
無筆試,問的問題都很基礎(chǔ),但是自己都沒答好,腦殼是懵的。一剛開始答得不對,面試官也不多做解釋,越答越?jīng)]信心。

1、equals 和 hashCode 的區(qū)別。

我答成了 String 的 == 與 equals 的區(qū)別。
... ...
變形問題:Set/HashMap 是如何去重的?

Object 的 equals 方法默認(rèn)是兩個對象的引用的比較,和 == 是一樣的,意思就是指向同一內(nèi)存,地址則相等,否則不相等。

public boolean equals(Object obj) {
  return (this == obj);
}

Object 的 hashCode 是一個本地方法。

public native int hashCode();

This is typically implemented by converting the internal address of the object into an
integer, but this implementation technique is not required by the Java? programming language.

這句話看得我有點懵,Java 中的默認(rèn) hashCode 到底是不是地址轉(zhuǎn)換出來的,有很多人把默認(rèn) toString 打印出來的 @ 后面當(dāng)做地址。還是說默認(rèn)是地址,但不是必須這樣實現(xiàn)?我覺得應(yīng)該是后者,默認(rèn)就是地址,所以大家都不一樣,但是允許重寫。不管一不一樣,Hash 取模就會有沖突。

總結(jié): equals 比較兩個對象是否相等,默認(rèn)是比較地址,可以重寫,應(yīng)該具有對稱性、反射性、傳遞性、一致性。比如 String 重寫后是比較值。
hashCode 主要用于 hash 計算,不同對象的 hashCode 可以相同。要求 equals 為 true 的兩個對象的 hashCode 一定相等。通常重寫 equals 方法也要重寫 hashCode 方法。
有人就喜歡鉆牛角尖,說不一定要重寫 hashCode 。是的,是不一定,因為 hashCode 主要用于 set/map 集合判重邏輯,只要你保證不用于 set/map 集合,不重寫也不會有問題。

對于變形題目,Set/Map 怎么判重的:HashSet 的內(nèi)部是實現(xiàn) HashMap。

 public boolean containsKey(Object key) {
    return getNode(hash(key), key) != null;
}

重新計算 hash 值:

 static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

把 key 的 hashCode 高16位低16位進行異或,目的是為了保證高16位也參與運算,減少沖突。

final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> 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) {
            if (first instanceof TreeNode)
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            do {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}

這是1.8版本的,hash 取??磳?yīng)桶是否為空,為空直接加入;不為空從第一個開始比較 hash 值是否相同,再比較地址是否相同或者 key != null 比較 equals。
1.8 加入了紅黑樹,所以這里有個節(jié)點類型的判斷。

2、Spring MVC 的執(zhí)行過程。

雖然源碼自己也看了很多遍,還是說的不利索。

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
        ModelAndView mv = null;
        Exception dispatchException = null;

        try {
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);

            //  根據(jù)請求的 url 找 Handler,返回一個 HandlerExecutionChain。
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) {
                                // 沒找到,發(fā)出異常
                noHandlerFound(processedRequest, response);
                return;
            }

            // 為 handler 找 adapter 適配器 。
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

            // Process last-modified header, if supported by the handler.
            String method = request.getMethod();
            boolean isGet = "GET".equals(method);
            if (isGet || "HEAD".equals(method)) {
                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                    return;
                }
            }
                        // 循環(huán)執(zhí)行 pre 攔截器
            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            // 真正調(diào)用 handler,返回 modelAndView,如果使用 @RestController 注解,返回空.
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }

            applyDefaultViewName(processedRequest, mv);

                        // 執(zhí)行 post 攔截器
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        catch (Exception ex) {
            dispatchException = ex;
        }
        catch (Throwable err) {
            // As of 4.3, we're processing Errors thrown from handler methods as well,
            // making them available for @ExceptionHandler methods and other scenarios.
            dispatchException = new NestedServletException("Handler dispatch failed", err);
        }
        //  渲染 view,返回結(jié)果 
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Throwable err) {
        triggerAfterCompletion(processedRequest, response, mappedHandler,
                new NestedServletException("Handler processing failed", err));
    }
    finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            // Instead of postHandle and afterCompletion
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        }
        else {
            // Clean up any resources used by a multipart request.
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }
}

簡單的說:找 handler、找 adaptor、執(zhí)行 pre 攔截器、調(diào)用 handler 返回 modelAndView、執(zhí)行 post 攔截器、渲染 view。

springmvc.png

(1) 客戶端通過url發(fā)送請求

(2-3) DispatcherServlet 接收到請求,通過 HandlerMapping 到對應(yīng)的handler,并將 URI 映射的控制器 controller 組成處理器執(zhí)行鏈返回給 Dispatcher Servlet。

(4) 找 HandlerAdapter

(5-7)由找到的 HandlerAdapter 調(diào)用相應(yīng)的 Handler 進行處理并返回 ModelAndView 給 DispatcherServlet

(8-9)DispatcherServlet 將獲取的 ModelAndView 傳遞給視圖解析器解析,返回具體 View

(10)DispatcherServlet 對 View 進行渲染視圖(即將模型數(shù)據(jù)填充至視圖中)

(11)將頁面響應(yīng)給用戶

組件:

  • DispatcherServlet:前端控制器
    用戶請求到達前端控制器,它就相當(dāng)于mvc模式中的c,DispatcherServlet 是整個流程控制的中心,由它調(diào)用其它組件處理用戶的請求,DispatcherServlet 的存在降低了組件之間的耦合性。

  • HandlerMapping:處理器映射器
       HandlerMapping 負(fù)責(zé)根據(jù)用戶請求 URL 找到 Handler 即處理器,Spring MVC 提供了不同的映射器實現(xiàn)不同的映射方式,例如:配置文件方式,實現(xiàn)接口方式,注解方式等。

  • Handler:處理器
       Handler 是繼 DispatcherServlet 前端控制器的后端控制器,在 DispatcherServlet的控制下Handler對具體的用戶請求進行處理。由于 Handler 涉及到具體的用戶業(yè)務(wù)請求,所以一般情況需要程序員根據(jù)業(yè)務(wù)需求開發(fā) Handler。

  • HandlerAdapter:處理器適配器
      通過 HandlerAdapter 對處理器進行執(zhí)行,這是適配器模式的應(yīng)用,通過擴展適配器可以對更多類型的處理器進行執(zhí)行。

  • ViewResolver:視圖解析器
      View Resolver 負(fù)責(zé)將處理結(jié)果生成 View 視圖,View Resolver首先根據(jù)邏輯視圖名解析成物理視圖名即具體的頁面地址,再生成 View 視圖對象,最后對 View 進行渲染將處理結(jié)果通過頁面展示給用戶。

  • View:視圖
      springmvc框架提供了很多的View視圖類型的支持,包括:jstlView、freemarkerView、pdfView等。我們最常用的視圖就是jsp。

一般情況下需要通過頁面標(biāo)簽或頁面模版技術(shù)將模型數(shù)據(jù)通過頁面展示給用戶,需要由程序員根據(jù)業(yè)務(wù)需求開發(fā)具體的頁面。

3、HashMap 先擴容還是先轉(zhuǎn)紅黑樹。

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
static final int TREEIFY_THRESHOLD = 8;
static final int MIN_TREEIFY_CAPACITY = 64;
static final float DEFAULT_LOAD_FACTOR = 0.75f;

初始化桶大小16,轉(zhuǎn)紅黑樹有兩個條件,一個是沖突的 key 超過8個,并且桶的數(shù)量大于等于64。0.75是負(fù)載因子,當(dāng)前有0.75的桶都有值了,就會進行擴容,擴大為當(dāng)前的2倍,再進行 reHash。

所以對于這道題,判斷沖突的key大于等于8個時,嘗試轉(zhuǎn)紅黑樹,如果桶的數(shù)量小于64,則擴容;否則轉(zhuǎn)紅黑樹。

 for (int binCount = 0; ; ++binCount) {
         if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
             treeifyBin(tab, hash);
         break;
 }

binCount 從 0 開始的,所以要減 1。

 final void treeifyBin(Node<K,V>[] tab, int hash) {
    int n, index; Node<K,V> e;
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
        resize();
    else if ((e = tab[index = (n - 1) & hash]) != null) {
        TreeNode<K,V> hd = null, tl = null;
        do {
            TreeNode<K,V> p = replacementTreeNode(e, null);
            if (tl == null)
                hd = p;
            else {
                p.prev = tl;
                tl.next = p;
            }
            tl = p;
        } while ((e = e.next) != null);
        if ((tab[index] = hd) != null)
            hd.treeify(tab);
    }
}

這是轉(zhuǎn)紅黑樹的方法,先判斷桶的大小,小于 64 就擴容。

4、@Controller 與 @RestController

@RestController 注解相當(dāng)于@ResponseBody + @Controller合在一起的作用。

  1. 如果只是使用 @RestController 注解 Controller,則 Controller 中的方法無法返回 jsp 頁面,或者 html,在上面 Spring MVC 調(diào)用過程中 adapter 調(diào)用 handler 的時候 modelAndView 就會返回 null,直接把 return 的內(nèi)容放到 response 里了。

  2. 如果需要返回到指定頁面,則需要用 @Controller配合視圖解析器InternalResourceViewResolver才行。
    如果需要返回JSON,XML或自定義mediaType內(nèi)容到頁面,則需要在對應(yīng)的方法上加上@ResponseBody注解。

5、Spring Cloud 有哪些組件?
Eureka
Ribbon
Feign
Config
Hystrix
Hystrix DashBoard
Bus
Data Stream
6、JVM 內(nèi)存模型
不要答成運行時數(shù)據(jù)區(qū)域了。
內(nèi)存模型

下面這個是運行時數(shù)據(jù)區(qū)域:
虛擬機

jvm.png

7、ConcurrentHashMap
ConcurrentHashMap

ConcurrentHashMap 和 HashMap 實現(xiàn)上類似,最主要的差別是 ConcurrentHashMap 采用了分段鎖(Segment),每個分段鎖維護著幾個桶(HashEntry),多個線程可以同時訪問不同分段鎖上的桶,從而使其并發(fā)度更高(并發(fā)度就是 Segment 的個數(shù))。
1.8 采用 CAS。

8、Mysql 查詢優(yōu)化
EXPLAIN

9、InnoDB 與 MyISAM 的區(qū)別。
對 MyISAM 不是很了解。
主要區(qū)別是 MyISAM 不支持事務(wù)。
MyISAM

10、volatile 有什么作用,是不是原子性?
保證可見性: 修飾變量使其修改之后能夠立馬被其他線程看見。原理是 volatile 修飾的變量讀取時必須從主存中獲取,修改后要立馬寫入主存,從而保證了修改之后能夠立馬被其他線程看見。
保證有序性:防止編譯器重排。會加入一個內(nèi)存屏障,防止后面的執(zhí)行排到前面。
不保證原子性:單一的讀/寫是具備原子性的,復(fù)合操作,比如 i++ 不具備原子性。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容