java打字員面試題目總結含答案

目錄

1、junit用法,before,beforeClass,after, afterClass的執行順序

2、Nginx的請求轉發算法,如何配置根據權重轉發

3、用hashmap實現redis有什麼問題

4、mvn install和mvn deploy的區別

5、兩個Integer的引用對象傳給一個swap方法在方法內部交換引用,返回後,兩個引用的值是否會發現變化

6、ReenTrantLock與Synchronized的區別

7、redis的應用場景

8、redis支持的數據類型

9、redis的數據淘汰策略

10、用java實現一個簡單的lru算法

11、redis的lru算法實現

12、redis中zset關於跳錶的數據結構

13、redis的管道pipeline

14、transient關鍵字

15、進程簡介及進程間通信方式

16、爲什麼不推薦使用Executors直接創建線程池

17、如何控制線程池線程的優先級

18、Spring如何解決循環依賴

19、事務隔離級別

20、mysql的MVCC多版本併發控制

21、Innodb的行鎖

22、MyIsam和Innodb的區別?何時使用MyIsam

23、爲什麼說B+樹比B樹更適合數據庫索引?

24、爲什麼JDBC需要打破雙親委派模型

25、Spring中BeanFactory和ApplicationContext的區別

26、CDN是什麼

27、哈希衝突是什麼及解決方式

28、二分查找簡介

29、happens-before原則

30、java內存模型簡介

31、如何保證可見性

32、UML中四大關係

33、tcp長連接和短連接的區別

34、父類的靜態方法能否被子類重寫

35、抽象類的意義

36、靜態內部類的意義

37、談談解析與分派

38、String爲什麼要設計成不可變的

39、簡單說一下泛型

40、談談淺拷貝和深拷貝

41、tcp可靠性傳輸的實現

42、聚集索引和非聚集索引


均爲自己思考整理,本意只做個人記錄,看到的都是緣分

1、junit用法,before,beforeClass,after, afterClass的執行順序

@Before:初始化方法,對於每一個測試方法都要執行一次(注意與BeforeClass區別,後者是對於所有方法執行一次)
@After:釋放資源,對於每一個測試方法都要執行一次(注意與AfterClass區別,後者是對於所有方法執行一次)
@Test:測試方法,在這裏可以測試期望異常和超時時間 
@Test(expected=ArithmeticException.class)檢查被測方法是否拋出ArithmeticException異常 
@Ignore:忽略的測試方法 
@BeforeClass:針對所有測試,只執行一次,且必須爲static void,使用JUnitCore.runClasses(TestClass.class)的時候可以驗證該方法只執行了一次
@AfterClass:針對所有測試,只執行一次,且必須爲static void 

所以執行順序爲:@BeforeClass -> @Before -> @Test -> @After -> @AfterClass

2、Nginx的請求轉發算法,如何配置根據權重轉發

Nginx的負載均衡是從 upstream 模塊中所定義的服務器列表選取一臺用於發送請求,以達到請求轉發的效果,自帶的總共有4種算法:
1)輪詢方式(默認方式,就是把請求平均的轉發給各個服務器)
2)權重方式(通過配置weight參數實現,權重值較大的服務器收到請求的概率則較大,可以根據服務器的性能配置不同的權重配比)
3)ip_hash方式(根據客戶端的ip計算出一個hash值,保證每個ip對應的請求都能訪問到同一後臺服務器,在一定程度上避免了session不能跨服務器的問題)
4)least_conn(最少連接數策略,把請求轉發給連接數較少的服務器,適用於請求處理時間不一致導致服務器過載的情況)

3、用hashmap實現redis有什麼問題

hashmap實現的是本地緩存,而redis是分佈式緩存,因此使用hashmap實現redis會有如下問題:
1、redis中的緩存在重啓後仍然可以生效,而hashmap是內存對象,一重啓就沒了
2、redis中的緩存可以被多個主機使用,即redis可以分佈式部署,而hashmap只能在當前機器上使用
3、hashmap不是線程安全的,並且多線程同時調用hashmap的resize方法後,後續調用get方法時,其鏈表結構可能會形成閉環,然後進入死循環,可以考慮concurrentHashMap

4、mvn install和mvn deploy的區別

前者是將打包的包部署到本地maven倉庫,以供其他項目使用,而後者不僅部署到本地maven倉庫,同時部署到遠程maven私服倉庫

5、兩個Integer的引用對象傳給一個swap方法在方法內部交換引用,返回後,兩個引用的值是否會發現變化

不會,java裏方法的參數傳遞方式只有值傳遞,所謂按值傳遞是傳遞的值的拷貝,按引用傳遞是傳遞的引用的地址值,java裏除了基本類型和string,其他都是按引用傳遞。調用方法時,實際上就是賦值操作,題目中的交換引用應該是用的'='賦值操作符,針對引用類型,只會改變引用中所保存的地址,原來的地址被覆蓋掉,但是原來的對象不會被改變,除非該對象本身提供了改變自身的方法並在方法中調用

6、ReenTrantLock與Synchronized的區別

1)前者是jdk實現的鎖,後者是依賴jvm實現的,由虛擬機自身進行維護,即隨着jdk的升級,鎖的性能也會隨之上升
2)前者的顆粒度和靈活度更細,但是使用起來會略顯麻煩,需手動加鎖、釋放鎖,而後者由編譯器去保證鎖的獲取和釋放
3)前者可以指定使用公平鎖或非公平鎖,而後者只能使用非公平鎖(所謂公平鎖是指先等待的線程先獲取鎖)
4)前者提供了Condition類來實現分組喚醒需要喚醒的線程們,而後者要麼隨機喚醒一個要麼喚醒全部線程
5)前者提供了一種能夠中斷等待鎖的線程的機制,通過lock.lockInterruptibly()來實現這個機制

7、redis的應用場景

緩存、分佈式鎖、利用豐富的數據結構來實現不同的業務場景(計數器、排行榜、好友關係、簡單消息隊列)

8、redis支持的數據類型

字符串(string)、列表(list)、集合(set)、散列(hash)、有序集合(zset)

9、redis的數據淘汰策略

1)no-eviction(默認):禁止驅逐數據,對於寫請求不再提供服務,直接返回錯誤(del請求和特殊請求除外)
2)volatile-lru:從設置了過期時間的key中使用lru算法進行淘汰(lru:淘汰最近最少使用)
3)volatile-ttl:從設置了過期時間的key中根據過期時間進行淘汰,越早過期的優先被淘汰
4)volatile-random:從設置了過期時間的key中隨機淘汰
5)allkeys-lru:在所有key中使用lru算法進行淘汰
6)allkeys-random:在所有key中隨機淘汰
ps:針對lru和ttl,挑選的數據並不是絕對的最近最少使用或過期時間最早,redis只是保證在隨機挑選中的數據是滿足上述條件的;當使用volatile-策略時,如果沒有key可以淘汰,則和no-eviction一樣返回錯誤

10、用java實現一個簡單的lru算法

lru算法是一種緩存置換算法,如果一個數據在最近一段時間沒有被使用,那麼將來被使用到的可能性也很小,所以就可以淘汰掉。實現一個簡單的lru算法主要就是對鏈表的考察,重點就是在put和get時將對應節點移動到鏈表頭部,鏈表的尾部便是最近最少使用。

public class LRUCache<K, V> {

    /**
     * 支持的最大節點數
     */
    private int capacity;
    /**
     * 當前節點數
     */
    private int count;
    /**
     * 節點集合
     */
    private Map<K, Node<K, V>> nodeMap;
    /**
     * 頭結點
     */
    private Node<K, V> head;
    /**
     * 尾節點
     */
    private Node<K, V> tail;

    /**
     * 初始化
     * @param capacity
     */
    public LRUCache (int capacity) {
        this.capacity = capacity;
        nodeMap = new HashMap<>();
        // 初始化頭尾節點,減少判斷爲空的代碼
        Node headNode = new Node(null, null);
        Node tailNode = new Node(null, null);
        headNode.next = tailNode;
        tailNode.pre = headNode;
        this.head = headNode;
        this.tail = tailNode;
    }

    public void put(K key, V val) {
        Node node = nodeMap.get(key);
        if (null == node) {
            // 判斷是否超過最大容量
            if (count >= capacity) {
                removeNode();
            }
            node = new Node(key, val);
            nodeMap.put(key, node);
            // 新加入的默認移動到頭結點
            moveHead(node);
            count++;
        } else {
            // 先移除原先節點,然後再移動到頭結點
            removeNode(node);
            moveHead(node);
        }
    }

    public Node get(K key) {
        Node node = nodeMap.get(key);
        if (null != node) {
            // 先移除原先節點,然後再移動到頭結點
            removeNode(node);
            moveHead(node);
        }
        return node;
    }

    /**
     * 測試用,按鏈表順序輸出
     */
    public void printNode(Node node) {
        if (null != node) {
            if (null != node.key)
                System.out.println(node.key +", value:" + node.value);
            printNode(node.next);
        }
    }

    public Node<K, V> getHead() {
        return head;
    }

    /**
     * 移除末尾節點
     */
    private void removeNode() {
        Node temp = tail.pre;
        removeNode(temp);
        nodeMap.remove(temp.key);
        count--;
    }

    /**
     * 移除指定節點,該方法只作刪除,不作容量減少
     * @param node
     */
    private void removeNode(Node node) {
        Node next = node.next;
        Node pre = node.pre;
        next.pre = pre;
        pre.next = next;
        node.next = null;
        node.pre = null;
    }

    /**
     * 移動到頭結點
     * @param node
     */
    private void moveHead(Node node) {
        Node temp = head.next;
        temp.pre = node;
        node.next = temp;
        node.pre = head;
        head.next = node;
    }

}

class Node<K, V> {
    K key;
    V value;
    /**
     * 上一節點引用
     */
    Node pre;
    /**
     * 下一節點引用
     */
    Node next;
    public Node (K key, V value) {
        this.key = key;
        this.value = value;
    }
}

11、redis的lru算法實現

redis採用的是近似lru算法實現,即並不是嚴格意義上的lru實現,近似lru通過隨機採樣法淘汰數據,每次取出5個key(默認是5個,可通過修改配置文件修改樣本數,建議10個,其淘汰結果最接近嚴格意義上的算法),然後從這些key裏面淘汰最近最少使用,redis爲了實現近似lru算法,給每個key增加了一個26bit的字段,用於存儲該key最後一次被訪問的時間
redis3.0對近似lru算法進行了優化,新算法會維護一個候選池,池中的數據根據訪問時間(訪問時間越大,說明最近被訪問)進行排序,第一次隨機選取的key都會放入池中,隨後每次隨機選取的key只有在訪問時間小於池中最小訪問時間纔會被放入池中
redis4.0新加了lfu算法,核心思想是根據key的最近被訪問的頻率進行淘汰,根據頻率來比較的話相對lru算法來說更準確,lfu也有兩種策略:volatile-lfu、allkeys-lfu

12、redis中zset關於跳錶的數據結構

跳躍表(skiplist)是一種基於有序鏈表的擴展(多層鏈表結構),即在原先鏈表上提取出關鍵節點作分層處理,即索引,查找時先從從索引層開始,然後再回到原鏈表,保證上一層節點數是下一層的一半,鏈表新增時同樣是先確定插入的位置,然後放進去之後需要動態增加索引節點,這裏採用的是拋硬幣的方式,即50%的概率判斷是否需要提拔到上一層作爲索引節點。其查詢/插入/刪除 複雜度o(lgn),換句話來說,跳錶是可以實現二分查找的有序鏈表

13、redis的管道pipeline

管道pipeline可以一次性發送多條命令並在執行完後一次性將結果返回,其通過減少客戶端與redis的通信次數來實現降低往返延時時間,而且pipeline實現的原理是隊列,遵循先進先出,因此保證了數據的順序性。

14、transient關鍵字

transient關鍵字就是說讓某些被修飾的成員屬性變量不被序列化,當某些屬性的生命週期僅存於調用者的內存中,而不想被寫到磁盤裏持久化或一些敏感屬性不想在網絡操作中被傳輸,此時可以使用transient關鍵字,其最大作用就是爲了節省存儲空間,但也會導致某些屬性需要重新計算,總的來說利大於弊。
ps:一個靜態變量不管是否被transient修飾,都不能被序列化

15、進程簡介及進程間通信方式

進程可以認爲是程序執行時的一個實例,是系統進行資源分配和調度的基本單位,每個進程都擁有獨立的內存空間,一個進程無法直接訪問另一個進程的變量和數據結構,進程之間要交換數據必須通過內核,在內核中開闢一塊緩衝區,進程1把數據從用戶空間拷到內核緩衝區,進程2再從內核緩衝區把數據讀走,常用的通信方式大概有5種:管道、信號、消息隊列、共享內存、套接字

16、爲什麼不推薦使用Executors直接創建線程池

1)LinkedBlockingQueue緩存隊列沒有指定最大值,導致其是無界的,無限增大最終將內存撐爆
2)部分線程池的線程數是Integer.MAX_VALUE,由於這個數量級已經夠大,當執行線程數的增多和線程沒有及時回收,最終也會將內存撐爆
建議使用ThreadPoolExecutor的方式自己新建線程池

17、如何控制線程池線程的優先級

jdk缺省的線程池不支持優先級調度的功能,需要使用PriorityBlockingQueue替代BlockingQueue,優先級隊列要求放入隊列的線程必須實現Comparable接口,該隊列會根據內部存儲的每個元素的compareTo方法比較每個元素的大小

18、Spring如何解決循環依賴

(詳細請看筆記)簡單版:spring使用三級緩存來解決field屬性的循環依賴,主要是基於java的引用傳遞特性實現,當獲取到對象的引用時,對象的屬性是可以延後設置的,在初始化的第一步完成後就將該對象加入到三級緩存中,供外部使用

19、事務隔離級別

讀未提交(Read uncommitted):能讀到未提交的內容,無特殊情況一般不會該種隔離級別
讀已提交(Read committed):只能讀到已提交的內容,能避免出現髒讀現象,Sql Server和Oracle的默認隔離級別即爲它
可重複讀(Repeatable read):爲了避免不可重複讀現象,mysql的默認隔離級別即爲它
串行化(Serializable):事務串行化順序執行,一個一個排隊等待,效率低基本不用

20、mysql的MVCC多版本併發控制

由於MVCC並沒有一個統一的實現標準,因此不同的數據庫不同的存儲引擎所實現的MVCC都不盡相同,以Innodb版本爲例,MVCC是通過保存數據在某個時間點的快照來實現的,通過這個快照可以提供一定級別(語句級或事物級)的一致性讀取,不管事務執行多長時間,事務看到的數據都是一致的。
MVCC在大多數情況下代替了行鎖,實現了對讀的非阻塞,讀不加鎖,讀寫不衝突,缺點是每行記錄都需要額外的存儲空間,需要做更多的行維護和檢查工作。
Innodb通過undo log保存了已更改行的舊版本的信息的快照,其內部實現中爲每一行數據增加了三個隱藏列用於實現MVCC
ps:MVCC只在READ COMMITED 和 REPEATABLE READ 兩個隔離級別下工作

21、Innodb的行鎖

Innodb實現了兩種形式的行鎖,分爲共享鎖和排他鎖。
共享鎖:又稱讀鎖,多個事務對於同一行數據可共享一把鎖,都能訪問到數據,但是隻能讀不能改,可通過select...lock in share mode語句加鎖
排他鎖:又稱寫鎖,該鎖不能與其他鎖並存,但是獲取該鎖的事務可以對數據進行讀取和修改,可通過select...for update語句加鎖
Innodb中還存在兩類意向鎖,均屬於表鎖,簡單說來存在意向鎖,則必定存在行級鎖,其作用就是爲了試圖在某個表應用共享或排他鎖時無需檢查該表的所有行,而只需檢查表上是否有意向鎖即可。
ps:Innodb行鎖是通過給索引上的索引項加鎖來實現的,只有通過索引條件檢索數據,Innodb纔會使用行鎖,否則將使用表鎖;同時排他鎖必須在事務塊(begin/commit)中才能生效

22、MyIsam和Innodb的區別?何時使用MyIsam

前者不支持外鍵、事務、行鎖,執行新增或更新操作時使用的是表級鎖定,查詢時該引擎效率很高,佔用的存儲空間小;後者均支持,佔用的存儲空間較大,也是mysql5.7版本之後默認的存儲引擎。
如果你的應用對查詢性能要求較高,就需要使用MyIsam,它的索引和數據是分開的(非聚集索引),而且其索引是壓縮的,可以更好的利用內存,所以它的查詢性能明顯優於Innodb。

23、爲什麼說B+樹比B樹更適合數據庫索引?

B樹在解決了IO性能的同時並沒有解決元素遍歷的效率低下問題,因此B+樹應運而生,B+樹只需要去遍歷葉子節點就可以實現整棵樹的遍歷,而且在數據庫中基於範圍查詢是非常頻繁的,而B樹不支持這樣的操作或者說效率太慢。
B+樹:非葉節點僅保存索引,所有與記錄相關的信息都存在葉子節點,且所有葉子節點組成一個有序鏈表。
文件系統和數據庫的索引數據都存在磁盤上的,當數據量過大的時候可能無法一次性加載進內存,這時B/B+樹的多路存儲威力就出來了,可以每次加載進B/B+樹的一個節點,然後一步步往下找;同時大規模數據存到磁盤的時候,定位數據也是一個非常耗費時間的操作,通過B/B+樹進行優化後便能提高磁盤讀取時定位的效率,這一切都取決於他們的多路存儲。

24、爲什麼JDBC需要打破雙親委派模型

主要是因爲原生的jdbc中Driver驅動本身只是一個接口,具體的實現是由不同數據庫廠商去實現的,而原生的jdbc中的類是放在rt.jar包的,是由啓動類加載器進行加載的,然後在jdbc中的driver類需要去動態加載不同數據庫類型的diver類,而啓動類加載器肯定加載這些自定義實現的類,因此java引入了一個線程上下文加載器(該加載器可以在在創建線程時指定類加載器,如果沒指定則先從父線程中繼承一個,如果全局範圍內都沒有,則默認是應用程序類加載器)

25、Spring中BeanFactory和ApplicationContext的區別

BeanFactory:spring中比較古老和原始的factory,無法支持spring插件,如:aop、web應用等
ApplicationContext:是BeanFactory的子類,由於古老的BeanFactory無法滿足不斷更新的spring的需求,於是ApplicationContext就基本替代了它的工作,以一種更面向框架的工作方式以及對上下文進行分層和實現繼承,並在這個基礎上對功能進行擴展,前者可以完成的,後者基本都能完成。
區別:使用BeanFactory加載xml時,只是說實例化了該容器,而容器中的bean並不會被實例化,只有在用到的時候纔會去初始化(顯示調用getBean);而使用ApplicationContext加載xxx.xml時,會實例化所有singleton的bean,好處是可以預先加載,壞處便是浪費內存

26、CDN是什麼

(詳細請看筆記)CDN全稱叫Content Delivery Network,即內容分發網絡。簡單說來,CDN的基本原理是廣泛採用各種緩存服務器,將這些緩存服務器分佈到用戶訪問相對集中的地區或網絡中,在用戶訪問網絡時。利用全局負載均衡技術將用戶的訪問指向距離最近的工作正常的壓力最小的緩存服務器上,由緩存服務器直接相應用戶請求。

CDN是一個策略性部署的整體系統,包括分佈式存儲、負載均衡、網絡請求的重定向和內容管理4個要件,而內容管理和全局的網絡流量管理(Traffic Management)是CDN的核心所在。其特點便是通過用戶就近性和服務器負載的判斷,CDN確保內容以一種極爲高效的方式爲用戶的請求提供服務。

27、哈希衝突是什麼及解決方式

哈希衝突/碰撞:假設hash表的大小爲9,現在要將一串數據存到表裏:28,19,15,20,33,12,17,10,5,簡單計算下hash(28)=1,然後將28這個值存到標記爲1的槽裏,接着hash(19)=1,此時發現1這個槽裏已經存在數據28了,此時則爲發生了哈希衝突或者說碰撞,一個好的哈希算法此時會重新計算這個值的存儲空間儘量減少碰撞
解決方式:
1)開放地址法,也稱再散列法,若哈希地址p衝突了,以p爲基礎產生另一個哈希地址p1,若p1還是衝突,再以p1爲基礎循環產生新的哈希地址,直到找到不重複的哈希地址,該方法有一個通用的再散列函數形式,再散列方法有三種:線性探測再散列、二次探測再散列、僞隨機探測再散列
2)再哈希法,同時構造多個不同的哈希函數,當第一個計算衝突時,計算第二個函數,直到不再產生衝突,該方法不易產生聚集,但增加了計算時間
3)鏈地址法,也稱拉鍊法,該方法的基本思想是將所有哈希地址爲i的元素構成一個稱爲同義詞鏈的單鏈表,並將單鏈表的頭指針存在哈希表的第i個單元中,因而查找、插入和刪除主要在同義詞鏈中進行,鏈地址法適用於經常進行插入和刪除的情況
4)建立公共溢出區,該方法的基本思想是將哈希表分爲基本表和溢出表兩部分,凡是跟基本表產生衝突的元素,一律填入溢出表

28、二分查找簡介

是一種在有序數組中用於查詢某一特定元素的搜索算法,每次搜索時先從數組的中間位置開始,若待搜尋元素正好等於中間元素則返回;若大於則從後半區間繼續判斷,每一次新的判斷也都是從區間的中間元素開始。

29、happens-before原則

即先行發生原則,該原則是判斷數據是否存在競爭、線程是否安全的主要依據,依靠這個原則,我們可以在併發環境下解決兩操作之間是否可能存在衝突的所有問題。如果說A操作先行發生於B操作,其實就是說在發生B操作之前,A操作產生的影響能被B觀察到,”影響“包括修改了內存中共享變量的值、發送了消息、調用了方法等。(當然兩個操作之間存在happens-before關係,並不意味着一定要按照該原則制定的順序來執行,如果重排序之後執行結果與按照happens-before關係來執行的結果一致,那麼這種重排序並不非法)

happens-before八大原則如下:
1)程序次序規則:一個線程內,按照代碼順序,書寫在前面的操作先行發生於書寫在後面的操作;
2)管程鎖定規則:一個unLock操作先行發生於後面對同一個鎖的lock操作;
3)volatile變量規則:對一個變量的寫操作先行發生於後面對這個變量的讀操作;
4)傳遞規則:如果操作A先行發生於操作B,而操作B又先行發生於操作C,則可以得出操作A先行發生於操作C;
5)線程啓動規則:Thread對象的start()方法先行發生於此線程的每個一個動作;
6)線程中斷規則:對線程interrupt()方法的調用先行發生於被中斷線程的代碼檢測到中斷事件的發生;
7)線程終結規則:線程中所有的操作都先行發生於線程的終止檢測,我們可以通過Thread.join()方法結束、Thread.isAlive()的返回值手段檢測到線程已經終止執行;
8)對象終結規則:一個對象的初始化完成先行發生於他的finalize()方法的開始;

30、java內存模型簡介

jvm將內存組織分爲主內存和工作內存,主內存主要包括本地方法區和堆,每個線程都有一個工作內存,工作內存中主要包括兩個部分,一個是屬於該線程私有的棧和對主內存部分變量拷貝的寄存器(包括程序計數器PC和cup工作的高速緩存區)
1)java中所有共享變量都存儲在主內存
2)線程對共享變量的操作都必須在自己的工作內存中進行,不能直接在主內存中讀寫
3)不同線程之間無法直接訪問其他線程工作內存中的變量,線程間變量值的傳遞需要通過主內存進行(假設線程1修改了某個共享變量的值,線程2想要看到的話,必須線程1需要先將該值更新到主內存,然後主內存將該值更新到線程2的工作內存中)

31、如何保證可見性

多線程運行時可能會出現可見性問題,但是我們需要明確在單核cpu情況下是不存在可見性問題的,只有多核cpu纔會出現該問題,當對一個普通變量進行讀寫時,線程會先從內存中拷貝該變量到cpu緩存中(因爲cpu是不會直接操作內存的,操作緩存比操作內存更快),然後cpu對緩存中的數據進行修改,但是在多核cpu情況下,每個線程會拷貝到不同的cpu緩存中,此時便會出現可見性問題(因爲不同的緩存之間是無法感知到對這個變量的修改)
volatile:主要通過兩個指令來保證(load和store),每次對volatile變量進行寫操作時,會在寫操作後加入一條store指令,即強迫線程將最新的值刷新到主內存中;在讀取操作時,會加入一條load指令,即強迫從主內存中讀入變量的值。但volatile不保證原子性,因爲會存在讓出cpu執行權的情況,
synchronized:使用synchronized關鍵字後,線程在加鎖時,會先清空工作內存,在主內存中拷貝最新變量的副本到工作內存,執行完代碼後將修改後的共享變量的值回寫到主內存中,最後釋放互斥鎖,以此保證可見性。

32、UML中四大關係

UML即 Unified Modeling Language,統一建模語言,UML的類圖中四大關係爲泛化、實現、關聯、依賴。泛化對應於繼承,存在於類和類,實現對應於implements實現,存在於類和接口。關聯依賴則比較像,關聯使的某個類能知道另一個類的屬性和方法,是具有持久性的,比如類B作爲類A的參數變量或類A引用了類B的實例變量。而依賴則屬於臨時性、偶發性的,關係比較弱,表示類與類之間的連接,表示一個類依賴於另外一個類的定義,比如類B作爲類A的方法參數,A方法的參數是B的屬性或A方法的返回值是B,舉個例子(我吃飯時使用筷子,我與筷子就屬於依賴,絕大部分場景下我不需要筷子,但是在吃飯時我離不開它)

33、tcp長連接和短連接的區別

長連接是指在一個tcp連接內,可產生多次數據傳輸,當無數據傳輸時,需要雙方發送檢測包以維持此連接(心跳機制),總的過程如下:
連接 = > 數據傳輸 => 。。心跳檢測 =>數據傳輸 =》關閉連接
短連接則是在需要進行數據傳輸時才建立一個tcp連接,數據傳輸完成後就斷開此連接,每一次建立tcp連接都需要三次握手和四次揮手:
連接 = > 數據傳輸 => 關閉連接

34、父類的靜態方法能否被子類重寫

不能,靜態方法只與類有關,不與實例有關,重寫只適用於實例方法,而非靜態方法。靜態方法是程序一運行就已經分配好了內存地址,而且該地址是固定的,所有引用到該方法的對象所指向的始終是同一個內存地址中的數據,如果子類定義了相同名稱的靜態方法,只會新增一個內存地址,並不會重寫。

35、抽象類的意義

1)爲子類提供一個公共的類型

2)封裝子類中重複的內容(成員變量和方法)

3)定義有抽象方法,子類雖然有不同的實現,但該方法的定義是一致的

36、靜態內部類的意義

首先我們在項目中也經常使用內部類,通常使用內部類的最大優點便是其非常好的解決了多重繼承的問題(每個內部類均能繼承一個實現,所以無論外部類是否已經繼承,對於內部類都沒有影響)。

那對於靜態內部類,其職能訪問外部類的靜態屬性和方法,這是與一般內部類的區別,而且靜態內部類還可以有靜態數據,靜態方法或者又一個靜態內部類,這些是非靜態內部類所沒有的。非靜態的內部類既可以訪問外部類的靜態成員方法與成員變量, 也可以訪問外部類的非靜態成員方法與成員變量。而靜態內部類不能訪問外部中的非靜態,因此靜態內部類的主要意義可能在工具類這一方面。

37、談談解析與分派

這兩者都處於方法調用的過程中(方法調用並不等同於方法執行),所謂方法調用是指程序需要確定方法的版本即調用哪一個方法,首先我們需要知道一切方法調用在class文件裏面存儲的都是符號引用,而不是方法在實際運行時內存佈局中的入口地址(相當於直接引用),因此java方法調用需要在類加載期間甚至是運行期間才能確定目標方法的直接引用。

解析:在類加載的解析階段,會將其中的一部分符號引用轉爲直接引用,當然這個前提是:這些方法在程序真正運行之前就已經確定了,並且在運行期間不可改變,比如靜態方法、私有方法就屬於這一類型(編譯器可知,運行期不可變)

分派:分派調用過程也是java多態性的一個具體體現,即重載、重寫都是通過分派實現的。分派又可分爲靜態分派(重載)和動態分派(重寫)

靜態分派:依賴靜態類型來確定方法執行版本的分配過程稱爲靜態分派,靜態分派的典型應用是方法重載,其發生於編譯階段,下面舉個例子來理解下什麼是靜態類型和實際類型。

針對Human man = new Man();

我們把Human稱爲變量的靜態類型,後面的Man則稱爲實際類型,靜態類型和實際類型在程序中都可以發生一些變化,區別是靜態類型的變化僅僅在使用時發生,變量本身的靜態類型並不會改變,並且最終的靜態類型是在編譯器可知的;而實際類型變化的結果只有在運行期纔可確定,編譯器在編譯程序的時候並不知道一個對象的實際類型是什麼。變化可見下圖:

那麼虛擬機(準確來說是編譯器)在重載時選擇哪個版本調用是通過參數的靜態類型而不是實際類型作爲判定依據的。因此上面的代碼中輸出的都是hello guy

動態分派:在運行期根據實際類型來確定方法執行版本的分派過程稱爲動態分派,典型應用便是方法重寫。java虛擬機是通過”穩定優化“的手段--在方法區中建立一個虛方法表,通過使用方法表的索引來代替元數據查找以提高性能。虛方法表中存放着各個方法的實際入口地址,如果某個方法在子類中沒有被重寫,那麼子類的虛方法表裏的地址入口和父類相同方法的地址入口是一致的,都指向父類的實現入口;如果在子類中重寫了這個方法,子類方法表中的地址將會替換爲指向子類實現版本的入口地址。

38、String爲什麼要設計成不可變的

主要是三方面因素:設計考慮、性能、安全
1)由於字符串常量池的存在(在jdk1.8中,字符串常量池已經從原先方法區中的運行時常量池分離到堆中了),當創建一個String對象時,如果該字符串值已經存在於常量池中,則不會創建一個新的對象,而是引用已經存在的對象;假如字符串對象允許改變,將會造成各種邏輯錯誤,比如改變一個對象會影響到另一個對象。
2)字符串不變性保證了哈希碼的唯一性,因此可以放心的進行緩存,不必每次都去計算新的哈希碼
3)String被許多的Java類/庫用來當做參數,例如:網絡連接地址、文件路徑等,假如字符串對象不是固定不變的,將會引起各種安全隱患

39、簡單說一下泛型

泛型可以認爲是java語言的一顆語法糖,其具體實現方法稱爲類型擦除,java語言的泛型只在程序的源代碼中存在,在編譯後的class文件中就已替換爲原來的原生類型,可以認爲是裸類型,並且在響應的地方插入了強制類型轉換代碼,因此對於運行的java來說,List<Integer>和List<String>是同一個類(List),基於這種實現的泛型稱爲僞泛型

40、談談淺拷貝和深拷貝

淺拷貝:指在拷貝對象時,對於基本數據類型的變量會重新複製一份,而對於引用類型的變量則只會對引用進行拷貝,舉個例子,A類中存在引用變量B,現對A進行拷貝成A1,若此時對A類中的變量B進行修改,A1的B值也會隨着修改,換句話說,這兩個對象的地址是一樣的
深拷貝:則是對對象及該對象關聯的對象內容,都會進行一份拷貝,但是需要每個被引用的類都實現cloneable接口,當類之間層級嵌套比較深的時候會非常麻煩。因此實際寫代碼時常有另外兩種方式:流方式和json轉換。這兩種方式由於都是先將對象轉換成字節流或json,然後再次讀取作爲一個新對象返回,那麼這個新對象自然和源對象不存在任何地址上的共享。

41、tcp可靠性傳輸的實現

1)確認應答機制(ACK):TCP對每個字節的數據都進行編號,即爲序列號,確認號=序列號+1,每個ACK都有對應的確認號,意思是告訴發送者已經接收到了數據,下一個數據應該從哪裏發送。
2)超時重傳機制:當一個主機長時間沒有收到對方發來的報文,我們可以認爲是ACK丟了,這時就需要觸發超時重傳機制
3)連接管理機制:由於TCP是面向連接的,因此可進行可靠性傳輸
那麼udp如何實現可靠性傳輸?
參考tcp,引入序列號,保證數據順序;引入確認應答,保證對端收到了數據;引入超時重傳,如果隔一段時間沒有應答,就重新發送數據。

更多tcp知識走:TCP三次握手和四次揮手

42、聚集索引和非聚集索引

兩者的根本區別是表記錄的排列順序和與索引的排列順序是否一致。
1)由於聚集索引規定了數據在表中的物理存儲書序,因此一個表只能有一個,但該索引可以包含多個列(組合索引)而非聚集索引一個表可以存在多個。
2)聚集索引存儲記錄是物理上連續存在,而非聚集索引是邏輯上的連續,物理存儲並不連續。
3)聚集索引查詢數據速度快,插入數據速度慢;非聚集索引反之。
聚集索引和非聚集索引都採用了B+樹的結構,但聚集索引的葉子節點就是數據節點;而非聚集索引的葉子節點仍然是索引節點,只不過有一個指針指向對應的數據塊

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章