Java面试要点
2019獨(dú)角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>
Java要點(diǎn)
1. Object的hashCode方法,equals方法和clone方法在實(shí)現(xiàn)的時(shí)候要注意什么?
在一個(gè)運(yùn)行的進(jìn)程中,相等的對象必須要有相同的哈希碼;不同的對象可以有相同的哈希碼;
1.無論你何時(shí)實(shí)現(xiàn)equals方法,你必須同時(shí)實(shí)現(xiàn)hashCode方法;
2.永遠(yuǎn)不要把哈希碼誤用作為key,哈希沖突是很常見的事情;hashmap中的contains方法的實(shí)現(xiàn)!
3.哈希碼可變,hashcode并不保證在不同的應(yīng)用執(zhí)行中得到相同的結(jié)果;
4.在分布式應(yīng)用中不要使用哈希碼。
替代哈希碼:SHA1,加密的哈希碼,160位密鑰,沖突幾乎是不可能的。
集合增加時(shí)的原理:
當(dāng)使用HashSet時(shí),hashCode()方法就會被調(diào)用,判斷已經(jīng)存儲在集合中的對象的hashCode值是否與增加的對象的hashCode值一致;如果不一致,直接加進(jìn)去;如果一致,再進(jìn)行equals方法的比較,equals方法如果返回true,表示對象已經(jīng)加進(jìn)去了,就不會再增加新的對象,否則加進(jìn)去。
使用clone()方法的步驟:
1.實(shí)現(xiàn)clone的類首先需要繼承Cloneable接口。
2.在類中重寫Object類中的clone()方法。
3.在clone方法中調(diào)用super.clone()。
4.把淺復(fù)制的引用指向原型對象新的克隆體。
2. HashMap是怎么實(shí)現(xiàn)的?
1.HashMap的數(shù)據(jù)結(jié)構(gòu)
數(shù)組的特點(diǎn)是:尋址容易,插入和刪除困難;而鏈表的特點(diǎn)是:尋址困難,插入和刪除容易。那么我們能不能綜合兩者的特性,做出一種尋址容易,插入刪除也容易的數(shù)據(jù)結(jié)構(gòu)?答案是肯定的,這就是我們要提起的哈希表,哈希表有多種不同的實(shí)現(xiàn)方法,我接下來解釋的是最常用的一種方法——拉鏈法,我們可以理解為“鏈表的數(shù)組”,如圖:
從上圖我們可以發(fā)現(xiàn)哈希表是由數(shù)組+鏈表組成的,一個(gè)長度為16的數(shù)組中,每個(gè)元素存儲的是一個(gè)鏈表的頭結(jié)點(diǎn)。那么這些元素是按照什么樣的規(guī)則存儲到數(shù)組中呢。一般情況是通過hash(key)%len獲得,也就是元素的key的哈希值對數(shù)組長度取模得到。比如上述哈希表中,12%16=12,28%16=12,108%16=12,140%16=12。所以12、28、108以及140都存儲在數(shù)組下標(biāo)為12的位置。
HashMap其實(shí)也是一個(gè)線性的數(shù)組實(shí)現(xiàn)的,所以可以理解為其存儲數(shù)據(jù)的容器就是一個(gè)線性數(shù)組。這可能讓我們很不解,一個(gè)線性的數(shù)組怎么實(shí)現(xiàn)按鍵值對來存取數(shù)據(jù)呢?這里HashMap有做一些處理。
首先HashMap里面實(shí)現(xiàn)一個(gè)靜態(tài)內(nèi)部類Entry,其重要的屬性有key,value,next,從屬性key,value我們就能很明顯的看出來Entry就是HashMap鍵值對實(shí)現(xiàn)的一個(gè)基礎(chǔ)bean,我們上面說到HashMap的基礎(chǔ)就是一個(gè)線性數(shù)組,這個(gè)數(shù)組就是Entry[],Map里面的內(nèi)容都保存在Entry[]里面。
2.HashMap的存取實(shí)現(xiàn)
既然是線性數(shù)組,為什么能隨機(jī)存取?這里HashMap用了一個(gè)小算法,大致是這樣實(shí)現(xiàn):
//存儲時(shí): int hash = key.hashCode();// 這個(gè)hashCode方法這里不詳述,只要理解每個(gè)key的hash是一個(gè)固定的int值 int index = hash % Entry[].length; Entry[index] = value; //取值時(shí): int hash = key.hashCode(); int index = hash % Entry[].length; return Entry[index];3. Java內(nèi)部類為什么可以訪問外部類的成員?
內(nèi)部類都持有一個(gè)外部類的引用,這個(gè)是引用是外部類名.this。內(nèi)部類可以定義在外部類中的成員位置上,也可以定義在外部類中的局部位置上。當(dāng)內(nèi)部類被定義在局部位置上,只能訪問局部中被final修飾的局部變量。
如果內(nèi)部類被靜態(tài)修飾,相當(dāng)于外部類,會出現(xiàn)訪問局限性,只能訪問外部類中的靜態(tài)成員。
注意:如果內(nèi)部類中定義了靜態(tài)成員,那么該內(nèi)部類必須是靜態(tài)的。內(nèi)部類編譯后的文件名為:“外部類名$內(nèi)部類名.java"
4. ArrayList如何邊遍歷邊刪除?多線程訪問ArrayList怎么做到互斥訪問?
ArrayList邊遍歷邊刪除——迭代器:
ArrayList<String> list = new ArrayList<String>(); list.add("one"); list.add("two"); list.add("two"); list.add("two"); list.add("two"); Iterator<String> iter = list.iterator(); while(iter.hasNext()){String s = iter.next();if(s.equals("two")){iter.remove();} } System.out.println(list);ListIterator和Iterator的區(qū)別:
1.ListIterator實(shí)現(xiàn)了Iterator接口,擁有Iterator的所有方法。
2.ListIterator有add()和set()方法,可以向List中添加/修改對象,而Iterator不能。
3.ListIterator和Iterator都有hasNext()和next()方法,可以實(shí)現(xiàn)順序向后遍歷。但是ListIterator有hasPrevious()和previous()方法,可以實(shí)現(xiàn)逆向遍歷,而Iterator不能。
4.ListIterator可以定位當(dāng)前的索引位置,nextIndex()和previousIndex()可以實(shí)現(xiàn)。Iterator沒有此功能。
使ArrayList線程安全:
1.將訪問ArrayList的方法設(shè)置為synchronized;
2.List list = Collections.synchronizedList(new ArrayList());
5. Java的四中引用類型
強(qiáng)引用:JVM寧愿拋出OOM也不會將它回收,可能導(dǎo)致內(nèi)存泄露
軟引用:當(dāng)內(nèi)存空間不足的時(shí)候才會去回收軟引用的對象
弱引用:在系統(tǒng)GC時(shí),弱引用的對象一定會被回收,軟弱引用適合保存那些可有可無的緩存數(shù)據(jù)
虛引用:虛引用跟沒有引用差不多,即使虛引用對象還存在,get方法總是返回null,它最大的作用是跟蹤對象回收,清理被銷毀對象的相關(guān)資源
WeakHashMap適用場景:如果系統(tǒng)需要一張很大的map表,map中的表項(xiàng)作為緩存之用,即使沒能從map中拿到數(shù)據(jù)也沒關(guān)系的情況下。一旦內(nèi)存不足的時(shí)候,WeakHashMap會將沒有被引用的表項(xiàng)清除掉,從而避免內(nèi)存溢出。它是實(shí)現(xiàn)緩存的一種特別好的方式。
如果希望WeakHashMap能夠自動清理數(shù)據(jù)就不要在系統(tǒng)的其他地方強(qiáng)引用WeakHashMap的key,否則,這些key不會被回收。
6. JVM
JVM內(nèi)存模型:程序計(jì)數(shù)器,虛擬機(jī)棧,本地方法棧,Java堆,方法區(qū)
程序計(jì)數(shù)器:程序計(jì)數(shù)器是一塊較小的內(nèi)存空間,它的作用可以看做是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號指示器。字節(jié)碼解釋器工作時(shí)就是通過改變這個(gè)計(jì)數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令,分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)等基礎(chǔ)功能都需要依賴這個(gè)計(jì)數(shù)器來完成。
虛擬機(jī)棧:與程序計(jì)數(shù)器一樣,Java 虛擬機(jī)棧(Java Virtual Machine Stacks)也是線程私有的,它的生命周期與線程相同。虛擬機(jī)棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個(gè)方法被執(zhí)行的時(shí)候都會同時(shí)創(chuàng)建一個(gè)棧幀(Stack Frame)用于存儲局部變量表、操作棧、動態(tài)鏈接、方法出口等信息。
本地方法棧:本地方法棧(Native Method Stacks)與虛擬機(jī)棧所發(fā)揮的作用是非常相似的,其區(qū)別不過是虛擬機(jī)棧為虛擬機(jī)執(zhí)行Java方法(也就是字節(jié)碼)服務(wù),而本地方法棧則是為虛擬機(jī)使用到的Native方法服務(wù)。
Java堆:幾乎所有的對象和數(shù)組都是在堆中分配空間的,分為新生代和老年代。新生代可分為eden, survivor space 0, survivor space 1
方法區(qū):方法區(qū)(Method Area)與Java 堆一樣,是各個(gè)線程共享的內(nèi)存區(qū)域,它用于存儲已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。在Hot spot虛擬機(jī)中,方法區(qū)也叫永久區(qū),但是也會被GC, GC主要有兩類:對常量池的回收,對類元數(shù)據(jù)的回收
如果VM確認(rèn)所有該類的實(shí)例都被回收并且裝載該類的類加載器也被回收了,那么就回收該類的元數(shù)據(jù)
運(yùn)行時(shí)常量池(Runtime Constant Pool)是方法區(qū)的一部分。(不一定)
Java語言并不要求常量一定只能在編譯期產(chǎn)生,也就是并非預(yù)置入Class文件中常量池的內(nèi)容才能進(jìn)入方法區(qū)運(yùn)行時(shí)常量池,運(yùn)行期間也可能將新的常量放入池中,這種特性被開發(fā)人員利用得比較多的便是String類的intern()方法。
JVM參數(shù):設(shè)置最大堆內(nèi)存Xmx,最小堆內(nèi)存Xms,新生代大小Xmn,老年代大小PermSize,線程棧大小Xss,新生代eden和s0空間大小比例以及老年代和新生代的空間大小比例
垃圾回收算法
(1)引用計(jì)數(shù)法:缺點(diǎn)是無法處理循環(huán)引用問題
(2)標(biāo)記一清除法:標(biāo)記所有從根節(jié)點(diǎn)開始的可達(dá)對象,清除所有未被標(biāo)記的對象。缺點(diǎn)是會造成內(nèi)存空間不連續(xù),不連續(xù)的內(nèi)存空間的工作效率低于連續(xù)的內(nèi)存空間,不容易分配內(nèi)存
(3)復(fù)制算法:將內(nèi)存空間分成兩塊,每次將正在使用的內(nèi)存中的存活對象復(fù)制到未使用的內(nèi)存塊中,算法效率高,但是代價(jià)是將系統(tǒng)內(nèi)存折半。適用于新生代(存活對象少,垃圾對象多)
(4)標(biāo)記一壓縮算法:標(biāo)記一清除的改進(jìn),清除未標(biāo)記的對象時(shí)還將所有的存活對象壓縮到內(nèi)存的一端既避免碎片產(chǎn)生,又不需要兩塊同樣大小的內(nèi)存塊,性價(jià)比高。適用于老年代
(5)分代:把堆分成兩個(gè)或者多個(gè)子堆,每一個(gè)子堆被視為一代。算法在運(yùn)行的過程中優(yōu)先收集那些“年幼”的對象,如果一個(gè)對象經(jīng)過多次收集仍然“存活”,那么就可以把這個(gè)對象轉(zhuǎn)移到高一級的堆里,減少對其的掃描次數(shù)。
垃圾回收器的類型
(1)線程數(shù):串行,并行
(2)工作模式:并發(fā),獨(dú)占
(3)碎片處理:壓縮,非壓縮
(4)分代:新生代,老年代
并行:開啟多個(gè)線程同時(shí)進(jìn)行垃圾回收,縮短GC停頓時(shí)間
并發(fā):垃圾回收線程和應(yīng)用程序線程交替工作
CMS: Concurrent Mark Sweep 并發(fā)標(biāo)記清除,減少GC造成的停頓時(shí)間。過程:初始標(biāo)記,并發(fā)標(biāo)記,重新標(biāo)記,并發(fā)清理,并發(fā)重置
G1:基于標(biāo)記-整理算法
GC Roots有哪些?
1.虛擬機(jī)棧(棧幀中的本地變量表)中引用的對象
2.方法區(qū)中的類靜態(tài)屬性引用的對象
3.方法區(qū)中的常量引用的對象
4.原生方法棧(Native Method Stack)中 JNI 中引用的對象
對方法區(qū)的定義是:各個(gè)線程共享的內(nèi)存區(qū)域,存儲已被虛擬機(jī)加載的類信息,變量,靜態(tài)變量等數(shù)據(jù)。
7. 抽象類和接口的區(qū)別
1.抽象類只能被繼承,而且只能單繼承。接口需要被實(shí)現(xiàn),而且可以多實(shí)現(xiàn)。
2.抽象類中可以定義非抽象方法,子類可以直接繼承使用。接口中都是抽象方法,需要實(shí)現(xiàn)類去實(shí)現(xiàn)。
3.抽象類使用的是is-a關(guān)系。接口使用的has-a系。
4.抽象類的成員修飾符可以自定義,接口中的成員修飾符是固定的,全都是public的。
8. ThreadLocal
首先,ThreadLocal不是用來解決共享對象的多線程訪問問題的,一般情況下,通過ThreadLocal.set()到線程中的對象是該線程自己使用的對象,其他線程是不需要訪問的,也訪問不到的。各個(gè)線程中訪問的是不同的對象。
另外,說ThreadLocal使得各線程能夠保持各自獨(dú)立的一個(gè)對象,并不是通過ThreadLocal.set()來實(shí)現(xiàn)的,而是通過每個(gè)線程中的new對象的操作來創(chuàng)建的對象,每個(gè)線程創(chuàng)建一個(gè),不是什么對象的拷貝或副本。通過ThreadLocal.set()將這個(gè)新創(chuàng)建的對象的引用保存到各線程的自己的一個(gè)map中,每個(gè)線程都有這樣一個(gè)map,執(zhí)行ThreadLocal.get()時(shí),各線程從自己的map中取出放進(jìn)去的對象,因此取出來的是各自自己線程中的對象,ThreadLocal實(shí)例是作為map的key來使用的。
下面來看一個(gè)hibernate中典型的ThreadLocal的應(yīng)用:
private static final ThreadLocal threadSession = new ThreadLocal(); public static Session getSession() throws InfrastructureException {Session s = (Session) threadSession.get();try {if (s == null) {s = getSessionFactory().openSession();threadSession.set(s);}} catch (HibernateException ex) {throw new InfrastructureException(ex);}return s; }總之,ThreadLocal不是用來解決對象共享訪問問題的,而主要是提供了保持對象的方法和避免參數(shù)傳遞的方便的對象訪問方式。歸納了兩點(diǎn):
1.每個(gè)線程中都有一個(gè)自己的ThreadLocalMap類對象,可以將線程自己的對象保持到其中,各管各的,線程可以正確的訪問到自己的對象。
2.將一個(gè)共用的ThreadLocal靜態(tài)實(shí)例作為key,將不同對象的引用保存到不同線程的
ThreadLocalMap中,然后在線程執(zhí)行的各處通過這個(gè)靜態(tài)ThreadLocal實(shí)例的get()方法取得自己線程保存的那個(gè)對象,避免了將這個(gè)對象作為參數(shù)傳遞的麻煩。
public class ThreadLocal<T> {private final int threadLocalHashCode = nextHashCode();private static int nextHashCode = 0;private static final int HASH_INCREMENT = 0x61c88647;private static synchronized int nextHashCode() {int h = nextHashCode;nextHashCode = h + HASH_INCREMENT;return h;}public T get() {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)return (T)map.get(this);T value = initialValue();createMap(t, value);return value;}public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);}ThreadLocalMap getMap(Thread t) {return t.threadLocals;}void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);}....... }ThreadLocalMap 類是ThreadLocal中定義的內(nèi)部類,但是它的實(shí)例卻用在Thread類中:
public class Thread implements Runnable {......ThreadLocal.ThreadLocalMap threadLocals = null; ...... }9. 設(shè)計(jì)模式
1.單例模式
2.工廠模式
簡單工廠模式 工廠方法模式 抽象工廠模式
3.適配器模式
類適配器 對象適配器 缺省適配模式
4.觀察者模式
5.生產(chǎn)者-消費(fèi)者模式
6.享元模式
如果在一個(gè)系統(tǒng)中存在多個(gè)相同的對象,那么只需要共享一份對象的拷貝,而不必為每一次使用都創(chuàng)建新的對象。類似對象池,但是不同的是前者保存的對象是不可以相互替換的,而后者可以。
10. 位運(yùn)算
& 按位與 相同位的兩個(gè)數(shù)字都為1,則為1;若有一個(gè)不為1,則為0。(5&3=1)
| 按位或 相同位只要一個(gè)為1即為1。(5|3=7)
^ 按位異或 相同位不同則為1,相同則為0。(5^3=6)
~ 按位取反 把內(nèi)存中的0和1全部取反。(~5=-6)
<< 左移 (5<<1=10)
>>?有符號右移 (5>>1=2)
>>>?無符號右移 (5>>>1=2)
轉(zhuǎn)載于:https://my.oschina.net/itmaker/blog/1787393
總結(jié)
- 上一篇: 微服务的降级学习
- 下一篇: Java 8 CompletableFu