即將到來金三銀四人才招聘的高峰期,渴望跳槽的朋友肯定跟我一樣四處找以往的面試題,但又感覺找的又不完整,在這里我將把我所見到的題目做一總結,並盡力將答案術語化、標准化。預祝大家面試順利。
術語會讓你的面試更有說服力,讓你感覺更踏實,建議大家多記背點術語。
1. 簡單說下什么是跨平台
術語:操作系統指令集、屏蔽系統之間的差異
由於各種操作系統所支持的指令集不是完全一致,所以在操作系統之上加個虛擬機可以來提供統一接口,屏蔽系統之間的差異。
2. Java有幾種基本數據類型
有八種基本數據類型。
數據類型 | 字節 | 默認值 |
---|---|---|
byte | 1 | 0 |
short | 2 | 0 |
int | 4 | 0 |
long | 8 | 0 |
float | 4 | 0.0f |
double | 8 | 0.0d |
char | 2 | 'u0000' |
boolean | 4 | false |
各自占用幾字節也記一下。
3. 面向對象特征
面向對象的編程語言有封裝、繼承 、抽象、多態等4個主要的特征。
-
封裝: 把描述一個對象的屬性和行為的代碼封裝在一個模塊中,也就是一個類中,屬性用變量定義,行為用方法進行定義,方法可以直接訪問同一個對象中的屬性。
-
抽象: 把現實生活中的對象抽象為類。分為過程抽象和數據抽象
-
數據抽象 -->鳥有翅膀,羽毛等(類的屬性)
-
過程抽象 -->鳥會飛,會叫(類的方法)
-
繼承:子類繼承父類的特征和行為。子類可以有父類的方法,屬性(非private)。子類也可以對父類進行擴展,也可以重寫父類的方法。缺點就是提高代碼之間的耦合性。
-
多態: 多態是指程序中定義的引用變量所指向的具體類型和通過該引用變量發出的方法調用在編程時並不確定,而是在程序運行期間才確定(比如:向上轉型,只有運行才能確定其對象屬性)。方法覆蓋和重載體現了多態性。
4. 為什么要有包裝類型
術語:讓基本類型也具有對象的特征
基本類型 | 包裝器類型 |
---|---|
boolean | Boolean |
char | Character |
int | Integer |
byte | Byte |
short | Short |
long | Long |
float | Float |
double | Double |
為了讓基本類型也具有對象的特征,就出現了包裝類型(如我們在使用集合類型Collection時就一定要使用包裝類型而非基本類型)因為容器都是裝object的,這是就需要這些基本類型的包裝器類了。
自動裝箱:new Integer(6);
,底層調用:Integer.valueOf(6)
自動拆箱: int i = new Integer(6);
,底層調用i.intValue();
方法實現。
1 Integer i = 6; 2 Integer j = 6; 3 System.out.println(i==j);
答案在下面這段代碼中找:
1 public static Integer valueOf(int i) { 2 if (i >= IntegerCache.low && i <= IntegerCache.high) 3 return IntegerCache.cache[i + (-IntegerCache.low)]; 4 return new Integer(i); 5 }
二者的區別:
-
聲明方式不同:基本類型不使用new關鍵字,而包裝類型需要使用new關鍵字來在堆中分配存儲空間;
-
存儲方式及位置不同:基本類型是直接將變量值存儲在棧中,而包裝類型是將對象放在堆中,然后通過引用來使用;
-
初始值不同:基本類型的初始值如int為0,boolean為false,而包裝類型的初始值為null;
-
使用方式不同:基本類型直接賦值直接使用就好,而包裝類型在集合如Collection、Map時會使用到。
5. ==和equals區別
-
==
較的是兩個引用在內存中指向的是不是同一對象(即同一內存空間),也就是說在內存空間中的存儲位置是否一致。如果兩個對象的引用相同時(指向同一對象時),“==”操作符返回true,否則返回flase。 -
equals
用來比較某些特征是否一樣。我們平時用的String類等的equals方法都是重寫后的,實現比較兩個對象的內容是否相等。
我們來看看String重寫的equals方法:
它不止判斷了內存地址,還增加了字符串是否相同的比較。
1 public boolean equals(Object anObject) { 2 //判斷內存地址是否相同 3 if (this == anObject) { 4 return true; 5 } 6 // 判斷參數類型是否是String類型 7 if (anObject instanceof String) { 8 // 強轉 9 String anotherString = (String)anObject; 10 int n = value.length; 11 // 判斷兩個字符串長度是否相等 12 if (n == anotherString.value.length) { 13 char v1[] = value; 14 char v2[] = anotherString.value; 15 int i = 0; 16 // 一一比較 字符是否相同 17 while (n-- != 0) { 18 if (v1[i] != v2[i]) 19 return false; 20 i++; 21 } 22 return true; 23 } 24 } 25 return false; 26 }
6. String、StringBuffer和StringBuilder區別
java中String、StringBuffer、StringBuilder是編程中經常使用的字符串類,他們之間的區別也是經常在面試中會問到的問題。現在總結一下,看看他們的不同與相同。
1. 數據可變和不可變
-
String
底層使用一個不可變的字符數組private final char value[];
所以它內容不可變。 -
StringBuffer
和StringBuilder
都繼承了AbstractStringBuilder
底層使用的是可變字符數組:char[] value;
2. 線程安全
-
StringBuilder
是線程不安全的,效率較高;而StringBuffer
是線程安全的,效率較低。
通過他們的append()
方法來看,StringBuffer
是有同步鎖,而StringBuilder
沒有:
1 @Override 2 public synchronized StringBuffer append(Object obj) { 3 toStringCache = null; 4 super.append(String.valueOf(obj)); 5 return this; 6 }
7 @Override 8 public StringBuilder append(String str) { 9 super.append(str); 10 return this; 11 }
3. 相同點
StringBuilder
與StringBuffer
有公共父類AbstractStringBuilder
。
最后,操作可變字符串速度:StringBuilder > StringBuffer > String
,這個答案就顯得不足為奇了。
7. 講一下Java中的集合
-
Collection下:List系(有序、元素允許重復)和Set系(無序、元素不重復)
set根據equals和hashcode判斷,一個對象要存儲在Set中,必須重寫equals和hashCode方法
-
Map下:HashMap線程不同步;TreeMap線程同步
-
Collection系列和Map系列:Map是對Collection的補充,兩個沒什么關系
8. ArrayList和LinkedList區別?
之前專門有寫過ArrayList和LinkedList源碼的文章。
-
ArrayList是實現了基於動態數組的數據結構,LinkedList基於鏈表的數據結構。
-
對於隨機訪問get和set,ArrayList覺得優於LinkedList,因為LinkedList要移動指針。
-
對於新增和刪除操作add和remove,LinedList比較占優勢,因為ArrayList要移動數據。
9. ConcurrentModificationException異常出現的原因
1 public class Test { 2 public static void main(String[] args) { 3 ArrayList<Integer> list = new ArrayList<Integer>(); 4 list.add(2); 5 Iterator<Integer> iterator = list.iterator(); 6 while(iterator.hasNext()){ 7 Integer integer = iterator.next(); 8 if(integer==2) 9 list.remove(integer); 10 } 11 } 12 }
執行上段代碼是有問題的,會拋出ConcurrentModificationException
異常。
原因:調用list.remove()
方法導致modCount
和expectedModCount
的值不一致。
1 final void checkForComodification() { 2 if (modCount != expectedModCount) 3 throw new ConcurrentModificationException(); 4 }
解決辦法:在迭代器中如果要刪除元素的話,需要調用Iterator
類的remove
方法。
1 public class Test { 2 public static void main(String[] args) { 3 ArrayList<Integer> list = new ArrayList<Integer>(); 4 list.add(2); 5 Iterator<Integer> iterator = list.iterator(); 6 while(iterator.hasNext()){ 7 Integer integer = iterator.next(); 8 if(integer==2) 9 iterator.remove(); //注意這個地方 10 } 11 } 12 }
10. HashMap和HashTable、ConcurrentHashMap區別?
相同點:
-
HashMap和Hashtable都實現了Map接口
-
都可以存儲key-value數據
不同點:
-
HashMap可以把null作為key或value,HashTable不可以
-
HashMap線程不安全,效率高。HashTable線程安全,效率低。
-
HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。
什么是fail-fast?
就是最快的時間能把錯誤拋出而不是讓程序執行。
10.2 如何保證線程安全又效率高?
Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的擴展性更好。
ConcurrentHashMap將整個Map分為N個segment(類似HashTable),可以提供相同的線程安全,但是效率提升N倍,默認N為16。
10.3 我們能否讓HashMap同步?
HashMap可以通過下面的語句進行同步:Map m = Collections.synchronizeMap(hashMap);
11. 拷貝文件的工具類使用字節流還是字符流
答案:字節流
11.1 什么是字節流,什么是字符流?
字節流:傳遞的是字節(二進制),
字符流:傳遞的是字符
11.2 答案
我們並不支持下載的文件有沒有包含字節流(圖片、影像、音源),所以考慮到通用性,我們會用字節流。
12. 線程創建方式
這個之前自己做過總結,也算比較全面。
方法一:繼承Thread類,作為線程對象存在(繼承Thread對象)
1 public class CreatThreadDemo1 extends Thread{ 2 /** 3 * 構造方法: 繼承父類方法的Thread(String name);方法 4 * @param name 5 */ 6 public CreatThreadDemo1(String name){ 7 super(name); 8 } 9 10 @Override 11 public void run() { 12 while (!interrupted()){ 13 System.out.println(getName()+"線程執行了..."); 14 try { 15 Thread.sleep(200); 16 } catch (InterruptedException e) { 17 e.printStackTrace(); 18 } 19 } 20 } 21 22 public static void main(String[] args) { 23 CreatThreadDemo1 d1 = new CreatThreadDemo1("first"); 24 CreatThreadDemo1 d2 = new CreatThreadDemo1("second"); 25 26 d1.start(); 27 d2.start(); 28 29 d1.interrupt(); //中斷第一個線程 30 } 31 }
常規方法,不多做介紹了,interrupted方法,是來判斷該線程是否被中斷。(終止線程不允許用stop方法,該方法不會施放占用的資源。所以我們在設計程序的時候,要按照中斷線程的思維去設計,就像上面的代碼一樣)。
讓線程等待的方法
-
Thread.sleep(200); //線程休息2ms
-
Object.wait(); //讓線程進入等待,直到調用Object的notify或者notifyAll時,線程停止休眠
方法二:實現runnable接口,作為線程任務存在
1 public class CreatThreadDemo2 implements Runnable { 2 @Override 3 public void run() { 4 while (true){ 5 System.out.println("線程執行了..."); 6 } 7 } 8 9 public static void main(String[] args) { 10 //將線程任務傳給線程對象 11 Thread thread = new Thread(new CreatThreadDemo2()); 12 //啟動線程 13 thread.start(); 14 } 15 }
Runnable 只是來修飾線程所執行的任務,它不是一個線程對象。想要啟動Runnable對象,必須將它放到一個線程對象里。
方法三:匿名內部類創建線程對象
1 public class CreatThreadDemo3 extends Thread{ 2 public static void main(String[] args) { 3 //創建無參線程對象 4 new Thread(){ 5 @Override 6 public void run() { 7 System.out.println("線程執行了..."); 8 } 9 }.start(); 10 //創建帶線程任務的線程對象 11 new Thread(new Runnable() { 12 @Override 13 public void run() { 14 System.out.println("線程執行了..."); 15 } 16 }).start(); 17 //創建帶線程任務並且重寫run方法的線程對象 18 new Thread(new Runnable() { 19 @Override 20 public void run() { 21 System.out.println("runnable run 線程執行了..."); 22 } 23 }){ 24 @Override 25 public void run() { 26 System.out.println("override run 線程執行了..."); 27 } 28 }.start(); 29 } 30 31 }
創建帶線程任務並且重寫run方法的線程對象中,為什么只運行了Thread的run方法。我們看看Thread類的源碼,
,我們可以看到Thread實現了Runnable接口,而Runnable接口里有一個run方法。
所以,我們最終調用的重寫的方法應該是Thread類的run方法。而不是Runnable接口的run方法。
方法四:創建帶返回值的線程
1 public class CreatThreadDemo4 implements Callable { 2 public static void main(String[] args) throws ExecutionException, InterruptedException { 3 CreatThreadDemo4 demo4 = new CreatThreadDemo4(); 4 5 FutureTask<Integer> task = new FutureTask<Integer>(demo4); //FutureTask最終實現的是runnable接口 6 7 Thread thread = new Thread(task); 8 9 thread.start(); 10 11 System.out.println("我可以在這里做點別的業務邏輯...因為FutureTask是提前完成任務"); 12 //拿出線程執行的返回值 13 Integer result = task.get(); 14 System.out.println("線程中運算的結果為:"+result); 15 } 16 17 //重寫Callable接口的call方法 18 @Override 19 public Object call() throws Exception { 20 int result = 1; 21 System.out.println("業務邏輯計算中..."); 22 Thread.sleep(3000); 23 return result; 24 } 25 } 26 Callable接口介紹: 27 28 public interface Callable<V> { 29 /** 30 * Computes a result, or throws an exception if unable to do so. 31 * 32 * @return computed result 33 * @throws Exception if unable to compute a result 34 */ 35 V call() throws Exception; 36 }
返回指定泛型的call方法。然后調用FutureTask對象的get方法得道call方法的返回值。
方法五:定時器Timer
1 public class CreatThreadDemo5 { 2 3 public static void main(String[] args) { 4 Timer timer = new Timer(); 5 6 timer.schedule(new TimerTask() { 7 @Override 8 public void run() { 9 System.out.println("定時器線程執行了..."); 10 } 11 },0,1000); //延遲0,周期1s 12 13 } 14 }
方法六:線程池創建線程
1 public class CreatThreadDemo6 { 2 public static void main(String[] args) { 3 //創建一個具有10個線程的線程池 4 ExecutorService threadPool = Executors.newFixedThreadPool(10); 5 long threadpoolUseTime = System.currentTimeMillis(); 6 for (int i = 0;i<10;i++){ 7 threadPool.execute(new Runnable() { 8 @Override 9 public void run() { 10 System.out.println(Thread.currentThread().getName()+"線程執行了..."); 11 } 12 }); 13 } 14 long threadpoolUseTime1 = System.currentTimeMillis(); 15 System.out.println("多線程用時"+(threadpoolUseTime1-threadpoolUseTime)); 16 //銷毀線程池 17 threadPool.shutdown(); 18 threadpoolUseTime = System.currentTimeMillis(); 19 } 20 21 }
方法七:利用java8新特性 stream 實現並發
lambda表達式不懂的,可以看看我的java8新特性文章:
java8-lambda:
https://www.jianshu.com/p/3a08dc78a05f
java8-stream:
https://www.jianshu.com/p/ea16d6712a00
1 public class CreatThreadDemo7 { 2 public static void main(String[] args) { 3 List<Integer> values = Arrays.asList(10,20,30,40); 4 //parallel 平行的,並行的 5 int result = values.parallelStream().mapToInt(p -> p*2).sum(); 6 System.out.println(result); 7 //怎么證明它是並發處理呢 8 values.parallelStream().forEach(p-> System.out.println(p)); 9 } 10 }
輸出:
200
40
10
20
30
怎么證明它是並發處理呢,他們並不是按照順序輸出的 。
文集介紹
該專題分為Java基礎、計算機網絡、操作系統、數據結構、算法精讀、數據庫面試題、框架面試題、服務高可用、分布式事務、分布式鎖、消息隊列等部分,盡量將全網的面試題一網打盡,方便大家手機閱讀和收藏。
每篇會精講18個問題,數量可以商討,評論區見。