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