java面試題

本套Java面試題大全

java垃圾回收機(jī)制:

使用可達(dá)性分析方法判斷對(duì)象是否可回收,從樹(shù)的根結(jié)點(diǎn) GC Roots 出發(fā),持續(xù)遍歷找出所有連接的樹(shù)枝對(duì)象,這些對(duì)象則被稱為“可達(dá)”對(duì)象
回收算法:1.標(biāo)記-清除 (容易產(chǎn)生內(nèi)存碎片) 2.標(biāo)記整理 (需要整理的過(guò)程) 3.復(fù)制(內(nèi)存利用率太低,只用了一半) 4.分代回收算法 根據(jù)java內(nèi)存堆和方法區(qū)數(shù)據(jù)類型分為新生代,老年代,永久代,新生代采用復(fù)制回收算法,老年代采用標(biāo)記-整理算法,永久代也就是方法區(qū),一般不做垃圾回收因?yàn)樾詢r(jià)比比較低。

java面向?qū)ο蟮娜筇匦裕?/h5>

封裝,集成,多態(tài)
封裝指的是屬性私有化,根據(jù)需要提供setter和getter方法來(lái)訪問(wèn)屬性。即隱藏具體屬性和實(shí)現(xiàn)細(xì)節(jié),僅對(duì)外開(kāi)放接口,控制程序中屬性的訪問(wèn)級(jí)別。

繼承是指將多個(gè)相同的屬性和方法提取出來(lái),新建一個(gè)父類。

多態(tài)指允許不同類的對(duì)象對(duì)同一消息做出響應(yīng)。即同一消息可以根據(jù)發(fā)送對(duì)象的不同而采用多種不同的行為方式

ArrayList和LinkList的區(qū)別:

ArrayList 底層實(shí)現(xiàn)就是數(shù)組,且ArrayList實(shí)現(xiàn)了RandomAccess,表示它能快速隨機(jī)訪問(wèn)存儲(chǔ)的元素,通過(guò)下標(biāo) index 訪問(wèn),只是我們需要用 get() 方法的形式, 數(shù)組支持隨機(jī)訪問(wèn), 查詢速度快, 增刪元素慢;
LinkedList 底層實(shí)現(xiàn)是鏈表, LinkedList 沒(méi)有實(shí)現(xiàn) RandomAccess 接口,鏈表支持順序訪問(wèn), 查詢速度慢, 增刪元素快

可以用CopyOnWriteArrayList實(shí)現(xiàn)arraylist高效率的線程安全,實(shí)現(xiàn)原理:則首先將當(dāng)前容器復(fù)制一份,然后在新副本上執(zhí)行寫(xiě)操作,結(jié)束之后再將原容器的引用指向新容器。

可以用ConcurrentLinkedQueue實(shí)現(xiàn)linkedList高效率的線程安全

HashTable和HashMap的區(qū)別詳解:

HashTable底層數(shù)組+鏈表實(shí)現(xiàn),無(wú)論key還是value都不能為null,線程安全
HashMap底層數(shù)組+鏈表實(shí)現(xiàn),可以存儲(chǔ)null鍵和null值,線程不安全
1.擴(kuò)容是一個(gè)特別耗性能的操作,所以當(dāng)程序員在使用HashMap的時(shí)候,估算map的大小,初始化的時(shí)候給一個(gè)大致的數(shù)值,避免map進(jìn)行頻繁的擴(kuò)容。

擴(kuò)容算法:JDK1.7擴(kuò)容我們是通過(guò)重新計(jì)算hash值,把原數(shù)組數(shù)據(jù)放在新數(shù)組數(shù)據(jù)原位置,在JDK1.8我們?cè)跀U(kuò)充HashMap的時(shí)候,不需要像JDK1.7的實(shí)現(xiàn)那樣重新計(jì)算hash,只需要看看原來(lái)的hash值新增的那個(gè)bit是1還是0就好了,是0的話索引沒(méi)變,是1的話索引變成“原索引+oldCap”

(2) 負(fù)載因子是可以修改的,也可以大于1,但是建議不要輕易修改,除非情況非常特殊。

(3) HashMap是線程不安全的,不要在并發(fā)的環(huán)境中同時(shí)操作HashMap,建議使用ConcurrentHashMap。

(4) JDK1.8引入紅黑樹(shù)大程度優(yōu)化了HashMap的性能。

Collection、ArrayList、HashMap、HashSet都是非同步的,線程不安全的
可以用concurentHashMap實(shí)現(xiàn)高效率的線程安全

對(duì)象鎖,方法鎖和類鎖:

對(duì)象鎖和方法鎖作用是一樣的,只是寫(xiě)法略有不同。
而類鎖其實(shí)是修飾靜態(tài)方法的鎖,并不是真實(shí)存在的,只是幫助理解鎖定實(shí)例方法和靜態(tài)方法的區(qū)別,類鎖是無(wú)論類有多少個(gè)實(shí)例對(duì)象都只共用一個(gè)鎖,而方法鎖是一個(gè)類對(duì)象持有一個(gè)鎖,每個(gè)對(duì)象鎖互不影響。

java中的垃圾回收機(jī)制

常見(jiàn)的判斷是否存活有兩種方法:引用計(jì)數(shù)法和可達(dá)性分析。

引用計(jì)數(shù)就是對(duì)象引用的次數(shù),當(dāng)引用次數(shù)為0時(shí),被標(biāo)記為可回收對(duì)象。但是有漏洞,當(dāng)兩個(gè)對(duì)象互相引用,它倆的計(jì)數(shù)都不為零,因此永遠(yuǎn)不會(huì)被回收。而實(shí)際上對(duì)于開(kāi)發(fā)者而言,這兩個(gè)對(duì)象已經(jīng)完全沒(méi)有用處了。因此,Java 里沒(méi)有采用這樣的方案來(lái)判定對(duì)象的“存活性”。

可達(dá)性分析基本思路是能夠通過(guò)遍歷找到所有連接的對(duì)象,成為"可達(dá)對(duì)象",不能到達(dá)的就是可回收對(duì)象。

垃圾回收算法有四種:1標(biāo)記-清理 2標(biāo)記-整理 3.復(fù)制 4.分代回收算法
1.標(biāo)記-清除 (容易產(chǎn)生內(nèi)存碎片) 2.標(biāo)記整理 (需要整理的過(guò)程) 3.復(fù)制(內(nèi)存利用率太低,只用了一半) 4.分代回收算法 根據(jù)java內(nèi)存堆和方法區(qū)數(shù)據(jù)類型分為新生代,老年代,永久代,新生代采用復(fù)制回收算法,老年代采用標(biāo)記-整理算法,永久代也就是方法區(qū),一般不做垃圾回收因?yàn)樾詢r(jià)比比較低。

一、Java 基礎(chǔ)

1. JDK 和 JRE 有什么區(qū)別?

  • JDK:Java Development Kit 的簡(jiǎn)稱,java 開(kāi)發(fā)工具包,提供了 java 的開(kāi)發(fā)環(huán)境和運(yùn)行環(huán)境。
  • JRE:Java Runtime Environment 的簡(jiǎn)稱,java 運(yùn)行環(huán)境,為 java 的運(yùn)行提供了所需環(huán)境。

具體來(lái)說(shuō) JDK 其實(shí)包含了 JRE,同時(shí)還包含了編譯 java 源碼的編譯器 javac,還包含了很多 java 程序調(diào)試和分析的工具。簡(jiǎn)單來(lái)說(shuō):如果你需要運(yùn)行 java 程序,只需安裝 JRE 就可以了,如果你需要編寫(xiě) java 程序,需要安裝 JDK。

2. == 和 equals 的區(qū)別是什么?

== 解讀

對(duì)于基本類型和引用類型 == 的作用效果是不同的,如下所示:

  • 基本類型:比較的是值是否相同;
  • 引用類型:比較的是引用是否相同;

代碼示例:

String x = "string";
String y = "string";
String z = new String("string");
System.out.println(x==y); // true
System.out.println(x==z); // false
System.out.println(x.equals(y)); // true
System.out.println(x.equals(z)); // true

代碼解讀:因?yàn)?x 和 y 指向的是同一個(gè)引用,所以 == 也是 true,而 new String()方法則重寫(xiě)開(kāi)辟了內(nèi)存空間,所以 == 結(jié)果為 false,而 equals 比較的一直是值,所以結(jié)果都為 true。

equals 解讀

equals 本質(zhì)上就是 ==,只不過(guò) String 和 Integer 等重寫(xiě)了 equals 方法,把它變成了值比較??聪旅娴拇a就明白了。

首先來(lái)看默認(rèn)情況下 equals 比較一個(gè)有相同值的對(duì)象,代碼如下:

class Cat {
    public Cat(String name) {
        this.name = name;
    }

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Cat c1 = new Cat("王磊");
Cat c2 = new Cat("王磊");
System.out.println(c1.equals(c2)); // false

輸出結(jié)果出乎我們的意料,竟然是 false?這是怎么回事,看了 equals 源碼就知道了,源碼如下:

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

原來(lái) equals 本質(zhì)上就是 ==。

那問(wèn)題來(lái)了,兩個(gè)相同值的 String 對(duì)象,為什么返回的是 true?代碼如下:

String s1 = new String("老王");
String s2 = new String("老王");
System.out.println(s1.equals(s2)); // true

同樣的,當(dāng)我們進(jìn)入 String 的 equals 方法,找到了答案,代碼如下:

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

原來(lái)是 String 重寫(xiě)了 Object 的 equals 方法,把引用比較改成了值比較。

總結(jié) :== 對(duì)于基本類型來(lái)說(shuō)是值比較,對(duì)于引用類型來(lái)說(shuō)是比較的是引用;而 equals 默認(rèn)情況下是引用比較,只是很多類重新了 equals 方法,比如 String、Integer 等把它變成了值比較,所以一般情況下 equals 比較的是值是否相等。

3. 兩個(gè)對(duì)象的 hashCode()相同,則 equals()也一定為 true,對(duì)嗎?

不對(duì),兩個(gè)對(duì)象的 hashCode()相同,equals()不一定 true。

代碼示例:

String str1 = "通話";
String str2 = "重地";
System.out.println(String.format("str1:%d | str2:%d",  str1.hashCode(),str2.hashCode()));
System.out.println(str1.equals(str2));

執(zhí)行的結(jié)果:

str1:1179395 | str2:1179395

false

代碼解讀:很顯然“通話”和“重地”的 hashCode() 相同,然而 equals() 則為 false,因?yàn)樵谏⒘斜碇?,hashCode()相等即兩個(gè)鍵值對(duì)的哈希值相等,然而哈希值相等,并不一定能得出鍵值對(duì)相等。

4. final 在 java 中有什么作用?

  • final 修飾的類叫最終類,該類不能被繼承。
  • final 修飾的方法不能被重寫(xiě)。
  • final 修飾的變量叫常量,常量必須初始化,初始化之后值就不能被修改。

5. java 中的 Math.round(-1.5) 等于多少?

等于 -1,因?yàn)樵跀?shù)軸上取值時(shí),中間值(0.5)向右取整,所以正 0.5 是往上取整,負(fù) 0.5 是直接舍棄。

6. String 屬于基礎(chǔ)的數(shù)據(jù)類型嗎?

String 不屬于基礎(chǔ)類型,基礎(chǔ)類型有 8 種:byte、boolean、char、short、int、float、long、double,而 String 屬于對(duì)象。

7. java 中操作字符串都有哪些類?它們之間有什么區(qū)別?

操作字符串的類有:String、StringBuffer、StringBuilder。

String 和 StringBuffer、StringBuilder 的區(qū)別在于 String 聲明的是不可變的對(duì)象,每次操作都會(huì)生成新的 String 對(duì)象,然后將指針指向新的 String 對(duì)象,而 StringBuffer、StringBuilder 可以在原有對(duì)象的基礎(chǔ)上進(jìn)行操作,所以在經(jīng)常改變字符串內(nèi)容的情況下最好不要使用 String。

StringBuffer 和 StringBuilder 最大的區(qū)別在于,StringBuffer 是線程安全的,而 StringBuilder 是非線程安全的,但 StringBuilder 的性能卻高于 StringBuffer,所以在單線程環(huán)境下推薦使用 StringBuilder,多線程環(huán)境下推薦使用 StringBuffer。

8. String str="i"與 String str=new String("i")一樣嗎?

不一樣,因?yàn)閮?nèi)存的分配方式不一樣。String str="i"的方式,java 虛擬機(jī)會(huì)將其分配到常量池中;而 String str=new String("i") 則會(huì)被分到堆內(nèi)存中。

9. 如何將字符串反轉(zhuǎn)?

使用 StringBuilder 或者 stringBuffer 的 reverse() 方法。

示例代碼:

// StringBuffer reverse
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("abcdefg");
System.out.println(stringBuffer.reverse()); // gfedcba
// StringBuilder reverse
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("abcdefg");
System.out.println(stringBuilder.reverse()); // gfedcba

10. String 類的常用方法都有那些?

  • indexOf():返回指定字符的索引。
  • charAt():返回指定索引處的字符。
  • replace():字符串替換。
  • trim():去除字符串兩端空白。
  • split():分割字符串,返回一個(gè)分割后的字符串?dāng)?shù)組。
  • getBytes():返回字符串的 byte 類型數(shù)組。
  • length():返回字符串長(zhǎng)度。
  • toLowerCase():將字符串轉(zhuǎn)成小寫(xiě)字母。
  • toUpperCase():將字符串轉(zhuǎn)成大寫(xiě)字符。
  • substring():截取字符串。
  • equals():字符串比較。

11. 抽象類必須要有抽象方法嗎?

不需要,抽象類不一定非要有抽象方法。

示例代碼:

abstract class Cat {
    public static void sayHi() {
        System.out.println("hi~");
    }
}

上面代碼,抽象類并沒(méi)有抽象方法但完全可以正常運(yùn)行。

12. 普通類和抽象類有哪些區(qū)別?

  • 普通類不能包含抽象方法,抽象類可以包含抽象方法。
  • 抽象類不能直接實(shí)例化,普通類可以直接實(shí)例化。

13. 抽象類能使用 final 修飾嗎?

不能,定義抽象類就是讓其他類繼承的,如果定義為 final 該類就不能被繼承,這樣彼此就會(huì)產(chǎn)生矛盾,所以 final 不能修飾抽象類,如下圖所示,編輯器也會(huì)提示錯(cuò)誤信息:

image

14. 接口和抽象類有什么區(qū)別?

  • 實(shí)現(xiàn):抽象類的子類使用 extends 來(lái)繼承;接口必須使用 implements 來(lái)實(shí)現(xiàn)接口。
  • 構(gòu)造函數(shù):抽象類可以有構(gòu)造函數(shù);接口不能有。
  • main 方法:抽象類可以有 main 方法,并且我們能運(yùn)行它;接口不能有 main 方法。
  • 實(shí)現(xiàn)數(shù)量:類可以實(shí)現(xiàn)很多個(gè)接口;但是只能繼承一個(gè)抽象類。
  • 訪問(wèn)修飾符:接口中的方法默認(rèn)使用 public 修飾;抽象類中的方法可以是任意訪問(wèn)修飾符。

15. java 中 IO 流分為幾種?

按功能來(lái)分:輸入流(input)、輸出流(output)。

按類型來(lái)分:字節(jié)流和字符流。

字節(jié)流和字符流的區(qū)別是:字節(jié)流按 8 位傳輸以字節(jié)為單位輸入輸出數(shù)據(jù),字符流按 16 位傳輸以字符為單位輸入輸出數(shù)據(jù)。

16. BIO、NIO、AIO 有什么區(qū)別?

  • BIO:Block IO 同步阻塞式 IO,就是我們平常使用的傳統(tǒng) IO,它的特點(diǎn)是模式簡(jiǎn)單使用方便,并發(fā)處理能力低。
  • NIO:New IO 同步非阻塞 IO,是傳統(tǒng) IO 的升級(jí),客戶端和服務(wù)器端通過(guò) Channel(通道)通訊,實(shí)現(xiàn)了多路復(fù)用。
  • AIO:Asynchronous IO 是 NIO 的升級(jí),也叫 NIO2,實(shí)現(xiàn)了異步非堵塞 IO ,異步 IO 的操作基于事件和回調(diào)機(jī)制。

17. Files的常用方法都有哪些?

  • Files.exists():檢測(cè)文件路徑是否存在。
  • Files.createFile():創(chuàng)建文件。
  • Files.createDirectory():創(chuàng)建文件夾。
  • Files.delete():刪除一個(gè)文件或目錄。
  • Files.copy():復(fù)制文件。
  • Files.move():移動(dòng)文件。
  • Files.size():查看文件個(gè)數(shù)。
  • Files.read():讀取文件。
  • Files.write():寫(xiě)入文件。

二、容器

18. java 容器都有哪些?

常用容器的圖錄:

image

19. Collection 和 Collections 有什么區(qū)別?

  • java.util.Collection 是一個(gè)集合接口(集合類的一個(gè)頂級(jí)接口)。它提供了對(duì)集合對(duì)象進(jìn)行基本操作的通用接口方法。Collection接口在Java 類庫(kù)中有很多具體的實(shí)現(xiàn)。Collection接口的意義是為各種具體的集合提供了最大化的統(tǒng)一操作方式,其直接繼承接口有List與Set。
  • Collections則是集合類的一個(gè)工具類/幫助類,其中提供了一系列靜態(tài)方法,用于對(duì)集合中元素進(jìn)行排序、搜索以及線程安全等各種操作。

20. List、Set、Map 之間的區(qū)別是什么?

image

21. HashMap 和 Hashtable 有什么區(qū)別?

  • hashMap去掉了HashTable 的contains方法,但是加上了containsValue()和containsKey()方法。
  • hashTable同步的,而HashMap是非同步的,效率上逼hashTable要高。
  • hashMap允許空鍵值,而hashTable不允許。

22. 如何決定使用 HashMap 還是 TreeMap?

對(duì)于在Map中插入、刪除和定位元素這類操作,HashMap是最好的選擇。然而,假如你需要對(duì)一個(gè)有序的key集合進(jìn)行遍歷,TreeMap是更好的選擇?;谀愕腸ollection的大小,也許向HashMap中添加元素會(huì)更快,將map換為T(mén)reeMap進(jìn)行有序key的遍歷。

23. 說(shuō)一下 HashMap 的實(shí)現(xiàn)原理?

HashMap概述: HashMap是基于哈希表的Map接口的非同步實(shí)現(xiàn)。此實(shí)現(xiàn)提供所有可選的映射操作,并允許使用null值和null鍵。此類不保證映射的順序,特別是它不保證該順序恒久不變。

HashMap的數(shù)據(jù)結(jié)構(gòu): 在java編程語(yǔ)言中,最基本的結(jié)構(gòu)就是兩種,一個(gè)是數(shù)組,另外一個(gè)是模擬指針(引用),所有的數(shù)據(jù)結(jié)構(gòu)都可以用這兩個(gè)基本結(jié)構(gòu)來(lái)構(gòu)造的,HashMap也不例外。HashMap實(shí)際上是一個(gè)“鏈表散列”的數(shù)據(jù)結(jié)構(gòu),即數(shù)組和鏈表的結(jié)合體。

當(dāng)我們往Hashmap中put元素時(shí),首先根據(jù)key的hashcode重新計(jì)算hash值,根絕hash值得到這個(gè)元素在數(shù)組中的位置(下標(biāo)),如果該數(shù)組在該位置上已經(jīng)存放了其他元素,那么在這個(gè)位置上的元素將以鏈表的形式存放,新加入的放在鏈頭,最先加入的放入鏈尾.如果數(shù)組中該位置沒(méi)有元素,就直接將該元素放到數(shù)組的該位置上。

需要注意Jdk 1.8中對(duì)HashMap的實(shí)現(xiàn)做了優(yōu)化,當(dāng)鏈表中的節(jié)點(diǎn)數(shù)據(jù)超過(guò)八個(gè)之后,該鏈表會(huì)轉(zhuǎn)為紅黑樹(shù)來(lái)提高查詢效率,從原來(lái)的O(n)到O(logn)

24. 說(shuō)一下 HashSet 的實(shí)現(xiàn)原理?

  • HashSet底層由HashMap實(shí)現(xiàn)
  • HashSet的值存放于HashMap的key上
  • HashMap的value統(tǒng)一為PRESENT

25. ArrayList 和 LinkedList 的區(qū)別是什么?

最明顯的區(qū)別是 ArrrayList底層的數(shù)據(jù)結(jié)構(gòu)是數(shù)組,支持隨機(jī)訪問(wèn),而 LinkedList 的底層數(shù)據(jù)結(jié)構(gòu)是雙向循環(huán)鏈表,不支持隨機(jī)訪問(wèn)。使用下標(biāo)訪問(wèn)一個(gè)元素,ArrayList 的時(shí)間復(fù)雜度是 O(1),而 LinkedList 是 O(n)。

26. 如何實(shí)現(xiàn)數(shù)組和 List 之間的轉(zhuǎn)換?

  • List轉(zhuǎn)換成為數(shù)組:調(diào)用ArrayList的toArray方法。
  • 數(shù)組轉(zhuǎn)換成為L(zhǎng)ist:調(diào)用Arrays的asList方法。

27. ArrayList 和 Vector 的區(qū)別是什么?

  • Vector是同步的,而ArrayList不是。然而,如果你尋求在迭代的時(shí)候?qū)α斜磉M(jìn)行改變,你應(yīng)該使用CopyOnWriteArrayList。
  • ArrayList比Vector快,它因?yàn)橛型?,不?huì)過(guò)載。
  • ArrayList更加通用,因?yàn)槲覀兛梢允褂肅ollections工具類輕易地獲取同步列表和只讀列表。

28. Array 和 ArrayList 有何區(qū)別?

  • Array可以容納基本類型和對(duì)象,而ArrayList只能容納對(duì)象。
  • Array是指定大小的,而ArrayList大小是固定的。
  • Array沒(méi)有提供ArrayList那么多功能,比如addAll、removeAll和iterator等。

29. 在 Queue 中 poll()和 remove()有什么區(qū)別?

poll() 和 remove() 都是從隊(duì)列中取出一個(gè)元素,但是 poll() 在獲取元素失敗的時(shí)候會(huì)返回空,但是 remove() 失敗的時(shí)候會(huì)拋出異常。

30. 哪些集合類是線程安全的?

  • vector:就比arraylist多了個(gè)同步化機(jī)制(線程安全),因?yàn)樾瘦^低,現(xiàn)在已經(jīng)不太建議使用。在web應(yīng)用中,特別是前臺(tái)頁(yè)面,往往效率(頁(yè)面響應(yīng)速度)是優(yōu)先考慮的。
  • statck:堆棧類,先進(jìn)后出。
  • hashtable:就比hashmap多了個(gè)線程安全。
  • enumeration:枚舉,相當(dāng)于迭代器。

31. 迭代器 Iterator 是什么?

迭代器是一種設(shè)計(jì)模式,它是一個(gè)對(duì)象,它可以遍歷并選擇序列中的對(duì)象,而開(kāi)發(fā)人員不需要了解該序列的底層結(jié)構(gòu)。迭代器通常被稱為“輕量級(jí)”對(duì)象,因?yàn)閯?chuàng)建它的代價(jià)小。

32. Iterator 怎么使用?有什么特點(diǎn)?

Java中的Iterator功能比較簡(jiǎn)單,并且只能單向移動(dòng):

(1) 使用方法iterator()要求容器返回一個(gè)Iterator。第一次調(diào)用Iterator的next()方法時(shí),它返回序列的第一個(gè)元素。注意:iterator()方法是java.lang.Iterable接口,被Collection繼承。

(2) 使用next()獲得序列中的下一個(gè)元素。

(3) 使用hasNext()檢查序列中是否還有元素。

(4) 使用remove()將迭代器新返回的元素刪除。

Iterator是Java迭代器最簡(jiǎn)單的實(shí)現(xiàn),為L(zhǎng)ist設(shè)計(jì)的ListIterator具有更多的功能,它可以從兩個(gè)方向遍歷List,也可以從List中插入和刪除元素。

33. Iterator 和 ListIterator 有什么區(qū)別?

  • Iterator可用來(lái)遍歷Set和List集合,但是ListIterator只能用來(lái)遍歷List。
  • Iterator對(duì)集合只能是前向遍歷,ListIterator既可以前向也可以后向。
  • ListIterator實(shí)現(xiàn)了Iterator接口,并包含其他的功能,比如:增加元素,替換元素,獲取前一個(gè)和后一個(gè)元素的索引,等等。

三、多線程

35. 并行和并發(fā)有什么區(qū)別?

  • 并行是指兩個(gè)或者多個(gè)事件在同一時(shí)刻發(fā)生;而并發(fā)是指兩個(gè)或多個(gè)事件在同一時(shí)間間隔發(fā)生。
  • 并行是在不同實(shí)體上的多個(gè)事件,并發(fā)是在同一實(shí)體上的多個(gè)事件。
  • 在一臺(tái)處理器上“同時(shí)”處理多個(gè)任務(wù),在多臺(tái)處理器上同時(shí)處理多個(gè)任務(wù)。如hadoop分布式集群。

所以并發(fā)編程的目標(biāo)是充分的利用處理器的每一個(gè)核,以達(dá)到最高的處理性能。

36. 線程和進(jìn)程的區(qū)別?

簡(jiǎn)而言之,進(jìn)程是程序運(yùn)行和資源分配的基本單位,一個(gè)程序至少有一個(gè)進(jìn)程,一個(gè)進(jìn)程至少有一個(gè)線程。進(jìn)程在執(zhí)行過(guò)程中擁有獨(dú)立的內(nèi)存單元,而多個(gè)線程共享內(nèi)存資源,減少切換次數(shù),從而效率更高。線程是進(jìn)程的一個(gè)實(shí)體,是cpu調(diào)度和分派的基本單位,是比程序更小的能獨(dú)立運(yùn)行的基本單位。同一進(jìn)程中的多個(gè)線程之間可以并發(fā)執(zhí)行。

37. 守護(hù)線程是什么?

守護(hù)線程(即daemon thread),是個(gè)服務(wù)線程,準(zhǔn)確地來(lái)說(shuō)就是服務(wù)其他的線程。

38. 創(chuàng)建線程有哪幾種方式?

①. 繼承Thread類創(chuàng)建線程類

  • 定義Thread類的子類,并重寫(xiě)該類的run方法,該run方法的方法體就代表了線程要完成的任務(wù)。因此把run()方法稱為執(zhí)行體。
  • 創(chuàng)建Thread子類的實(shí)例,即創(chuàng)建了線程對(duì)象。
  • 調(diào)用線程對(duì)象的start()方法來(lái)啟動(dòng)該線程。

②. 通過(guò)Runnable接口創(chuàng)建線程類

  • 定義runnable接口的實(shí)現(xiàn)類,并重寫(xiě)該接口的run()方法,該run()方法的方法體同樣是該線程的線程執(zhí)行體。
  • 創(chuàng)建 Runnable實(shí)現(xiàn)類的實(shí)例,并依此實(shí)例作為T(mén)hread的target來(lái)創(chuàng)建Thread對(duì)象,該Thread對(duì)象才是真正的線程對(duì)象。
  • 調(diào)用線程對(duì)象的start()方法來(lái)啟動(dòng)該線程。

③. 通過(guò)Callable和Future創(chuàng)建線程

  • 創(chuàng)建Callable接口的實(shí)現(xiàn)類,并實(shí)現(xiàn)call()方法,該call()方法將作為線程執(zhí)行體,并且有返回值。
  • 創(chuàng)建Callable實(shí)現(xiàn)類的實(shí)例,使用FutureTask類來(lái)包裝Callable對(duì)象,該FutureTask對(duì)象封裝了該Callable對(duì)象的call()方法的返回值。
  • 使用FutureTask對(duì)象作為T(mén)hread對(duì)象的target創(chuàng)建并啟動(dòng)新線程。
  • 調(diào)用FutureTask對(duì)象的get()方法來(lái)獲得子線程執(zhí)行結(jié)束后的返回值。

39. 說(shuō)一下 runnable 和 callable 有什么區(qū)別?

有點(diǎn)深的問(wèn)題了,也看出一個(gè)Java程序員學(xué)習(xí)知識(shí)的廣度。

  • Runnable接口中的run()方法的返回值是void,它做的事情只是純粹地去執(zhí)行run()方法中的代碼而已;
  • Callable接口中的call()方法是有返回值的,是一個(gè)泛型,和Future、FutureTask配合可以用來(lái)獲取異步執(zhí)行的結(jié)果。

40. 線程有哪些狀態(tài)?

線程通常都有五種狀態(tài),創(chuàng)建、就緒、運(yùn)行、阻塞和死亡。

  • 創(chuàng)建狀態(tài)。在生成線程對(duì)象,并沒(méi)有調(diào)用該對(duì)象的start方法,這是線程處于創(chuàng)建狀態(tài)。
  • 就緒狀態(tài)。當(dāng)調(diào)用了線程對(duì)象的start方法之后,該線程就進(jìn)入了就緒狀態(tài),但是此時(shí)線程調(diào)度程序還沒(méi)有把該線程設(shè)置為當(dāng)前線程,此時(shí)處于就緒狀態(tài)。在線程運(yùn)行之后,從等待或者睡眠中回來(lái)之后,也會(huì)處于就緒狀態(tài)。
  • 運(yùn)行狀態(tài)。線程調(diào)度程序?qū)⑻幱诰途w狀態(tài)的線程設(shè)置為當(dāng)前線程,此時(shí)線程就進(jìn)入了運(yùn)行狀態(tài),開(kāi)始運(yùn)行run函數(shù)當(dāng)中的代碼。
  • 阻塞狀態(tài)。線程正在運(yùn)行的時(shí)候,被暫停,通常是為了等待某個(gè)時(shí)間的發(fā)生(比如說(shuō)某項(xiàng)資源就緒)之后再繼續(xù)運(yùn)行。sleep,suspend,wait等方法都可以導(dǎo)致線程阻塞。
  • 死亡狀態(tài)。如果一個(gè)線程的run方法執(zhí)行結(jié)束或者調(diào)用stop方法后,該線程就會(huì)死亡。對(duì)于已經(jīng)死亡的線程,無(wú)法再使用start方法令其進(jìn)入就緒

41. sleep() 和 wait() 有什么區(qū)別?

sleep():方法是線程類(Thread)的靜態(tài)方法,讓調(diào)用線程進(jìn)入睡眠狀態(tài),讓出執(zhí)行機(jī)會(huì)給其他線程,等到休眠時(shí)間結(jié)束后,線程進(jìn)入就緒狀態(tài)和其他線程一起競(jìng)爭(zhēng)cpu的執(zhí)行時(shí)間。因?yàn)閟leep() 是static靜態(tài)的方法,他不能改變對(duì)象的機(jī)鎖,當(dāng)一個(gè)synchronized塊中調(diào)用了sleep() 方法,線程雖然進(jìn)入休眠,但是對(duì)象的機(jī)鎖沒(méi)有被釋放,其他線程依然無(wú)法訪問(wèn)這個(gè)對(duì)象。

wait():wait()是Object類的方法,當(dāng)一個(gè)線程執(zhí)行到wait方法時(shí),它就進(jìn)入到一個(gè)和該對(duì)象相關(guān)的等待池,同時(shí)釋放對(duì)象的機(jī)鎖,使得其他線程能夠訪問(wèn),可以通過(guò)notify,notifyAll方法來(lái)喚醒等待的線程。

42. notify()和 notifyAll()有什么區(qū)別?

  • 如果線程調(diào)用了對(duì)象的 wait()方法,那么線程便會(huì)處于該對(duì)象的等待池中,等待池中的線程不會(huì)去競(jìng)爭(zhēng)該對(duì)象的鎖。
  • 當(dāng)有線程調(diào)用了對(duì)象的 notifyAll()方法(喚醒所有 wait 線程)或 notify()方法(只隨機(jī)喚醒一個(gè) wait 線程),被喚醒的的線程便會(huì)進(jìn)入該對(duì)象的鎖池中,鎖池中的線程會(huì)去競(jìng)爭(zhēng)該對(duì)象鎖。也就是說(shuō),調(diào)用了notify后只要一個(gè)線程會(huì)由等待池進(jìn)入鎖池,而notifyAll會(huì)將該對(duì)象等待池內(nèi)的所有線程移動(dòng)到鎖池中,等待鎖競(jìng)爭(zhēng)。
  • 優(yōu)先級(jí)高的線程競(jìng)爭(zhēng)到對(duì)象鎖的概率大,假若某線程沒(méi)有競(jìng)爭(zhēng)到該對(duì)象鎖,它還會(huì)留在鎖池中,唯有線程再次調(diào)用 wait()方法,它才會(huì)重新回到等待池中。而競(jìng)爭(zhēng)到對(duì)象鎖的線程則繼續(xù)往下執(zhí)行,直到執(zhí)行完了 synchronized 代碼塊,它會(huì)釋放掉該對(duì)象鎖,這時(shí)鎖池中的線程會(huì)繼續(xù)競(jìng)爭(zhēng)該對(duì)象鎖。

43. 線程的 run()和 start()有什么區(qū)別?

每個(gè)線程都是通過(guò)某個(gè)特定Thread對(duì)象所對(duì)應(yīng)的方法run()來(lái)完成其操作的,方法run()稱為線程體。通過(guò)調(diào)用Thread類的start()方法來(lái)啟動(dòng)一個(gè)線程。

start()方法來(lái)啟動(dòng)一個(gè)線程,真正實(shí)現(xiàn)了多線程運(yùn)行。這時(shí)無(wú)需等待run方法體代碼執(zhí)行完畢,可以直接繼續(xù)執(zhí)行下面的代碼; 這時(shí)此線程是處于就緒狀態(tài), 并沒(méi)有運(yùn)行。 然后通過(guò)此Thread類調(diào)用方法run()來(lái)完成其運(yùn)行狀態(tài), 這里方法run()稱為線程體,它包含了要執(zhí)行的這個(gè)線程的內(nèi)容, Run方法運(yùn)行結(jié)束, 此線程終止。然后CPU再調(diào)度其它線程。

run()方法是在本線程里的,只是線程里的一個(gè)函數(shù),而不是多線程的。 如果直接調(diào)用run(),其實(shí)就相當(dāng)于是調(diào)用了一個(gè)普通函數(shù)而已,直接待用run()方法必須等待run()方法執(zhí)行完畢才能執(zhí)行下面的代碼,所以執(zhí)行路徑還是只有一條,根本就沒(méi)有線程的特征,所以在多線程執(zhí)行時(shí)要使用start()方法而不是run()方法。

44. 創(chuàng)建線程池有哪幾種方式?

①. newFixedThreadPool(int nThreads)

創(chuàng)建一個(gè)固定長(zhǎng)度的線程池,每當(dāng)提交一個(gè)任務(wù)就創(chuàng)建一個(gè)線程,直到達(dá)到線程池的最大數(shù)量,這時(shí)線程規(guī)模將不再變化,當(dāng)線程發(fā)生未預(yù)期的錯(cuò)誤而結(jié)束時(shí),線程池會(huì)補(bǔ)充一個(gè)新的線程。

②. newCachedThreadPool()

創(chuàng)建一個(gè)可緩存的線程池,如果線程池的規(guī)模超過(guò)了處理需求,將自動(dòng)回收空閑線程,而當(dāng)需求增加時(shí),則可以自動(dòng)添加新線程,線程池的規(guī)模不存在任何限制。

③. newSingleThreadExecutor()

這是一個(gè)單線程的Executor,它創(chuàng)建單個(gè)工作線程來(lái)執(zhí)行任務(wù),如果這個(gè)線程異常結(jié)束,會(huì)創(chuàng)建一個(gè)新的來(lái)替代它;它的特點(diǎn)是能確保依照任務(wù)在隊(duì)列中的順序來(lái)串行執(zhí)行。

④. newScheduledThreadPool(int corePoolSize)

創(chuàng)建了一個(gè)固定長(zhǎng)度的線程池,而且以延遲或定時(shí)的方式來(lái)執(zhí)行任務(wù),類似于Timer。

45. 線程池都有哪些狀態(tài)?

線程池有5種狀態(tài):Running、ShutDown、Stop、Tidying、Terminated。

線程池各個(gè)狀態(tài)切換框架圖:

image

46. 線程池中 submit()和 execute()方法有什么區(qū)別?

  • 接收的參數(shù)不一樣
  • submit有返回值,而execute沒(méi)有
  • submit方便Exception處理

47. 在 java 程序中怎么保證多線程的運(yùn)行安全?

線程安全在三個(gè)方面體現(xiàn):

  • 原子性:提供互斥訪問(wèn),同一時(shí)刻只能有一個(gè)線程對(duì)數(shù)據(jù)進(jìn)行操作,(atomic,synchronized);
  • 可見(jiàn)性:一個(gè)線程對(duì)主內(nèi)存的修改可以及時(shí)地被其他線程看到,(synchronized,volatile);
  • 有序性:一個(gè)線程觀察其他線程中的指令執(zhí)行順序,由于指令重排序,該觀察結(jié)果一般雜亂無(wú)序,(happens-before原則)。

48. 多線程鎖的升級(jí)原理是什么?

在Java中,鎖共有4種狀態(tài),級(jí)別從低到高依次為:無(wú)狀態(tài)鎖,偏向鎖,輕量級(jí)鎖和重量級(jí)鎖狀態(tài),這幾個(gè)狀態(tài)會(huì)隨著競(jìng)爭(zhēng)情況逐漸升級(jí)。鎖可以升級(jí)但不能降級(jí)。

鎖升級(jí)的圖示過(guò)程:

image

49. 什么是死鎖?

死鎖是指兩個(gè)或兩個(gè)以上的進(jìn)程在執(zhí)行過(guò)程中,由于競(jìng)爭(zhēng)資源或者由于彼此通信而造成的一種阻塞的現(xiàn)象,若無(wú)外力作用,它們都將無(wú)法推進(jìn)下去。此時(shí)稱系統(tǒng)處于死鎖狀態(tài)或系統(tǒng)產(chǎn)生了死鎖,這些永遠(yuǎn)在互相等待的進(jìn)程稱為死鎖進(jìn)程。是操作系統(tǒng)層面的一個(gè)錯(cuò)誤,是進(jìn)程死鎖的簡(jiǎn)稱,最早在 1965 年由 Dijkstra 在研究銀行家算法時(shí)提出的,它是計(jì)算機(jī)操作系統(tǒng)乃至整個(gè)并發(fā)程序設(shè)計(jì)領(lǐng)域最難處理的問(wèn)題之一。

50. 怎么防止死鎖?

死鎖的四個(gè)必要條件:

  • 互斥條件:進(jìn)程對(duì)所分配到的資源不允許其他進(jìn)程進(jìn)行訪問(wèn),若其他進(jìn)程訪問(wèn)該資源,只能等待,直至占有該資源的進(jìn)程使用完成后釋放該資源
  • 請(qǐng)求和保持條件:進(jìn)程獲得一定的資源之后,又對(duì)其他資源發(fā)出請(qǐng)求,但是該資源可能被其他進(jìn)程占有,此事請(qǐng)求阻塞,但又對(duì)自己獲得的資源保持不放
  • 不可剝奪條件:是指進(jìn)程已獲得的資源,在未完成使用之前,不可被剝奪,只能在使用完后自己釋放
  • 環(huán)路等待條件:是指進(jìn)程發(fā)生死鎖后,若干進(jìn)程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系

這四個(gè)條件是死鎖的必要條件,只要系統(tǒng)發(fā)生死鎖,這些條件必然成立,而只要上述條件之 一不滿足,就不會(huì)發(fā)生死鎖。

理解了死鎖的原因,尤其是產(chǎn)生死鎖的四個(gè)必要條件,就可以最大可能地避免、預(yù)防和 解除死鎖。

所以,在系統(tǒng)設(shè)計(jì)、進(jìn)程調(diào)度等方面注意如何不讓這四個(gè)必要條件成立,如何確 定資源的合理分配算法,避免進(jìn)程永久占據(jù)系統(tǒng)資源。

此外,也要防止進(jìn)程在處于等待狀態(tài)的情況下占用資源。因此,對(duì)資源的分配要給予合理的規(guī)劃。

51. ThreadLocal 是什么?有哪些使用場(chǎng)景?

線程局部變量是局限于線程內(nèi)部的變量,屬于線程自身所有,不在多個(gè)線程間共享。Java提供ThreadLocal類來(lái)支持線程局部變量,是一種實(shí)現(xiàn)線程安全的方式。但是在管理環(huán)境下(如 web 服務(wù)器)使用線程局部變量的時(shí)候要特別小心,在這種情況下,工作線程的生命周期比任何應(yīng)用變量的生命周期都要長(zhǎng)。任何線程局部變量一旦在工作完成后沒(méi)有釋放,Java 應(yīng)用就存在內(nèi)存泄露的風(fēng)險(xiǎn)。

52.說(shuō)一下 synchronized 底層實(shí)現(xiàn)原理?

synchronized可以保證方法或者代碼塊在運(yùn)行時(shí),同一時(shí)刻只有一個(gè)方法可以進(jìn)入到臨界區(qū),同時(shí)它還可以保證共享變量的內(nèi)存可見(jiàn)性。

Java中每一個(gè)對(duì)象都可以作為鎖,這是synchronized實(shí)現(xiàn)同步的基礎(chǔ):

  • 普通同步方法,鎖是當(dāng)前實(shí)例對(duì)象
  • 靜態(tài)同步方法,鎖是當(dāng)前類的class對(duì)象
  • 同步方法塊,鎖是括號(hào)里面的對(duì)象

53. synchronized 和 volatile 的區(qū)別是什么?

  • volatile本質(zhì)是在告訴jvm當(dāng)前變量在寄存器(工作內(nèi)存)中的值是不確定的,需要從主存中讀?。?synchronized則是鎖定當(dāng)前變量,只有當(dāng)前線程可以訪問(wèn)該變量,其他線程被阻塞住。
  • volatile僅能使用在變量級(jí)別;synchronized則可以使用在變量、方法、和類級(jí)別的。
  • volatile僅能實(shí)現(xiàn)變量的修改可見(jiàn)性,不能保證原子性;而synchronized則可以保證變量的修改可見(jiàn)性和原子性。
  • volatile不會(huì)造成線程的阻塞;synchronized可能會(huì)造成線程的阻塞。
  • volatile標(biāo)記的變量不會(huì)被編譯器優(yōu)化;synchronized標(biāo)記的變量可以被編譯器優(yōu)化。

54. synchronized 和 Lock 有什么區(qū)別?

  • 首先synchronized是java內(nèi)置關(guān)鍵字,在jvm層面,Lock是個(gè)java類;
  • synchronized無(wú)法判斷是否獲取鎖的狀態(tài),Lock可以判斷是否獲取到鎖;
  • synchronized會(huì)自動(dòng)釋放鎖(a 線程執(zhí)行完同步代碼會(huì)釋放鎖 ;b 線程執(zhí)行過(guò)程中發(fā)生異常會(huì)釋放鎖),Lock需在finally中手工釋放鎖(unlock()方法釋放鎖),否則容易造成線程死鎖;
  • 用synchronized關(guān)鍵字的兩個(gè)線程1和線程2,如果當(dāng)前線程1獲得鎖,線程2線程等待。如果線程1阻塞,線程2則會(huì)一直等待下去,而Lock鎖就不一定會(huì)等待下去,如果嘗試獲取不到鎖,線程可以不用一直等待就結(jié)束了;
  • synchronized的鎖可重入、不可中斷、非公平,而Lock鎖可重入、可判斷、可公平(兩者皆可);
  • Lock鎖適合大量同步的代碼的同步問(wèn)題,synchronized鎖適合代碼少量的同步問(wèn)題。

55. synchronized 和 ReentrantLock 區(qū)別是什么?

synchronized是和if、else、for、while一樣的關(guān)鍵字,ReentrantLock是類,這是二者的本質(zhì)區(qū)別。既然ReentrantLock是類,那么它就提供了比synchronized更多更靈活的特性,可以被繼承、可以有方法、可以有各種各樣的類變量,ReentrantLock比synchronized的擴(kuò)展性體現(xiàn)在幾點(diǎn)上:

  • ReentrantLock可以對(duì)獲取鎖的等待時(shí)間進(jìn)行設(shè)置,這樣就避免了死鎖
  • ReentrantLock可以獲取各種鎖的信息
  • ReentrantLock可以靈活地實(shí)現(xiàn)多路通知

另外,二者的鎖機(jī)制其實(shí)也是不一樣的:ReentrantLock底層調(diào)用的是Unsafe的park方法加鎖,synchronized操作的應(yīng)該是對(duì)象頭中mark word。

56. 說(shuō)一下 atomic 的原理?

Atomic包中的類基本的特性就是在多線程環(huán)境下,當(dāng)有多個(gè)線程同時(shí)對(duì)單個(gè)(包括基本類型及引用類型)變量進(jìn)行操作時(shí),具有排他性,即當(dāng)多個(gè)線程同時(shí)對(duì)該變量的值進(jìn)行更新時(shí),僅有一個(gè)線程能成功,而未成功的線程可以向自旋鎖一樣,繼續(xù)嘗試,一直等到執(zhí)行成功。

Atomic系列的類中的核心方法都會(huì)調(diào)用unsafe類中的幾個(gè)本地方法。我們需要先知道一個(gè)東西就是Unsafe類,全名為:sun.misc.Unsafe,這個(gè)類包含了大量的對(duì)C代碼的操作,包括很多直接內(nèi)存分配以及原子操作的調(diào)用,而它之所以標(biāo)記為非安全的,是告訴你這個(gè)里面大量的方法調(diào)用都會(huì)存在安全隱患,需要小心使用,否則會(huì)導(dǎo)致嚴(yán)重的后果,例如在通過(guò)unsafe分配內(nèi)存的時(shí)候,如果自己指定某些區(qū)域可能會(huì)導(dǎo)致一些類似C++一樣的指針越界到其他進(jìn)程的問(wèn)題。


四、反射

57. 什么是反射?

反射主要是指程序可以訪問(wèn)、檢測(cè)和修改它本身狀態(tài)或行為的一種能力

Java反射:

在Java運(yùn)行時(shí)環(huán)境中,對(duì)于任意一個(gè)類,能否知道這個(gè)類有哪些屬性和方法?對(duì)于任意一個(gè)對(duì)象,能否調(diào)用它的任意一個(gè)方法

Java反射機(jī)制主要提供了以下功能:

  • 在運(yùn)行時(shí)判斷任意一個(gè)對(duì)象所屬的類。
  • 在運(yùn)行時(shí)構(gòu)造任意一個(gè)類的對(duì)象。
  • 在運(yùn)行時(shí)判斷任意一個(gè)類所具有的成員變量和方法。
  • 在運(yùn)行時(shí)調(diào)用任意一個(gè)對(duì)象的方法。

58. 什么是 java 序列化?什么情況下需要序列化?

簡(jiǎn)單說(shuō)就是為了保存在內(nèi)存中的各種對(duì)象的狀態(tài)(也就是實(shí)例變量,不是方法),并且可以把保存的對(duì)象狀態(tài)再讀出來(lái)。雖然你可以用你自己的各種各樣的方法來(lái)保存object states,但是Java給你提供一種應(yīng)該比你自己好的保存對(duì)象狀態(tài)的機(jī)制,那就是序列化。

什么情況下需要序列化:

a)當(dāng)你想把的內(nèi)存中的對(duì)象狀態(tài)保存到一個(gè)文件中或者數(shù)據(jù)庫(kù)中時(shí)候;
b)當(dāng)你想用套接字在網(wǎng)絡(luò)上傳送對(duì)象的時(shí)候;
c)當(dāng)你想通過(guò)RMI傳輸對(duì)象的時(shí)候;

59. 動(dòng)態(tài)代理是什么?有哪些應(yīng)用?

動(dòng)態(tài)代理:

當(dāng)想要給實(shí)現(xiàn)了某個(gè)接口的類中的方法,加一些額外的處理。比如說(shuō)加日志,加事務(wù)等。可以給這個(gè)類創(chuàng)建一個(gè)代理,故名思議就是創(chuàng)建一個(gè)新的類,這個(gè)類不僅包含原來(lái)類方法的功能,而且還在原來(lái)的基礎(chǔ)上添加了額外處理的新類。這個(gè)代理類并不是定義好的,是動(dòng)態(tài)生成的。具有解耦意義,靈活,擴(kuò)展性強(qiáng)。

動(dòng)態(tài)代理的應(yīng)用:

  • Spring的AOP
  • 加事務(wù)
  • 加權(quán)限
  • 加日志

60. 怎么實(shí)現(xiàn)動(dòng)態(tài)代理?

首先必須定義一個(gè)接口,還要有一個(gè)InvocationHandler(將實(shí)現(xiàn)接口的類的對(duì)象傳遞給它)處理類。再有一個(gè)工具類Proxy(習(xí)慣性將其稱為代理類,因?yàn)檎{(diào)用他的newInstance()可以產(chǎn)生代理對(duì)象,其實(shí)他只是一個(gè)產(chǎn)生代理對(duì)象的工具類)。利用到InvocationHandler,拼接代理類源碼,將其編譯生成代理類的二進(jìn)制碼,利用加載器加載,并將其實(shí)例化產(chǎn)生代理對(duì)象,最后返回。


五、對(duì)象拷貝

61. 為什么要使用克???

想對(duì)一個(gè)對(duì)象進(jìn)行處理,又想保留原有的數(shù)據(jù)進(jìn)行接下來(lái)的操作,就需要克隆了,Java語(yǔ)言中克隆針對(duì)的是類的實(shí)例。

62. 如何實(shí)現(xiàn)對(duì)象克?。?/strong>

有兩種方式:

1). 實(shí)現(xiàn)Cloneable接口并重寫(xiě)Object類中的clone()方法;

2). 實(shí)現(xiàn)Serializable接口,通過(guò)對(duì)象的序列化和反序列化實(shí)現(xiàn)克隆,可以實(shí)現(xiàn)真正的深度克隆,代碼如下:


import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class MyUtil {

    private MyUtil() {
        throw new AssertionError();
    }

    @SuppressWarnings("unchecked")
    public static <T extends Serializable> T clone(T obj) throws Exception {
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bout);
        oos.writeObject(obj);

        ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bin);
        return (T) ois.readObject();

        // 說(shuō)明:調(diào)用ByteArrayInputStream或ByteArrayOutputStream對(duì)象的close方法沒(méi)有任何意義
        // 這兩個(gè)基于內(nèi)存的流只要垃圾回收器清理對(duì)象就能夠釋放資源,這一點(diǎn)不同于對(duì)外部資源(如文件流)的釋放
    }
}

下面是測(cè)試代碼:


import java.io.Serializable;

/**
 * 人類
 * @author nnngu
 *
 */
class Person implements Serializable {
    private static final long serialVersionUID = -9102017020286042305L;

    private String name;    // 姓名
    private int age;        // 年齡
    private Car car;        // 座駕

    public Person(String name, int age, Car car) {
        this.name = name;
        this.age = age;
        this.car = car;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Car getCar() {
        return car;
    }

    public void setCar(Car car) {
        this.car = car;
    }

    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + ", car=" + car + "]";
    }

}

/**
 * 小汽車類
 * @author nnngu
 *
 */
class Car implements Serializable {
    private static final long serialVersionUID = -5713945027627603702L;

    private String brand;       // 品牌
    private int maxSpeed;       // 最高時(shí)速

    public Car(String brand, int maxSpeed) {
        this.brand = brand;
        this.maxSpeed = maxSpeed;
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public int getMaxSpeed() {
        return maxSpeed;
    }

    public void setMaxSpeed(int maxSpeed) {
        this.maxSpeed = maxSpeed;
    }

    @Override
    public String toString() {
        return "Car [brand=" + brand + ", maxSpeed=" + maxSpeed + "]";
    }

}
class CloneTest {

    public static void main(String[] args) {
        try {
            Person p1 = new Person("郭靖", 33, new Car("Benz", 300));
            Person p2 = MyUtil.clone(p1);   // 深度克隆
            p2.getCar().setBrand("BYD");
            // 修改克隆的Person對(duì)象p2關(guān)聯(lián)的汽車對(duì)象的品牌屬性
            // 原來(lái)的Person對(duì)象p1關(guān)聯(lián)的汽車不會(huì)受到任何影響
            // 因?yàn)樵诳寺erson對(duì)象時(shí)其關(guān)聯(lián)的汽車對(duì)象也被克隆了
            System.out.println(p1);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

注意:基于序列化和反序列化實(shí)現(xiàn)的克隆不僅僅是深度克隆,更重要的是通過(guò)泛型限定,可以檢查出要克隆的對(duì)象是否支持序列化,這項(xiàng)檢查是編譯器完成的,不是在運(yùn)行時(shí)拋出異常,這種是方案明顯優(yōu)于使用Object類的clone方法克隆對(duì)象。讓問(wèn)題在編譯的時(shí)候暴露出來(lái)總是好過(guò)把問(wèn)題留到運(yùn)行時(shí)。

63. 深拷貝和淺拷貝區(qū)別是什么?

  • 淺拷貝只是復(fù)制了對(duì)象的引用地址,兩個(gè)對(duì)象指向同一個(gè)內(nèi)存地址,所以修改其中任意的值,另一個(gè)值都會(huì)隨之變化,這就是淺拷貝(例:assign())
  • 深拷貝是將對(duì)象及值復(fù)制過(guò)來(lái),兩個(gè)對(duì)象修改其中任意的值另一個(gè)值不會(huì)改變,這就是深拷貝(例:JSON.parse()和JSON.stringify(),但是此方法無(wú)法復(fù)制函數(shù)類型)

七、異常

74. throw 和 throws 的區(qū)別?

throws是用來(lái)聲明一個(gè)方法可能拋出的所有異常信息,throws是將異常聲明但是不處理,而是將異常往上傳,誰(shuí)調(diào)用我就交給誰(shuí)處理。而throw則是指拋出的一個(gè)具體的異常類型。

75. final、finally、finalize 有什么區(qū)別?

  • final可以修飾類、變量、方法,修飾類表示該類不能被繼承、修飾方法表示該方法不能被重寫(xiě)、修飾變量表示該變量是一個(gè)常量不能被重新賦值。
  • finally一般作用在try-catch代碼塊中,在處理異常的時(shí)候,通常我們將一定要執(zhí)行的代碼方法finally代碼塊中,表示不管是否出現(xiàn)異常,該代碼塊都會(huì)執(zhí)行,一般用來(lái)存放一些關(guān)閉資源的代碼。
  • finalize是一個(gè)方法,屬于Object類的一個(gè)方法,而Object類是所有類的父類,該方法一般由垃圾回收器來(lái)調(diào)用,當(dāng)我們調(diào)用System的gc()方法的時(shí)候,由垃圾回收器調(diào)用finalize(),回收垃圾。

76. try-catch-finally 中哪個(gè)部分可以省略?

答:catch 可以省略

原因:

更為嚴(yán)格的說(shuō)法其實(shí)是:try只適合處理運(yùn)行時(shí)異常,try+catch適合處理運(yùn)行時(shí)異常+普通異常。也就是說(shuō),如果你只用try去處理普通異常卻不加以catch處理,編譯是通不過(guò)的,因?yàn)榫幾g器硬性規(guī)定,普通異常如果選擇捕獲,則必須用catch顯示聲明以便進(jìn)一步處理。而運(yùn)行時(shí)異常在編譯時(shí)沒(méi)有如此規(guī)定,所以catch可以省略,你加上catch編譯器也覺(jué)得無(wú)可厚非。

理論上,編譯器看任何代碼都不順眼,都覺(jué)得可能有潛在的問(wèn)題,所以你即使對(duì)所有代碼加上try,代碼在運(yùn)行期時(shí)也只不過(guò)是在正常運(yùn)行的基礎(chǔ)上加一層皮。但是你一旦對(duì)一段代碼加上try,就等于顯示地承諾編譯器,對(duì)這段代碼可能拋出的異常進(jìn)行捕獲而非向上拋出處理。如果是普通異常,編譯器要求必須用catch捕獲以便進(jìn)一步處理;如果運(yùn)行時(shí)異常,捕獲然后丟棄并且+finally掃尾處理,或者加上catch捕獲以便進(jìn)一步處理。

至于加上finally,則是在不管有沒(méi)捕獲異常,都要進(jìn)行的“掃尾”處理。

77. try-catch-finally 中,如果 catch 中 return 了,finally 還會(huì)執(zhí)行嗎?

答:會(huì)執(zhí)行,在 return 前執(zhí)行。

代碼示例1:


/*
 * java面試題--如果catch里面有return語(yǔ)句,finally里面的代碼還會(huì)執(zhí)行嗎?
 */
public class FinallyDemo2 {
    public static void main(String[] args) {
        System.out.println(getInt());
    }

    public static int getInt() {
        int a = 10;
        try {
            System.out.println(a / 0);
            a = 20;
        } catch (ArithmeticException e) {
            a = 30;
            return a;
            /*
             * return a 在程序執(zhí)行到這一步的時(shí)候,這里不是return a 而是 return 30;這個(gè)返回路徑就形成了
             * 但是呢,它發(fā)現(xiàn)后面還有finally,所以繼續(xù)執(zhí)行finally的內(nèi)容,a=40
             * 再次回到以前的路徑,繼續(xù)走return 30,形成返回路徑之后,這里的a就不是a變量了,而是常量30
             */
        } finally {
            a = 40;
        }

//      return a;
    }
}

執(zhí)行結(jié)果:30

代碼示例2:


package com.java_02;

/*
 * java面試題--如果catch里面有return語(yǔ)句,finally里面的代碼還會(huì)執(zhí)行嗎?
 */
public class FinallyDemo2 {
    public static void main(String[] args) {
        System.out.println(getInt());
    }

    public static int getInt() {
        int a = 10;
        try {
            System.out.println(a / 0);
            a = 20;
        } catch (ArithmeticException e) {
            a = 30;
            return a;
            /*
             * return a 在程序執(zhí)行到這一步的時(shí)候,這里不是return a 而是 return 30;這個(gè)返回路徑就形成了
             * 但是呢,它發(fā)現(xiàn)后面還有finally,所以繼續(xù)執(zhí)行finally的內(nèi)容,a=40
             * 再次回到以前的路徑,繼續(xù)走return 30,形成返回路徑之后,這里的a就不是a變量了,而是常量30
             */
        } finally {
            a = 40;
            return a; //如果這樣,就又重新形成了一條返回路徑,由于只能通過(guò)1個(gè)return返回,所以這里直接返回40
        }

//      return a;
    }
}

執(zhí)行結(jié)果:40

78. 常見(jiàn)的異常類有哪些?

  • NullPointerException:當(dāng)應(yīng)用程序試圖訪問(wèn)空對(duì)象時(shí),則拋出該異常。
  • SQLException:提供關(guān)于數(shù)據(jù)庫(kù)訪問(wèn)錯(cuò)誤或其他錯(cuò)誤信息的異常。
  • IndexOutOfBoundsException:指示某排序索引(例如對(duì)數(shù)組、字符串或向量的排序)超出范圍時(shí)拋出。
  • NumberFormatException:當(dāng)應(yīng)用程序試圖將字符串轉(zhuǎn)換成一種數(shù)值類型,但該字符串不能轉(zhuǎn)換為適當(dāng)格式時(shí),拋出該異常。
  • FileNotFoundException:當(dāng)試圖打開(kāi)指定路徑名表示的文件失敗時(shí),拋出此異常。
  • IOException:當(dāng)發(fā)生某種I/O異常時(shí),拋出此異常。此類是失敗或中斷的I/O操作生成的異常的通用類。
  • ClassCastException:當(dāng)試圖將對(duì)象強(qiáng)制轉(zhuǎn)換為不是實(shí)例的子類時(shí),拋出該異常。
  • ArrayStoreException:試圖將錯(cuò)誤類型的對(duì)象存儲(chǔ)到一個(gè)對(duì)象數(shù)組時(shí)拋出的異常。
  • IllegalArgumentException:拋出的異常表明向方法傳遞了一個(gè)不合法或不正確的參數(shù)。
  • ArithmeticException:當(dāng)出現(xiàn)異常的運(yùn)算條件時(shí),拋出此異常。例如,一個(gè)整數(shù)“除以零”時(shí),拋出此類的一個(gè)實(shí)例。
  • NegativeArraySizeException:如果應(yīng)用程序試圖創(chuàng)建大小為負(fù)的數(shù)組,則拋出該異常。
  • NoSuchMethodException:無(wú)法找到某一特定方法時(shí),拋出該異常。
  • SecurityException:由安全管理器拋出的異常,指示存在安全侵犯。
  • UnsupportedOperationException:當(dāng)不支持請(qǐng)求的操作時(shí),拋出該異常。
  • RuntimeExceptionRuntimeException:是那些可能在Java虛擬機(jī)正常運(yùn)行期間拋出的異常的超類。

八、網(wǎng)絡(luò)

79. http 響應(yīng)碼 301 和 302 代表的是什么?有什么區(qū)別?

答:301,302 都是HTTP狀態(tài)的編碼,都代表著某個(gè)URL發(fā)生了轉(zhuǎn)移。

**區(qū)別: **

  • 301 redirect: 301 代表永久性轉(zhuǎn)移(Permanently Moved)。
  • 302 redirect: 302 代表暫時(shí)性轉(zhuǎn)移(Temporarily Moved )。

80. forward 和 redirect 的區(qū)別?

Forward和Redirect代表了兩種請(qǐng)求轉(zhuǎn)發(fā)方式:直接轉(zhuǎn)發(fā)和間接轉(zhuǎn)發(fā)。

直接轉(zhuǎn)發(fā)方式(Forward),客戶端和瀏覽器只發(fā)出一次請(qǐng)求,Servlet、HTML、JSP或其它信息資源,由第二個(gè)信息資源響應(yīng)該請(qǐng)求,在請(qǐng)求對(duì)象request中,保存的對(duì)象對(duì)于每個(gè)信息資源是共享的。

間接轉(zhuǎn)發(fā)方式(Redirect)實(shí)際是兩次HTTP請(qǐng)求,服務(wù)器端在響應(yīng)第一次請(qǐng)求的時(shí)候,讓瀏覽器再向另外一個(gè)URL發(fā)出請(qǐng)求,從而達(dá)到轉(zhuǎn)發(fā)的目的。

舉個(gè)通俗的例子:

直接轉(zhuǎn)發(fā)就相當(dāng)于:“A找B借錢,B說(shuō)沒(méi)有,B去找C借,借到借不到都會(huì)把消息傳遞給A”;

間接轉(zhuǎn)發(fā)就相當(dāng)于:"A找B借錢,B說(shuō)沒(méi)有,讓A去找C借"。

81. 簡(jiǎn)述 tcp 和 udp的區(qū)別?

  • TCP面向連接(如打電話要先撥號(hào)建立連接);UDP是無(wú)連接的,即發(fā)送數(shù)據(jù)之前不需要建立連接。
  • TCP提供可靠的服務(wù)。也就是說(shuō),通過(guò)TCP連接傳送的數(shù)據(jù),無(wú)差錯(cuò),不丟失,不重復(fù),且按序到達(dá);UDP盡最大努力交付,即不保證可靠交付。
  • Tcp通過(guò)校驗(yàn)和,重傳控制,序號(hào)標(biāo)識(shí),滑動(dòng)窗口、確認(rèn)應(yīng)答實(shí)現(xiàn)可靠傳輸。如丟包時(shí)的重發(fā)控制,還可以對(duì)次序亂掉的分包進(jìn)行順序控制。
  • UDP具有較好的實(shí)時(shí)性,工作效率比TCP高,適用于對(duì)高速傳輸和實(shí)時(shí)性有較高的通信或廣播通信。
  • 每一條TCP連接只能是點(diǎn)到點(diǎn)的;UDP支持一對(duì)一,一對(duì)多,多對(duì)一和多對(duì)多的交互通信。
  • TCP對(duì)系統(tǒng)資源要求較多,UDP對(duì)系統(tǒng)資源要求較少。

82. tcp 為什么要三次握手,兩次不行嗎?為什么?

為了實(shí)現(xiàn)可靠數(shù)據(jù)傳輸, TCP 協(xié)議的通信雙方, 都必須維護(hù)一個(gè)序列號(hào), 以標(biāo)識(shí)發(fā)送出去的數(shù)據(jù)包中, 哪些是已經(jīng)被對(duì)方收到的。 三次握手的過(guò)程即是通信雙方相互告知序列號(hào)起始值, 并確認(rèn)對(duì)方已經(jīng)收到了序列號(hào)起始值的必經(jīng)步驟。

如果只是兩次握手, 至多只有連接發(fā)起方的起始序列號(hào)能被確認(rèn), 另一方選擇的序列號(hào)則得不到確認(rèn)。

83. 說(shuō)一下 tcp 粘包是怎么產(chǎn)生的?

①. 發(fā)送方產(chǎn)生粘包

采用TCP協(xié)議傳輸數(shù)據(jù)的客戶端與服務(wù)器經(jīng)常是保持一個(gè)長(zhǎng)連接的狀態(tài)(一次連接發(fā)一次數(shù)據(jù)不存在粘包),雙方在連接不斷開(kāi)的情況下,可以一直傳輸數(shù)據(jù);但當(dāng)發(fā)送的數(shù)據(jù)包過(guò)于的小時(shí),那么TCP協(xié)議默認(rèn)的會(huì)啟用Nagle算法,將這些較小的數(shù)據(jù)包進(jìn)行合并發(fā)送(緩沖區(qū)數(shù)據(jù)發(fā)送是一個(gè)堆壓的過(guò)程);這個(gè)合并過(guò)程就是在發(fā)送緩沖區(qū)中進(jìn)行的,也就是說(shuō)數(shù)據(jù)發(fā)送出來(lái)它已經(jīng)是粘包的狀態(tài)了。

image

②. 接收方產(chǎn)生粘包

接收方采用TCP協(xié)議接收數(shù)據(jù)時(shí)的過(guò)程是這樣的:數(shù)據(jù)到底接收方,從網(wǎng)絡(luò)模型的下方傳遞至傳輸層,傳輸層的TCP協(xié)議處理是將其放置接收緩沖區(qū),然后由應(yīng)用層來(lái)主動(dòng)獲?。–語(yǔ)言用recv、read等函數(shù));這時(shí)會(huì)出現(xiàn)一個(gè)問(wèn)題,就是我們?cè)诔绦蛑姓{(diào)用的讀取數(shù)據(jù)函數(shù)不能及時(shí)的把緩沖區(qū)中的數(shù)據(jù)拿出來(lái),而下一個(gè)數(shù)據(jù)又到來(lái)并有一部分放入的緩沖區(qū)末尾,等我們讀取數(shù)據(jù)時(shí)就是一個(gè)粘包。(放數(shù)據(jù)的速度 > 應(yīng)用層拿數(shù)據(jù)速度)

image

84. OSI 的七層模型都有哪些?

  1. 應(yīng)用層:網(wǎng)絡(luò)服務(wù)與最終用戶的一個(gè)接口。
  2. 表示層:數(shù)據(jù)的表示、安全、壓縮。
  3. 會(huì)話層:建立、管理、終止會(huì)話。
  4. 傳輸層:定義傳輸數(shù)據(jù)的協(xié)議端口號(hào),以及流控和差錯(cuò)校驗(yàn)。
  5. 網(wǎng)絡(luò)層:進(jìn)行邏輯地址尋址,實(shí)現(xiàn)不同網(wǎng)絡(luò)之間的路徑選擇。
  6. 數(shù)據(jù)鏈路層:建立邏輯連接、進(jìn)行硬件地址尋址、差錯(cuò)校驗(yàn)等功能。
  7. 物理層:建立、維護(hù)、斷開(kāi)物理連接。

85. get 和 post 請(qǐng)求有哪些區(qū)別?

  • GET在瀏覽器回退時(shí)是無(wú)害的,而POST會(huì)再次提交請(qǐng)求。
  • GET產(chǎn)生的URL地址可以被Bookmark,而POST不可以。
  • GET請(qǐng)求會(huì)被瀏覽器主動(dòng)cache,而POST不會(huì),除非手動(dòng)設(shè)置。
  • GET請(qǐng)求只能進(jìn)行url編碼,而POST支持多種編碼方式。
  • GET請(qǐng)求參數(shù)會(huì)被完整保留在瀏覽器歷史記錄里,而POST中的參數(shù)不會(huì)被保留。
  • GET請(qǐng)求在URL中傳送的參數(shù)是有長(zhǎng)度限制的,而POST么有。
  • 對(duì)參數(shù)的數(shù)據(jù)類型,GET只接受ASCII字符,而POST沒(méi)有限制。
  • GET比POST更不安全,因?yàn)閰?shù)直接暴露在URL上,所以不能用來(lái)傳遞敏感信息。
  • GET參數(shù)通過(guò)URL傳遞,POST放在Request body中。

86. 如何實(shí)現(xiàn)跨域?

方式一:圖片ping或script標(biāo)簽跨域

圖片ping常用于跟蹤用戶點(diǎn)擊頁(yè)面或動(dòng)態(tài)廣告曝光次數(shù)。
script標(biāo)簽可以得到從其他來(lái)源數(shù)據(jù),這也是JSONP依賴的根據(jù)。

方式二:JSONP跨域

JSONP(JSON with Padding)是數(shù)據(jù)格式JSON的一種“使用模式”,可以讓網(wǎng)頁(yè)從別的網(wǎng)域要數(shù)據(jù)。根據(jù) XmlHttpRequest 對(duì)象受到同源策略的影響,而利用 <script>元素的這個(gè)開(kāi)放策略,網(wǎng)頁(yè)可以得到從其他來(lái)源動(dòng)態(tài)產(chǎn)生的JSON數(shù)據(jù),而這種使用模式就是所謂的 JSONP。用JSONP抓到的數(shù)據(jù)并不是JSON,而是任意的JavaScript,用 JavaScript解釋器運(yùn)行而不是用JSON解析器解析。所有,通過(guò)Chrome查看所有JSONP發(fā)送的Get請(qǐng)求都是js類型,而非XHR。

image

缺點(diǎn):

  • 只能使用Get請(qǐng)求
  • 不能注冊(cè)success、error等事件監(jiān)聽(tīng)函數(shù),不能很容易的確定JSONP請(qǐng)求是否失敗
  • JSONP是從其他域中加載代碼執(zhí)行,容易受到跨站請(qǐng)求偽造的攻擊,其安全性無(wú)法確保

方式三:CORS

Cross-Origin Resource Sharing(CORS)跨域資源共享是一份瀏覽器技術(shù)的規(guī)范,提供了 Web 服務(wù)從不同域傳來(lái)沙盒腳本的方法,以避開(kāi)瀏覽器的同源策略,確保安全的跨域數(shù)據(jù)傳輸。現(xiàn)代瀏覽器使用CORS在API容器如XMLHttpRequest來(lái)減少HTTP請(qǐng)求的風(fēng)險(xiǎn)來(lái)源。與 JSONP 不同,CORS 除了 GET 要求方法以外也支持其他的 HTTP 要求。服務(wù)器一般需要增加如下響應(yīng)頭的一種或幾種:

Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400

跨域請(qǐng)求默認(rèn)不會(huì)攜帶Cookie信息,如果需要攜帶,請(qǐng)配置下述參數(shù):

"Access-Control-Allow-Credentials": true
// Ajax設(shè)置
"withCredentials": true

方式四:window.name+iframe

window.name通過(guò)在iframe(一般動(dòng)態(tài)創(chuàng)建i)中加載跨域HTML文件來(lái)起作用。然后,HTML文件將傳遞給請(qǐng)求者的字符串內(nèi)容賦值給window.name。然后,請(qǐng)求者可以檢索window.name值作為響應(yīng)。

  • iframe標(biāo)簽的跨域能力;
  • window.name屬性值在文檔刷新后依舊存在的能力(且最大允許2M左右)。

每個(gè)iframe都有包裹它的window,而這個(gè)window是top window的子窗口。contentWindow屬性返回<iframe>元素的Window對(duì)象。你可以使用這個(gè)Window對(duì)象來(lái)訪問(wèn)iframe的文檔及其內(nèi)部DOM。

<!-- 
 下述用端口 
 10000表示:domainA
 10001表示:domainB
-->

<!-- localhost:10000 -->
<script>
  var iframe = document.createElement('iframe');
  iframe.style.display = 'none'; // 隱藏

  var state = 0; // 防止頁(yè)面無(wú)限刷新
  iframe.onload = function() {
      if(state === 1) {
          console.log(JSON.parse(iframe.contentWindow.name));
          // 清除創(chuàng)建的iframe
          iframe.contentWindow.document.write('');
          iframe.contentWindow.close();
          document.body.removeChild(iframe);
      } else if(state === 0) {
          state = 1;
          // 加載完成,指向當(dāng)前域,防止錯(cuò)誤(proxy.html為空白頁(yè)面)
          // Blocked a frame with origin "http://localhost:10000" from accessing a cross-origin frame.
          iframe.contentWindow.location = 'http://localhost:10000/proxy.html';
      }
  };

  iframe.src = 'http://localhost:10001';
  document.body.appendChild(iframe);
</script>

<!-- localhost:10001 -->
<!DOCTYPE html>
...
<script>
  window.name = JSON.stringify({a: 1, b: 2});
</script>
</html>

方式五:window.postMessage()

HTML5新特性,可以用來(lái)向其他所有的 window 對(duì)象發(fā)送消息。需要注意的是我們必須要保證所有的腳本執(zhí)行完才發(fā)送 MessageEvent,如果在函數(shù)執(zhí)行的過(guò)程中調(diào)用了它,就會(huì)讓后面的函數(shù)超時(shí)無(wú)法執(zhí)行。

下述代碼實(shí)現(xiàn)了跨域存儲(chǔ)localStorage

<!-- 
 下述用端口 
 10000表示:domainA
 10001表示:domainB
-->

<!-- localhost:10000 -->
<iframe src="http://localhost:10001/msg.html" name="myPostMessage" style="display:none;">
</iframe>

<script>
  function main() {
      LSsetItem('test', 'Test: ' + new Date());
      LSgetItem('test', function(value) {
          console.log('value: ' + value);
      });
      LSremoveItem('test');
  }

  var callbacks = {};
  window.addEventListener('message', function(event) {
      if (event.source === frames['myPostMessage']) {
          console.log(event)
          var data = /^#localStorage#(\d+)(null)?#([\S\s]*)/.exec(event.data);
          if (data) {
              if (callbacks[data[1]]) {
                  callbacks[data[1]](data[2] === 'null' ? null : data[3]);
              }
              delete callbacks[data[1]];
          }
      }
  }, false);

  var domain = '*';
  // 增加
  function LSsetItem(key, value) {
      var obj = {
          setItem: key,
          value: value
      };
      frames['myPostMessage'].postMessage(JSON.stringify(obj), domain);
  }
  // 獲取
  function LSgetItem(key, callback) {
      var identifier = new Date().getTime();
      var obj = {
          identifier: identifier,
          getItem: key
      };
      callbacks[identifier] = callback;
      frames['myPostMessage'].postMessage(JSON.stringify(obj), domain);
  }
  // 刪除
  function LSremoveItem(key) {
      var obj = {
          removeItem: key
      };
      frames['myPostMessage'].postMessage(JSON.stringify(obj), domain);
  }
</script>

<!-- localhost:10001 -->
<script>
  window.addEventListener('message', function(event) {
    console.log('Receiver debugging', event);
    if (event.origin == 'http://localhost:10000') {
      var data = JSON.parse(event.data);
      if ('setItem' in data) {
        localStorage.setItem(data.setItem, data.value);
      } else if ('getItem' in data) {
        var gotItem = localStorage.getItem(data.getItem);
        event.source.postMessage(
          '#localStorage#' + data.identifier +
          (gotItem === null ? 'null#' : '#' + gotItem),
          event.origin
        );
      } else if ('removeItem' in data) {
        localStorage.removeItem(data.removeItem);
      }
    }
  }, false);
</script>

注意Safari一下,會(huì)報(bào)錯(cuò):

Blocked a frame with origin “http://localhost:10001” from accessing a frame with origin “http://localhost:10000“. Protocols, domains, and ports must match.

避免該錯(cuò)誤,可以在Safari瀏覽器中勾選開(kāi)發(fā)菜單==>停用跨域限制。或者只能使用服務(wù)器端轉(zhuǎn)存的方式實(shí)現(xiàn),因?yàn)镾afari瀏覽器默認(rèn)只支持CORS跨域請(qǐng)求。

方式六:修改document.domain跨子域

前提條件:這兩個(gè)域名必須屬于同一個(gè)基礎(chǔ)域名!而且所用的協(xié)議,端口都要一致,否則無(wú)法利用document.domain進(jìn)行跨域,所以只能跨子域

在根域范圍內(nèi),允許把domain屬性的值設(shè)置為它的上一級(jí)域。例如,在”aaa.xxx.com”域內(nèi),可以把domain設(shè)置為 “xxx.com” 但不能設(shè)置為 “xxx.org” 或者”com”。

現(xiàn)在存在兩個(gè)域名aaa.xxx.com和bbb.xxx.com。在aaa下嵌入bbb的頁(yè)面,由于其document.name不一致,無(wú)法在aaa下操作bbb的js。可以在aaa和bbb下通過(guò)js將document.name = 'xxx.com';設(shè)置一致,來(lái)達(dá)到互相訪問(wèn)的作用。

方式七:WebSocket

WebSocket protocol 是HTML5一種新的協(xié)議。它實(shí)現(xiàn)了瀏覽器與服務(wù)器全雙工通信,同時(shí)允許跨域通訊,是server push技術(shù)的一種很棒的實(shí)現(xiàn)。相關(guān)文章,請(qǐng)查看:WebSocket、WebSocket-SockJS

需要注意:WebSocket對(duì)象不支持DOM 2級(jí)事件偵聽(tīng)器,必須使用DOM 0級(jí)語(yǔ)法分別定義各個(gè)事件。

方式八:代理

同源策略是針對(duì)瀏覽器端進(jìn)行的限制,可以通過(guò)服務(wù)器端來(lái)解決該問(wèn)題

DomainA客戶端(瀏覽器) ==> DomainA服務(wù)器 ==> DomainB服務(wù)器 ==> DomainA客戶端(瀏覽器)

來(lái)源:blog.csdn.net/ligang2585116/article/details/73072868

87.說(shuō)一下 JSONP 實(shí)現(xiàn)原理?

jsonp 即 json+padding,動(dòng)態(tài)創(chuàng)建script標(biāo)簽,利用script標(biāo)簽的src屬性可以獲取任何域下的js腳本,通過(guò)這個(gè)特性(也可以說(shuō)漏洞),服務(wù)器端不在返貨json格式,而是返回一段調(diào)用某個(gè)函數(shù)的js代碼,在src中進(jìn)行了調(diào)用,這樣實(shí)現(xiàn)了跨域。


九、設(shè)計(jì)模式

88. 說(shuō)一下你熟悉的設(shè)計(jì)模式?

參考:常用的設(shè)計(jì)模式匯總,超詳細(xì)!

89. 簡(jiǎn)單工廠和抽象工廠有什么區(qū)別?

簡(jiǎn)單工廠模式

這個(gè)模式本身很簡(jiǎn)單而且使用在業(yè)務(wù)較簡(jiǎn)單的情況下。一般用于小項(xiàng)目或者具體產(chǎn)品很少擴(kuò)展的情況(這樣工廠類才不用經(jīng)常更改)。

它由三種角色組成:

  • 工廠類角色:這是本模式的核心,含有一定的商業(yè)邏輯和判斷邏輯,根據(jù)邏輯不同,產(chǎn)生具體的工廠產(chǎn)品。如例子中的Driver類。
  • 抽象產(chǎn)品角色:它一般是具體產(chǎn)品繼承的父類或者實(shí)現(xiàn)的接口。由接口或者抽象類來(lái)實(shí)現(xiàn)。如例中的Car接口。
  • 具體產(chǎn)品角色:工廠類所創(chuàng)建的對(duì)象就是此角色的實(shí)例。在java中由一個(gè)具體類實(shí)現(xiàn),如例子中的Benz、Bmw類。

來(lái)用類圖來(lái)清晰的表示下的它們之間的關(guān)系:


image

抽象工廠模式:

先來(lái)認(rèn)識(shí)下什么是產(chǎn)品族: 位于不同產(chǎn)品等級(jí)結(jié)構(gòu)中,功能相關(guān)聯(lián)的產(chǎn)品組成的家族。

image

圖中的BmwCar和BenzCar就是兩個(gè)產(chǎn)品樹(shù)(產(chǎn)品層次結(jié)構(gòu));而如圖所示的BenzSportsCar和BmwSportsCar就是一個(gè)產(chǎn)品族。他們都可以放到跑車家族中,因此功能有所關(guān)聯(lián)。同理BmwBussinessCar和BenzBusinessCar也是一個(gè)產(chǎn)品族。

可以這么說(shuō),它和工廠方法模式的區(qū)別就在于需要?jiǎng)?chuàng)建對(duì)象的復(fù)雜程度上。而且抽象工廠模式是三個(gè)里面最為抽象、最具一般性的。抽象工廠模式的用意為:給客戶端提供一個(gè)接口,可以創(chuàng)建多個(gè)產(chǎn)品族中的產(chǎn)品對(duì)象。

而且使用抽象工廠模式還要滿足一下條件:

  1. 系統(tǒng)中有多個(gè)產(chǎn)品族,而系統(tǒng)一次只可能消費(fèi)其中一族產(chǎn)品
  2. 同屬于同一個(gè)產(chǎn)品族的產(chǎn)品以其使用。

來(lái)看看抽象工廠模式的各個(gè)角色(和工廠方法的如出一轍):

  • 抽象工廠角色: 這是工廠方法模式的核心,它與應(yīng)用程序無(wú)關(guān)。是具體工廠角色必須實(shí)現(xiàn)的接口或者必須繼承的父類。在java中它由抽象類或者接口來(lái)實(shí)現(xiàn)。
  • 具體工廠角色:它含有和具體業(yè)務(wù)邏輯有關(guān)的代碼。由應(yīng)用程序調(diào)用以創(chuàng)建對(duì)應(yīng)的具體產(chǎn)品的對(duì)象。在java中它由具體的類來(lái)實(shí)現(xiàn)。
  • 抽象產(chǎn)品角色:它是具體產(chǎn)品繼承的父類或者是實(shí)現(xiàn)的接口。在java中一般有抽象類或者接口來(lái)實(shí)現(xiàn)。
  • 具體產(chǎn)品角色:具體工廠角色所創(chuàng)建的對(duì)象就是此角色的實(shí)例。在java中由具體的類來(lái)實(shí)現(xiàn)。

103.Java - 死鎖的原因及解決方法

104. 重入鎖和不可重入鎖,公平鎖和不公平鎖的含義和區(qū)別,獨(dú)占鎖和共享鎖的含義和區(qū)別?

答:
公平鎖會(huì)獲取鎖時(shí)會(huì)先判斷阻塞隊(duì)列里是否有線程再等待,若有會(huì)優(yōu)先堵塞隊(duì)列的線程去獲取鎖。
非公平鎖獲取鎖時(shí)不會(huì)判斷阻塞隊(duì)列是否有線程再等待,所以對(duì)于已經(jīng)在等待的線程來(lái)說(shuō)是不公平的,但如果是因?yàn)槠渌驔](méi)有競(jìng)爭(zhēng)到鎖,它也會(huì)加入阻塞隊(duì)列。

進(jìn)入阻塞隊(duì)列的線程,競(jìng)爭(zhēng)鎖時(shí)都是公平的,應(yīng)為隊(duì)列為先進(jìn)先出(FIFO)。

獨(dú)占鎖是一種悲觀保守的加鎖策略,它避免了讀/讀沖突,如果某個(gè)只讀線程獲取鎖,則其他讀線程都只能等待,這種情況下就限制了不必要的并發(fā)性,因?yàn)樽x操作并不會(huì)影響數(shù)據(jù)的一致性,ReentrantLock就是以獨(dú)占方式實(shí)現(xiàn)的互斥鎖。共享鎖則是一種樂(lè)觀鎖,它放寬了加鎖策略,允許多個(gè)執(zhí)行讀操作的線程同時(shí)訪問(wèn)共享資源。 java的并發(fā)包中提供了ReadWriteLock,讀-寫(xiě)鎖。它允許一個(gè)資源可以被多個(gè)讀操作訪問(wèn),或者被一個(gè) 寫(xiě)操作訪問(wèn),但兩者不能同時(shí)進(jìn)行。

105. arraylist和LinkedList的clone方法的區(qū)別?

答:兩個(gè)集合的clone方法都是淺拷貝,只clone對(duì)象本身,不clone對(duì)象里的字段,如果要實(shí)現(xiàn)深拷貝,需要手動(dòng)創(chuàng)建一個(gè)集合,然后通過(guò)遍歷逐個(gè)把舊集合的數(shù)據(jù)拷貝過(guò)去。

106. java位運(yùn)算都有哪些?

答:位運(yùn)算符主要針對(duì)二進(jìn)制,它包括了:“與”、“非”、“或”、“異或”。
(1)“與”運(yùn)算符用符號(hào)“&”表示,其使用規(guī)律如下:
兩個(gè)操作數(shù)中位都為1,結(jié)果才為1,否則結(jié)果為0。
(2)“或”運(yùn)算符用符號(hào)“|”表示,其運(yùn)算規(guī)律如下:
兩個(gè)位只要有一個(gè)為1,那么結(jié)果就是1,否則就為0。
(3)“非”運(yùn)算符用符號(hào)“~”表示,其運(yùn)算規(guī)律如下:
如果位為0,結(jié)果是1,如果位為1,結(jié)果是0。
(4)“異或”運(yùn)算符是用符號(hào)“^”表示的,其運(yùn)算規(guī)律是:
兩個(gè)操作數(shù)的位中,相同則結(jié)果為0,不同則結(jié)果為1。

107.JAVA虛擬機(jī)、Dalvik虛擬機(jī)和ART虛擬機(jī)簡(jiǎn)要對(duì)比

答:(1)JAVA虛擬機(jī)運(yùn)行的是JAVA字節(jié)碼,Dalvik虛擬機(jī)運(yùn)行的是Dalvik字節(jié)碼
(2)Dalvik可執(zhí)行文件體積更小。為了減小執(zhí)行文件的體積,安卓使用Dalvik虛擬機(jī),SDK中有個(gè)dx工具負(fù)責(zé)將JAVA字節(jié)碼轉(zhuǎn)換為Dalvik字節(jié)碼,dx工具對(duì)JAVA類文件重新排列,將所有JAVA類文件中的常量池分解,消除其中的冗余信息,重新組合形成一個(gè)常量池,所有的類文件共享同一個(gè)常量池,使得相同的字符串、常量在DEX文件中只出現(xiàn)一次,從而減小了文件的體積。

(3)JVM基于棧,DVM基于寄存器

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

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

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