2019年Java面試題基礎系列228道(3),查漏補缺!

2019年Java面試題基礎系列228道

第一篇更新1~20題的答案解析

2019年Java面試題基礎系列228道(1),快看看哪些你還不會?

第二篇更新21~50題答案解析

2019年Java面試題基礎系列228道(2),查漏補缺!

 

本次更新Java 面試題(一)的51~95題答案

51、類 ExampleA 繼承 Exception,類 ExampleB 繼承 ExampleA。

52、List、Set、Map 是否繼承自 Collection 接口?

53、闡述 ArrayList、Vector、LinkedList 的存儲性能和特性。

54、Collection 和 Collections 的區別?

55、List、Map、Set 三個接口存取元素時,各有什麼特點?

56、TreeMap 和 TreeSet 在排序時如何比較元素?Collections 工具類中的 sort()方法如何比較元素?

57、Thread 類的 sleep()方法和對象的 wait()方法都可以讓線程暫停執行,它們有什麼區別?

58、線程的 sleep()方法和 yield()方法有什麼區別?

59、當一個線程進入一個對象的 synchronized 方法 A 之後,其它線程是否可進入此對象 synchronized 方法 B?

60、請說出與線程同步以及線程調度相關的方法。

61、編寫多線程程序有幾種實現方式?

62、synchronized 關鍵字的用法?

63、舉例說明同步和異步。

64、啓動一個線程是調用 run()還是 start()方法?

65、什麼是線程池(thread pool)?

66、線程的基本狀態以及狀態之間的關係?

67、簡述 synchronized 和 java.util.concurrent.locks.Lock 的異同?

68、Java 中如何實現序列化,有什麼意義?

69、Java 中有幾種類型的流?

70、寫一個方法,輸入一個文件名和一個字符串,統計這個字符串在這個文件中出現的次數。

71、如何用 Java 代碼列出一個目錄下所有的文件?

72、用 Java 的套接字編程實現一個多線程的回顯(echo)服務器。

73、XML 文檔定義有幾種形式?它們之間有何本質區別?解析 XML文檔有哪幾種方式?

74、你在項目中哪些地方用到了 XML?

75、闡述 JDBC 操作數據庫的步驟。

76、Statement 和 PreparedStatement 有什麼區別?哪個性能更好?

77、使用 JDBC 操作數據庫時,如何提升讀取數據的性能?如何提升更新數據的性能?

78、在進行數據庫編程時,連接池有什麼作用?

79、什麼是 DAO 模式?

80、事務的 ACID 是指什麼?

82、JDBC 能否處理 Blob 和 Clob?

83、簡述正則表達式及其用途。

84、Java 中是如何支持正則表達式操作的?

85、獲得一個類的類對象有哪些方式?

88、如何通過反射調用對象的方法?

90、簡述一下你瞭解的設計模式。

91、用 Java 寫一個單例類。

92、什麼是 UML?

93、UML 中有哪些常用的圖?

95、用 Java 寫一個折半查找。

 

51、類 ExampleA 繼承 Exception,類 ExampleB 繼承ExampleA。

有如下代碼片斷:

try {
	throw new ExampleB("b")
}
catch(ExampleA e){
	System.out.println("ExampleA");
}
catch(Exception e){
	System.out.println("Exception");
}

**請問執行此段代碼的輸出是什麼?

答:

輸出:ExampleA。(根據里氏代換原則[能使用父類型的地方一定能使用子類型],抓取 ExampleA 類型異常的 catch 塊能夠抓住 try 塊中拋出的 ExampleB 類型的異常)

面試題 - 說出下面代碼的運行結果。(此題的出處是《Java 編程思想》一書)

class Annoyance extends Exception {
}
class Sneeze extends Annoyance {
}
class Human {
	public static void main(String[] args)
	throws Exception {
		try {
			try {
				throw new Sneeze();
			}
			catch ( Annoyance a ) {
				System.out.println("Caught Annoyance");
				throw a;
			}
		}
		catch ( Sneeze s ) {
			System.out.println("Caught Sneeze");
			return ;
		}
		finally {
			System.out.println("Hello World!");
		}
	}
}

 

52、List、Set、Map 是否繼承自 Collection 接口?

List、Set 是 ,Map 不是。Map 是鍵值對映射容器,與 List 和 Set 有明顯的區別,而 Set 存儲的零散的元素且不允許有重複元素(數學中的集合也是如此),List是線性結構的容器,適用於按數值索引訪問元素的情形。

53、闡述 ArrayList、Vector、LinkedList 的存儲性能和特性。

ArrayList 和 Vector 都是使用數組方式存儲數據,此數組元素數大於實際存儲的數據以便增加和插入元素,它們都允許直接按序號索引元素,但是插入元素要涉及數組元素移動等內存操作,所以索引數據快而插入數據慢,Vector 中的方法由於添加了 synchronized 修飾,因此 Vector 是線程安全的容器,但性能上較ArrayList 差,因此已經是 Java 中的遺留容器。LinkedList 使用雙向鏈表實現存儲(將內存中零散的內存單元通過附加的引用關聯起來,形成一個可以按序號索引的線性結構,這種鏈式存儲方式與數組的連續存儲方式相比,內存的利用率更高),按序號索引數據需要進行前向或後向遍歷,但是插入數據時只需要記錄本項的前後項即可,所以插入速度較快。Vector 屬於遺留容器(Java 早期的版本中提供的容器,除此之外,Hashtable、Dictionary、BitSet、Stack、Properties都是遺留容器),已經不推薦使用,但是由於 ArrayList 和 LinkedListed 都是非線程安全的,如果遇到多個線程操作同一個容器的場景,則可以通過工具類Collections 中的 synchronizedList 方法將其轉換成線程安全的容器後再使用(這是對裝潢模式的應用,將已有對象傳入另一個類的構造器中創建新的對象來增強實現)。

補充:遺留容器中的 Properties 類和 Stack 類在設計上有嚴重的問題,Properties是一個鍵和值都是字符串的特殊的鍵值對映射,在設計上應該是關聯一個Hashtable 並將其兩個泛型參數設置爲 String 類型,但是 Java API 中的Properties 直接繼承了 Hashtable,這很明顯是對繼承的濫用。這裏複用代碼的方式應該是 Has-A 關係而不是 Is-A 關係,另一方面容器都屬於工具類,繼承工具類本身就是一個錯誤的做法,使用工具類最好的方式是 Has-A 關係(關聯)或Use-A 關係(依賴)。同理,Stack 類繼承 Vector 也是不正確的。Sun 公司的工程師們也會犯這種低級錯誤,讓人唏噓不已。

54、Collection 和 Collections 的區別?

Collection 是一個接口,它是 Set、List 等容器的父接口;Collections 是個一個工具類,提供了一系列的靜態方法來輔助容器操作,這些方法包括對容器的搜索、排序、線程安全化等等。

55、List、Map、Set 三個接口存取元素時,各有什麼特點?

List 以特定索引來存取元素,可以有重複元素。Set 不能存放重複元素(用對象的equals()方法來區分元素是否重複)。Map 保存鍵值對(key-value pair)映射,映射關係可以是一對一或多對一。Set 和 Map 容器都有基於哈希存儲和排序樹的兩種實現版本,基於哈希存儲的版本理論存取時間複雜度爲 O(1),而基於排序樹版本的實現在插入或刪除元素時會按照元素或元素的鍵(key)構成排序樹從而達到排序和去重的效果。

56、TreeMap 和 TreeSet 在排序時如何比較元素?Collections 工具類中的 sort()方法如何比較元素?

TreeSet 要求存放的對象所屬的類必須實現 Comparable 接口,該接口提供了比較元素的 compareTo()方法,當插入元素時會回調該方法比較元素的大小。TreeMap 要求存放的鍵值對映射的鍵必須實現 Comparable 接口從而根據鍵對元素進 行排 序。Collections 工具類的 sort 方法有兩種重載的形式,第一種要求傳入的待排序容器中存放的對象比較實現 Comparable 接口以實現元素的比較;第二種不強制性的要求容器中的元素必須可比較,但是要求傳入第二個參數,參數是Comparator 接口的子類型(需要重寫 compare 方法實現元素的比較),相當於一個臨時定義的排序規則,其實就是通過接口注入比較元素大小的算法,也是對回調模式的應用(Java 中對函數式編程的支持)。

57、Thread 類的 sleep()方法和對象的 wait()方法都可以讓線程暫停執行,它們有什麼區別?

sleep()方法(休眠)是線程類(Thread)的靜態方法,調用此方法會讓當前線程暫停執行指定的時間,將執行機會(CPU)讓給其他線程,但是對象的鎖依然保持,因此休眠時間結束後會自動恢復(線程回到就緒狀態,請參考第 66 題中的線程狀態轉換圖)。wait()是 Object 類的方法,調用對象的 wait()方法導致當前線程放棄對象的鎖(線程暫停執行),進入對象的等待池(wait pool),只有調用對象的 notify()方法(或 notifyAll()方法)時才能喚醒等待池中的線程進入等鎖池(lock pool),如果線程重新獲得對象的鎖就可以進入就緒狀態。

補充:可能不少人對什麼是進程,什麼是線程還比較模糊,對於爲什麼需要多線程編程也不是特別理解。簡單的說:進程是具有一定獨立功能的程序關於某個數據集合上的一次運行活動,是操作系統進行資源分配和調度的一個獨立單位;線程是進程的一個實體,是 CPU 調度和分派的基本單位,是比進程更小的能獨立運行的基本單位。線程的劃分尺度小於進程,這使得多線程程序的併發性高;進程在執行時通常擁有獨立的內存單元,而線程之間可以共享內存。使用多線程的編程通常能夠帶來更好的性能和用戶體驗,但是多線程的程序對於其他程序是不友好的,因爲它可能佔用了更多的 CPU 資源。當然,也不是線程越多,程序的性能就越好,因爲線程之間的調度和切換也會浪費 CPU 時間。時下很時髦的 Node.js就採用了單線程異步 I/O 的工作模式。

58、線程的 sleep()方法和 yield()方法有什麼區別?

(1) sleep()方法給其他線程運行機會時不考慮線程的優先級,因此會給低優先級的線程以運行的機會;yield()方法只會給相同優先級或更高優先級的線程以運行的機會;

(2) 線程執行 sleep()方法後轉入阻塞(blocked)狀態,而執行 yield()方法後轉入就緒(ready)狀態;

(3)sleep()方法聲明拋出 InterruptedException,而 yield()方法沒有聲明任何異常;

(4)sleep()方法比 yield()方法(跟操作系統 CPU 調度相關)具有更好的可移植性。

59、當一個線程進入一個對象的 synchronized 方法 A 之後,其它線程是否可進入此對象的 synchronized 方法 B?

不能。其它線程只能訪問該對象的非同步方法,同步方法則不能進入。因爲非靜態方法上的 synchronized 修飾符要求執行方法時要獲得對象的鎖,如果已經進入A 方法說明對象鎖已經被取走,那麼試圖進入 B 方法的線程就只能在等鎖池(注意不是等待池哦)中等待對象的鎖。

60、請說出與線程同步以及線程調度相關的方法。

(1) wait():使一個線程處於等待(阻塞)狀態,並且釋放所持有的對象的鎖;

(2)sleep():使一個正在運行的線程處於睡眠狀態,是一個靜態方法,調用此方法要處理 InterruptedException 異常;

(3)notify():喚醒一個處於等待狀態的線程,當然在調用此方法的時候,並不能確切的喚醒某一個等待狀態的線程,而是由 JVM 確定喚醒哪個線程,而且與優先級無關;

(4)notityAll():喚醒所有處於等待狀態的線程,該方法並不是將對象的鎖給所有線程,而是讓它們競爭,只有獲得鎖的線程才能進入就緒狀態;

補充:Java 5 通過 Lock 接口提供了顯式的鎖機制(explicit lock),增強了靈活性以及對線程的協調。Lock 接口中定義了加鎖(lock())和解鎖(unlock())的方法,同時還提供了 newCondition()方法來產生用於線程之間通信的 Condition 對象;此外,Java 5 還提供了信號量機制(semaphore),信號量可以用來限制對某個共享資源進行訪問的線程的數量。在對資源進行訪問之前,線程必須得到信號量的許可(調用 Semaphore 對象的 acquire()方法);在完成對資源的訪問後,線程必須向信號量歸還許可(調用 Semaphore 對象的 release()方法)。

61、編寫多線程程序有幾種實現方式?

Java 5 以前實現多線程有兩種實現方法:一種是繼承 Thread 類;另一種是實現Runnable 接口。兩種方式都要通過重寫 run()方法來定義線程的行爲,推薦使用後者,因爲 Java 中的繼承是單繼承,一個類有一個父類,如果繼承了 Thread 類就無法再繼承其他類了,顯然使用 Runnable 接口更爲靈活。

補充:Java 5 以後創建線程還有第三種方式:實現 Callable 接口,該接口中的 call方法可以在線程執行結束時產生一個返回值。

62、synchronized 關鍵字的用法?

synchronized 關鍵字可以將對象或者方法標記爲同步,以實現對對象和方法的互斥訪問,可以用 synchronized(對象) { … }定義同步代碼塊,或者在聲明方法時將 synchronized 作爲方法的修飾符。在第 60 題的例子中已經展示了synchronized 關鍵字的用法。

63、舉例說明同步和異步。

如果系統中存在臨界資源(資源數量少於競爭資源的線程數量的資源),例如正在寫的數據以後可能被另一個線程讀到,或者正在讀的數據可能已經被另一個線程寫過了,那麼這些數據就必須進行同步存取(數據庫操作中的排他鎖就是最好的例子)。當應用程序在對象上調用了一個需要花費很長時間來執行的方法,並且不希望讓程序等待方法的返回時,就應該使用異步編程,在很多情況下采用異步途徑往往更有效率。事實上,所謂的同步就是指阻塞式操作,而異步就是非阻塞式操作。

64、啓動一個線程是調用 run()還是 start()方法?

啓動一個線程是調用 start()方法,使線程所代表的虛擬處理機處於可運行狀態,這意味着它可以由 JVM 調度並執行,這並不意味着線程就會立即運行。run()方法是線程啓動後要進行回調(callback)的方法。

65、什麼是線程池(thread pool)?

在面向對象編程中,創建和銷燬對象是很費時間的,因爲創建一個對象要獲取內存資源或者其它更多資源。在 Java 中更是如此,虛擬機將試圖跟蹤每一個對象,以便能夠在對象銷燬後進行垃圾回收。所以提高服務程序效率的一個手段就是儘可能減少創建和銷燬對象的次數,特別是一些很耗資源的對象創建和銷燬,這就是”池化資源”技術產生的原因。線程池顧名思義就是事先創建若干個可執行的線程放入一個池(容器)中,需要的時候從池中獲取線程不用自行創建,使用完畢不需要銷燬線程而是放回池中,從而減少創建和銷燬線程對象的開銷。Java 5+中的 Executor 接口定義一個執行線程的工具。它的子類型即線程池接口是 ExecutorService。要配置一個線程池是比較複雜的,尤其是對於線程池的原理不是很清楚的情況下,因此在工具類 Executors 面提供了一些靜態工廠方法,生成一些常用的線程池,如下所示:

(1)newSingleThreadExecutor:創建一個單線程的線程池。這個線程池只有一個線程在工作,也就是相當於單線程串行執行所有任務。如果這個唯一的線程因爲異常結束,那麼會有一個新的線程來替代它。此線程池保證所有任務的執行順序按照任務的提交順序執行。

(2)newFixedThreadPool:創建固定大小的線程池。每次提交一個任務就創建一個線程,直到線程達到線程池的最大大小。線程池的大小一旦達到最大值就會保持不變,如果某個線程因爲執行異常而結束,那麼線程池會補充一個新線程。

(3) newCachedThreadPool:創建一個可緩存的線程池。如果線程池的大小超過了處理任務所需要的線程,那麼就會回收部分空閒(60 秒不執行任務)的線程,當任務數增加時,此線程池又可以智能的添加新線程來處理任務。此線程池不會對線程池大小做限制,線程池大小完全依賴於操作系統(或者說 JVM)能夠創建的最大線程大小。

(4)newScheduledThreadPool:創建一個大小無限的線程池。此線程池支持定時以及週期性執行任務的需求。

(5)newSingleThreadExecutor:創建一個單線程的線程池。此線程池支持定時以及週期性執行任務的需求。

第 60 題的例子中演示了通過 Executors 工具類創建線程池並使用線程池執行線程的代碼。如果希望在服務器上使用線程池,強烈建議使用 newFixedThreadPool方法來創建線程池,這樣能獲得更好的性能。

66、線程的基本狀態以及狀態之間的關係?

 

說明:其中 Running 表示運行狀態,Runnable 表示就緒狀態(萬事俱備,只欠CPU),Blocked 表示阻塞狀態,阻塞狀態又有多種情況,可能是因爲調用 wait()方法進入等待池,也可能是執行同步方法或同步代碼塊進入等鎖池,或者是調用了 sleep()方法或 join()方法等待休眠或其他線程結束,或是因爲發生了 I/O 中斷。

67、簡述 synchronized 和 java.util.concurrent.locks.Lock的異同?

Lock 是 Java 5 以後引入的新的 API,和關鍵字 synchronized 相比主要相同點:Lock 能完成 synchronized 所實現的所有功能;主要不同點:Lock 有比synchronized 更精確的線程語義和更好的性能,而且不強制性的要求一定要獲得鎖。synchronized 會自動釋放鎖,而 Lock 一定要求程序員手工釋放,並且最好在 finally 塊中釋放(這是釋放外部資源的最好的地方)。

68、Java 中如何實現序列化,有什麼意義?

序列化就是一種用來處理對象流的機制,所謂對象流也就是將對象的內容進行流化。可以對流化後的對象進行讀寫操作,也可將流化後的對象傳輸於網絡之間。序列化是爲了解決對象流讀寫操作時可能引發的問題(如果不進行序列化可能會存在數據亂序的問題)。要實現序列化,需要讓一個類實現 Serializable 接口,該接口是一個標識性接口,標註該類對象是可被序列化的,然後使用一個輸出流來構造一個對象輸出流並通過 writeObject(Object)方法就可以將實現對象寫出(即保存其狀態);如果需要反序列化則可以用一個輸入流建立對象輸入流,然後通過 readObject 方法從流中讀取對象。序列化除了能夠實現對象的持久化之外,還能夠用於對象的深度克隆(可以參考第 29 題)。

69、Java 中有幾種類型的流?

字節流和字符流。字節流繼承於 InputStream、OutputStream,字符流繼承於Reader、Writer。在 java.io 包中還有許多其他的流,主要是爲了提高性能和使用方便。關於 Java 的 I/O 需要注意的有兩點:一是兩種對稱性(輸入和輸出的對稱性,字節和字符的對稱性);二是兩種設計模式(適配器模式和裝潢模式)。另外 Java 中的流不同於 C#的是它只有一個維度一個方向。

70、寫一個方法,輸入一個文件名和一個字符串,統計這個字符串在這個文件中出現的次數。

代碼如下:

import java.io.BufferedReader;
import java.io.FileReader;
public final class MyUtil {
	// 工具類中的方法都是靜態方式訪問的因此將構造器私有不允許創建對象
	(絕對好習慣)
	private MyUtil() {
		throw new AssertionError();
	}
	/**
* 統計給定文件中給定字符串的出現次數
*
* @param filename 文件名
* @param word 字符串
* @return 字符串在文件中出現的次數
*/
	public static int countWordInFile(String filename, String word) {
		int counter = 0;
		try (FileReader fr = new FileReader(filename)) {
			try (BufferedReader br = new BufferedReader(fr)) {
				String line = null;
				while ((line = br.readLine()) != null) {
					int index = -1;
					while (line.length() >= word.length() && (index =
					line.indexOf(word)) >= 0) {
						counter++;
						line = line.substring(index + word.length());
					}
				}
			}
		}
		catch (Exception ex) {
			ex.printStackTrace();
		}
		return counter;
	}
}

71、如何用 Java 代碼列出一個目錄下所有的文件?

如果只要求列出當前文件夾下的文件,代碼如下所示:

import java.io.File;
class Test12 {
	public static void main(String[] args) {
		File f = new File("/Users/Hao/Downloads");
		for (File temp : f.listFiles()) {
			if(temp.isFile()) {
				System.out.println(temp.getName());
			}
		}
	}
}

如果需要對文件夾繼續展開,代碼如下所示:

import java.io.File;
class Test12 {
	public static void main(String[] args) {
		showDirectory(new File("/Users/Hao/Downloads"));
	}
	public static void showDirectory(File f) {
		_walkDirectory(f, 0);
	}
	private static void _walkDirectory(File f, int level) {
		if(f.isDirectory()) {
			for (File temp : f.listFiles()) {
				_walkDirectory(temp, level + 1);
			}
		} else {
			for (int i = 0; i < level - 1; i++) {
				System.out.print("t");
}
System.out.println(f.getName());
}
}
}

在 Java 7 中可以使用 NIO.2 的 API 來做同樣的事情,代碼如下所示:

class ShowFileTest {
	public static void main(String[] args) throws IOException {
		Path initPath = Paths.get("/Users/Hao/Downloads");
		Files.walkFileTree(initPath, new SimpleFileVisitor<Path>() {
			@Override
			public FileVisitResult visitFile(Path file, BasicFileAttributes
			attrs)
			throws IOException {
				System.out.println(file.getFileName().toString());
				return FileVisitResult.CONTINUE;
			}
		}
		);
	}
}

72、用 Java 的套接字編程實現一個多線程的回顯(echo)服務器。

73、XML 文檔定義有幾種形式?它們之間有何本質區別?解析XML 文檔有哪幾種方式?

XML 文檔定義分爲 DTD 和 Schema 兩種形式,二者都是對 XML 語法的約束,其本質區別在於 Schema 本身也是一個 XML 文件,可以被 XML 解析器解析,而且可以爲 XML 承載的數據定義類型,約束能力較之 DTD 更強大。對 XML 的解析主要有 DOM(文檔對象模型,Document Object Model)、SAX(Simple API forXML)和 StAX(Java 6 中引入的新的解析 XML 的方式,Streaming API for XML),其中 DOM 處理大型文件時其性能下降的非常厲害,這個問題是由 DOM 樹結構佔用的內存較多造成的,而且 DOM 解析方式必須在解析文件之前把整個文檔裝入內存,適合對 XML 的隨機訪問(典型的用空間換取時間的策略);SAX 是事件驅動型的 XML 解析方式,它順序讀取 XML 文件,不需要一次全部裝載整個文件。當遇到像文件開頭,文檔結束,或者標籤開頭與標籤結束時,它會觸發一個事件,用戶通過事件回調代碼來處理 XML 文件,適合對 XML 的順序訪問;顧名思義,StAX 把重點放在流上,實際上 StAX 與其他解析方式的本質區別就在於應用程序能夠把 XML 作爲一個事件流來處理。將 XML 作爲一組事件來處理的想法並不新穎( SAX 就是這樣做的),但不同之處在於 StAX 允許應用程序代碼把這些事件逐個拉出來,而不用提供在解析器方便時從解析器中接收事件的處理程序。

74、你在項目中哪些地方用到了 XML?

XML 的主要作用有兩個方面:數據交換和信息配置。在做數據交換時,XML 將數據用標籤組裝成起來,然後壓縮打包加密後通過網絡傳送給接收者,接收解密與解壓縮後再從 XML 文件中還原相關信息進行處理,XML 曾經是異構系統間交換數據的事實標準,但此項功能幾乎已經被被JSON(JavaScript Object Notation)取而代之。當然,目前很多軟件仍然使用 XML 來存儲配置信息,我們在很多項目中通常也會將作爲配置信息的硬代碼寫在 XML 文件中,Java 的很多框架也是這麼做的,而且這些框架都選擇了 dom4j 作爲處理 XML 的工具,因爲 Sun 公司的官方API 實在不怎麼好用。

補充:現在有很多時髦的軟件(如 Sublime)已經開始將配置文件書寫成 JSON格式,我們已經強烈的感受到 XML 的另一項功能也將逐漸被業界拋棄。

75、闡述 JDBC 操作數據庫的步驟。

下面的代碼以連接本機的 Oracle 數據庫爲例,演示 JDBC 操作數據庫的步驟。

(1) 加載驅動。

Class.forName("oracle.jdbc.driver.OracleDriver");

(2) 創建連接。

Connection con =
DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:orcl",
"scott", "tiger");

(3) 創建語句。

PreparedStatement ps = con.prepareStatement("select * from emp
where sal between ? and ?");
ps.setint(1, 1000);
ps.setint(2, 3000);

(4)執行語句。

ResultSet rs = ps.executeQuery();

(5)處理結果。

while(rs.next()) {
	System.out.println(rs.getint("empno") + " - " +
	rs.getString("ename"));
}

(6) 關閉資源。

finally {
	if(con != null) {
		try {
			con.close();
		}
		catch (SQLException e) {
			e.printStackTrace();
		}
	}
}

提示:關閉外部資源的順序應該和打開的順序相反,也就是說先關閉 ResultSet、再關閉 Statement、在關閉 Connection。上面的代碼只關閉了 Connection(連接),雖然通常情況下在關閉連接時,連接上創建的語句和打開的遊標也會關閉,但不能保證總是如此,因此應該按照剛纔說的順序分別關閉。此外,第一步加載驅動在 JDBC 4.0 中是可以省略的(自動從類路徑中加載驅動),但是我們建議保留。

76、Statement 和 PreparedStatement 有什麼區別?哪個性能更好?

與 Statement 相比,①PreparedStatement 接口代表預編譯的語句,它主要的優勢在於可以減少 SQL 的編譯錯誤並增加 SQL 的安全性(減少 SQL 注射攻擊的可能性);②PreparedStatement 中的 SQL 語句是可以帶參數的,避免了用字符串連接拼接 SQL 語句的麻煩和不安全;③當批量處理 SQL 或頻繁執行相同的查詢時,PreparedStatement 有明顯的性能上的優勢,由於數據庫可以將編譯優化後的SQL 語句緩存起來,下次執行相同結構的語句時就會很快(不用再次編譯和生成執行計劃)。

補充:爲了提供對存儲過程的調用,JDBC API 中還提供了 CallableStatement 接口。存儲過程(Stored Procedure)是數據庫中一組爲了完成特定功能的 SQL 語句的集合,經編譯後存儲在數據庫中,用戶通過指定存儲過程的名字並給出參數(如果該存儲過程帶有參數)來執行它。雖然調用存儲過程會在網絡開銷、安全性、性能上獲得很多好處,但是存在如果底層數據庫發生遷移時就會有很多麻煩,因爲每種數據庫的存儲過程在書寫上存在不少的差別。

77、使用 JDBC 操作數據庫時,如何提升讀取數據的性能?如何提升更新數據的性能?

要提升讀取數據的性能,可以指定通過結果集(ResultSet)對象的 setFetchSize()方法指定每次抓取的記錄數(典型的空間換時間策略);要提升更新數據的性能可以使用 PreparedStatement 語句構建批處理,將若干 SQL 語句置於一個批處理中執行。

78、在進行數據庫編程時,連接池有什麼作用?

由於創建連接和釋放連接都有很大的開銷(尤其是數據庫服務器不在本地時,每次建立連接都需要進行 TCP 的三次握手,釋放連接需要進行 TCP 四次握手,造成的開銷是不可忽視的),爲了提升系統訪問數據庫的性能,可以事先創建若干連接置於連接池中,需要時直接從連接池獲取,使用結束時歸還連接池而不必關閉連接,從而避免頻繁創建和釋放連接所造成的開銷,這是典型的用空間換取時間的策略(浪費了空間存儲連接,但節省了創建和釋放連接的時間)。池化技術在Java 開發中是很常見的,在使用線程時創建線程池的道理與此相同。基於 Java 的開源數據庫連接池主要有:C3P0、Proxool、DBCP、BoneCP、Druid 等。

補充:在計算機系統中時間和空間是不可調和的矛盾,理解這一點對設計滿足性能要求的算法是至關重要的。大型網站性能優化的一個關鍵就是使用緩存,而緩存跟上面講的連接池道理非常類似,也是使用空間換時間的策略。可以將熱點數據置於緩存中,當用戶查詢這些數據時可以直接從緩存中得到,這無論如何也快過去數據庫中查詢。當然,緩存的置換策略等也會對系統性能產生重要影響,對於這個問題的討論已經超出了這裏要闡述的範圍。

79、什麼是 DAO 模式?

DAO(Data Access Object)顧名思義是一個爲數據庫或其他持久化機制提供了抽象接口的對象,在不暴露底層持久化方案實現細節的前提下提供了各種數據訪問操作。在實際的開發中,應該將所有對數據源的訪問操作進行抽象化後封裝在一個公共 API 中。用程序設計語言來說,就是建立一個接口,接口中定義了此應用程序中將會用到的所有事務方法。在這個應用程序中,當需要和數據源進行交互的時候則使用這個接口,並且編寫一個單獨的類來實現這個接口,在邏輯上該類對應一個特定的數據存儲。DAO 模式實際上包含了兩個模式,一是 DataAccessor(數據訪問器),二是 Data Object(數據對象),前者要解決如何訪問數據的問題,而後者要解決的是如何用對象封裝數據。

80、事務的 ACID 是指什麼?

(1)原子性(Atomic):事務中各項操作,要麼全做要麼全不做,任何一項操作的失敗都會導致整個事務的失敗;

(2)一致性(Consistent):事務結束後系統狀態是一致的;

(3)隔離性(Isolated):併發執行的事務彼此無法看到對方的中間狀態;

(4)持久性(Durable):事務完成後所做的改動都會被持久化,即使發生災難性的失敗。通過日誌和同步備份可以在故障發生後重建數據。

補充:關於事務,在面試中被問到的概率是很高的,可以問的問題也是很多的。首先需要知道的是,只有存在併發數據訪問時才需要事務。當多個事務訪問同一數據時,可能會存在 5 類問題,包括 3 類數據讀取問題(髒讀、不可重複讀和幻讀)和 2 類數據更新問題(第 1 類丟失更新和第 2 類丟失更新)。

髒讀(Dirty Read):A 事務讀取 B 事務尚未提交的數據並在此基礎上操作,而 B事務執行回滾,那麼 A 讀取到的數據就是髒數據。

不可重複讀(Unrepeatable Read):事務 A 重新讀取前面讀取過的數據,發現該數據已經被另一個已提交的事務 B 修改過了。

幻讀(Phantom Read):事務 A 重新執行一個查詢,返回一系列符合查詢條件的行,發現其中插入了被事務 B 提交的行。

第 1 類丟失更新:事務 A 撤銷時,把已經提交的事務 B 的更新數據覆蓋了。

第 2 類丟失更新:事務 A 覆蓋事務 B 已經提交的數據,造成事務 B 所做的操作丟失。

數據併發訪問所產生的問題,在有些場景下可能是允許的,但是有些場景下可能就是致命的,數據庫通常會通過鎖機制來解決數據併發訪問問題,按鎖定對象不同可以分爲表級鎖和行級鎖;按併發事務鎖定關係可以分爲共享鎖和獨佔鎖,具體的內容大家可以自行查閱資料進行了解。直接使用鎖是非常麻煩的,爲此數據庫爲用戶提供了自動鎖機制,只要用戶指定會話的事務隔離級別,數據庫就會通過分析 SQL 語句然後爲事務訪問的資源加上合適的鎖,此外,數據庫還會維護這些鎖通過各種手段提高系統的性能,這些對用戶來說都是透明的(就是說你不用理解,事實上我確實也不知道)。ANSI/ISOSQL 92 標準定義了 4 個等級的事務隔離級別,如下表所示:

需要說明的是,事務隔離級別和數據訪問的併發性是對立的,事務隔離級別越高併發性就越差。所以要根據具體的應用來確定合適的事務隔離級別,這個地方沒有萬能的原則。

81、JDBC 中如何進行事務處理?

Connection 提供了事務處理的方法,通過調用 setAutoCommit(false)可以設置手動提交事務;當事務完成後用 commit()顯式提交事務;如果在事務處理過程中發生異常則通過 rollback()進行事務回滾。除此之外,從 JDBC 3.0 中還引入了Savepoint(保存點)的概念,允許通過代碼設置保存點並讓事務回滾到指定的保存點。

82、JDBC 能否處理 Blob 和 Clob?

Blob 是指二進制大對象(Binary Large Object),而 Clob 是指大字符對象(Character Large Objec),因此其中 Blob 是爲存儲大的二進制數據而設計的,而 Clob 是爲存儲大的文本數據而設計的。JDBC 的 PreparedStatement 和ResultSet 都提供了相應的方法來支持 Blob 和 Clob 操作。

83、簡述正則表達式及其用途。

在編寫處理字符串的程序時,經常會有查找符合某些複雜規則的字符串的需要。正則表達式就是用於描述這些規則的工具。換句話說,正則表達式就是記錄文本規則的代碼。

說明:計算機誕生初期處理的信息幾乎都是數值,但是時過境遷,今天我們使用計算機處理的信息更多的時候不是數值而是字符串,正則表達式就是在進行字符串匹配和處理的時候最爲強大的工具,絕大多數語言都提供了對正則表達式的支持。

84、Java 中是如何支持正則表達式操作的?

Java 中的 String 類提供了支持正則表達式操作的方法,包括:matches()、replaceAll()、replaceFirst()、split()。此外,Java 中可以用 Pattern 類表示正則表達式對象,它提供了豐富的 API 進行各種正則表達式操作。

面試題: - 如果要從字符串中截取第一個英文左括號之前的字符串,例如:北京市(朝陽區)(西城區)(海淀區),截取結果爲:北京市,那麼正則表達式怎麼寫?

import java.util.regex.Matcher;
import java.util.regex.Pattern;
class RegExpTest {
	public static void main(String[] args) {
		String str = "北京市(朝陽區)(西城區)(海淀區)";
		Pattern p = Pattern.compile(".*?(?=\()");
		Matcher m = p.matcher(str);
		if(m.find()) {
			System.out.println(m.group());
		}
	}
}

85、獲得一個類的類對象有哪些方式?

(1)方法 1:類型.class,例如:String.class

(2)方法 2:對象.getClass(),例如:”hello”.getClass()

(3)方法 3:Class.forName(),例如:Class.forName(“java.lang.String”)

86、如何通過反射創建對象?

方法 1:通過類對象調用 newInstance()方法,例如:String.class.newInstance()

方法 2:通過類對象的 getConstructor()或 getDeclaredConstructor()方法獲得構造器(Constructor)對象並調用其 newInstance()方法創建對象,例如:String.class.getConstructor(String.class).newInstance(“Hello”);

87、如何通過反射獲取和設置對象私有字段的值?

可以通過類對象的 getDeclaredField()方法字段(Field)對象,然後再通過字段對象的 setAccessible(true)將其設置爲可以訪問,接下來就可以通過 get/set 方法來獲取/設置字段的值了。下面的代碼實現了一個反射的工具類,其中的兩個靜態方法分別用於獲取和設置私有字段的值,字段可以是基本類型也可以是對象類型且支持多級對象操作。

88、如何通過反射調用對象的方法?

請看下面的代碼:

import java.lang.reflect.Method;
class MethodInvokeTest {
	public static void main(String[] args) throws Exception {
		String str = "hello";
		Method m = str.getClass().getMethod("toUpperCase");
		System.out.println(m.invoke(str));
		// HELLO
	}
}

89、簡述一下面向對象的”六原則一法則”。

(1)單一職責原則:一個類只做它該做的事情。(單一職責原則想表達的就是”高內聚”,寫代碼最終極的原則只有六個字”高內聚、低耦合”,就如同葵花寶典或辟邪劍譜的中心思想就八個字”欲練此功必先自宮”,所謂的高內聚就是一個代碼模塊只完成一項功能,在面向對象中,如果只讓一個類完成它該做的事,而不涉及與它無關的領域就是踐行了高內聚的原則,這個類就只有單一職責。我們都知道一句話叫”因爲專注,所以專業”,一個對象如果承擔太多的職責,那麼註定它什麼都做不好。這個世界上任何好的東西都有兩個特徵,一個是功能單一,好的相機絕對不是電視購物裏面賣的那種一個機器有一百多種功能的,它基本上只能照相;另一個是模塊化,好的自行車是組裝車,從減震叉、剎車到變速器,所有的部件都是可以拆卸和重新組裝的,好的乒乓球拍也不是成品拍,一定是底板和膠皮可以拆分和自行組裝的,一個好的軟件系統,它裏面的每個功能模塊也應該是可以輕易的拿到其他系統中使用的,這樣才能實現軟件複用的目標。)

(2)開閉原則:軟件實體應當對擴展開放,對修改關閉。(在理想的狀態下,當我們需要爲一個軟件系統增加新功能時,只需要從原來的系統派生出一些新類就可以,不需要修改原來的任何一行代碼。要做到開閉有兩個要點:①抽象是關鍵,一個系統中如果沒有抽象類或接口系統就沒有擴展點;②封裝可變性,將系統中的各種可變因素封裝到一個繼承結構中,如果多個可變因素混雜在一起,系統將變得複雜而換亂,如果不清楚如何封裝可變性,可以參考《設計模式精解》一書中對橋樑模式的講解的章節。)

(3)依賴倒轉原則:面向接口編程。(該原則說得直白和具體一些就是聲明方法的參數類型、方法的返回類型、變量的引用類型時,儘可能使用抽象類型而不用具體類型,因爲抽象類型可以被它的任何一個子類型所替代,請參考下面的里氏替換原則。)

(4)里氏替換原則:任何時候都可以用子類型替換掉父類型。(關於里氏替換原則的描述,Barbara Liskov 女士的描述比這個要複雜得多,但簡單的說就是能用父類型的地方就一定能使用子類型。里氏替換原則可以檢查繼承關係是否合理,如果一個繼承關係違背了里氏替換原則,那麼這個繼承關係一定是錯誤的,需要對代碼進行重構。例如讓貓繼承狗,或者狗繼承貓,又或者讓正方形繼承長方形都是錯誤的繼承關係,因爲你很容易找到違反里氏替換原則的場景。需要注意的是:子類一定是增加父類的能力而不是減少父類的能力,因爲子類比父類的能力更多,把能力多的對象當成能力少的對象來用當然沒有任何問題。)

(5)接口隔離原則:接口要小而專,絕不能大而全。(臃腫的接口是對接口的污染,既然接口表示能力,那麼一個接口只應該描述一種能力,接口也應該是高度內聚的。例如,琴棋書畫就應該分別設計爲四個接口,而不應設計成一個接口中的四個方法,因爲如果設計成一個接口中的四個方法,那麼這個接口很難用,畢竟琴棋書畫四樣都精通的人還是少數,而如果設計成四個接口,會幾項就實現幾個接口,這樣的話每個接口被複用的可能性是很高的。Java 中的接口代表能力、代表約定、代表角色,能否正確的使用接口一定是編程水平高低的重要標識。)

(6)合成聚合複用原則:優先使用聚合或合成關係複用代碼。(通過繼承來複用代碼是面向對象程序設計中被濫用得最多的東西,因爲所有的教科書都無一例外的對繼承進行了鼓吹從而誤導了初學者,類與類之間簡單的說有三種關係,Is-A 關係、Has-A 關係、Use-A 關係,分別代表繼承、關聯和依賴。其中,關聯關係根據其關聯的強度又可以進一步劃分爲關聯、聚合和合成,但說白了都是Has-A 關係,合成聚合複用原則想表達的是優先考慮 Has-A 關係而不是 Is-A 關係複用代碼,原因嘛可以自己從百度上找到一萬個理由,需要說明的是,即使在Java 的 API 中也有不少濫用繼承的例子,例如 Properties 類繼承了 Hashtable類,Stack 類繼承了 Vector 類,這些繼承明顯就是錯誤的,更好的做法是在Properties 類中放置一個 Hashtable 類型的成員並且將其鍵和值都設置爲字符串來存儲數據,而 Stack 類的設計也應該是在 Stack 類中放一個 Vector 對象來存儲數據。記住:任何時候都不要繼承工具類,工具是可以擁有並可以使用的,而不是拿來繼承的。)

(7)迪米特法則:迪米特法則又叫最少知識原則,一個對象應當對其他對象有儘可能少的瞭解。(迪米特法則簡單的說就是如何做到”低耦合”,門面模式和調停者模式就是對迪米特法則的踐行。對於門面模式可以舉一個簡單的例子,你去一家公司洽談業務,你不需要了解這個公司內部是如何運作的,你甚至可以對這個公司一無所知,去的時候只需要找到公司入口處的前臺美女,告訴她們你要做什麼,她們會找到合適的人跟你接洽,前臺的美女就是公司這個系統的門面。再複雜的系統都可以爲用戶提供一個簡單的門面,Java Web 開發中作爲前端控制器的 Servlet 或 Filter 不就是一個門面嗎,瀏覽器對服務器的運作方式一無所知,但是通過前端控制器就能夠根據你的請求得到相應的服務。調停者模式也可以舉一個簡單的例子來說明,例如一臺計算機,CPU、內存、硬盤、顯卡、聲卡各種設備需要相互配合才能很好的工作,但是如果這些東西都直接連接到一起,計算機的佈線將異常複雜,在這種情況下,主板作爲一個調停者的身份出現,它將各個設備連接在一起而不需要每個設備之間直接交換數據,這樣就減小了系統的耦合度和複雜度,如下圖所示。迪米特法則用通俗的話來將就是不要和陌生人打交道,如果真的需要,找一個自己的朋友,讓他替你和陌生人打交道。)

 

90、簡述一下你瞭解的設計模式。

所謂設計模式,就是一套被反覆使用的代碼設計經驗的總結(情境中一個問題經過證實的一個解決方案)。使用設計模式是爲了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。設計模式使人們可以更加簡單方便的複用成功的設計和體系結構。將已證實的技術表述成設計模式也會使新系統開發者更加容易理解其設計思路。

在 GoF 的《Design Patterns: Elements of Reusable Object-OrientedSoftware》中給出了三類(創建型[對類的實例化過程的抽象化]、結構型[描述如何將類或對象結合在一起形成更大的結構]、行爲型[對在不同的對象之間劃分責任和算法的抽象化])共 23 種設計模式,包括:Abstract Factory(抽象工廠模式),Builder(建造者模式),Factory Method(工廠方法模式),Prototype(原始模型模式),Singleton(單例模式);Facade(門面模式),Adapter(適配器模式),Bridge(橋樑模式),Composite(合成模式),Decorator(裝飾模式),Flyweight(享元模式),Proxy(代理模式);Command(命令模式),Interpreter(解釋器模式),Visitor(訪問者模式),Iterator(迭代子模式),Mediator(調停者模式),Memento(備忘錄模式),Observer(觀察者模式),State(狀態 模式 ),Strategy(策略 模式 ),Template Method(模板方法模式),Chain Of Responsibility(責任鏈模式)。

面試被問到關於設計模式的知識時,可以揀最常用的作答,例如:

(1)工廠模式:工廠類可以根據條件生成不同的子類實例,這些子類有一個公共的抽象父類並且實現了相同的方法,但是這些方法針對不同的數據進行了不同的操作(多態方法)。當得到子類的實例後,開發人員可以調用基類中的方法而不必考慮到底返回的是哪一個子類的實例。

(2)代理模式:給一個對象提供一個代理對象,並由代理對象控制原對象的引用。實際開發中,按照使用目的的不同,代理可以分爲:遠程代理、虛擬代理、保護代理、Cache 代理、防火牆代理、同步化代理、智能引用代理。

(3)適配器模式:把一個類的接口變換成客戶端所期待的另一種接口,從而使原本因接口不匹配而無法在一起使用的類能夠一起工作。

(4)模板方法模式:提供一個抽象類,將部分邏輯以具體方法或構造器的形式實現,然後聲明一些抽象方法來迫使子類實現剩餘的邏輯。不同的子類可以以不同的方式實現這些抽象方法(多態實現),從而實現不同的業務邏輯。除此之外,還可以講講上面提到的門面模式、橋樑模式、單例模式、裝潢模式(Collections 工具類和 I/O 系統中都使用裝潢模式)等,反正基本原則就是揀自己最熟悉的、用得最多的作答,以免言多必失。

91、用 Java 寫一個單例類。

(1)餓漢式單例

public class Singleton {
	private Singleton(){
	}
	private static Singleton instance = new Singleton();
	public static Singleton getInstance(){
		return instance;
	}
}

(2)懶漢式單例

public class Singleton {
	private static Singleton instance = null;
	private Singleton() {
	}
	public static synchronized Singleton getInstance(){
		if (instance == null) instance = new Singleton();
		return instance;
	}
}

注意:實現一個單例有兩點注意事項,①將構造器私有,不允許外界通過構造器創建對象;②通過公開的靜態方法向外界返回類的唯一實例。這裏有一個問題可以思考:Spring 的 IoC 容器可以爲普通的類創建單例,它是怎麼做到的呢?

92、什麼是 UML?

UML 是統一建模語言(Unified Modeling Language)的縮寫,它發表於 1997年,綜合了當時已經存在的面向對象的建模語言、方法和過程,是一個支持模型化和軟件系統開發的圖形化語言,爲軟件開發的所有階段提供模型化和可視化支持。使用 UML 可以幫助溝通與交流,輔助應用設計和文檔的生成,還能夠闡釋系統的結構和行爲。

93、UML 中有哪些常用的圖?

UML 定義了多種圖形化的符號來描述軟件系統部分或全部的靜態結構和動態結構,包括:用例圖(use case diagram)、類圖(class diagram)、時序圖(sequencediagram)、協作圖(collaboration diagram)、狀態圖(statechart diagram)、活動圖(activity diagram)、構件圖(component diagram)、部署圖(deploymentdiagram)等。在這些圖形化符號中,有三種圖最爲重要,分別是:用例圖(用來捕獲需求,描述系統的功能,通過該圖可以迅速的瞭解系統的功能模塊及其關係)、類圖(描述類以及類與類之間的關係,通過該圖可以快速瞭解系統)、時序圖(描述執行特定任務時對象之間的交互關係以及執行順序,通過該圖可以瞭解對象能接收的消息也就是說對象能夠向外界提供的服務)。用例圖:

類圖:

時序圖:

 

94、用 Java 寫一個冒泡排序。

冒泡排序幾乎是個程序員都寫得出來,但是面試的時候如何寫一個逼格高的冒泡排序卻不是每個人都能做到,下面提供一個參考代碼:

import java.util.Comparator;
/**
* 排序器接口(策略模式: 將算法封裝到具有共同接口的獨立的類中使得它們可
以相互替換)
* @author 駱昊
*
*/
public interface Sorter {
	/**
* 排序
* @param list 待排序的數組
*/
	public <T extends Comparable<T>> void sort(T[] list);
	/**
* 排序
* @param list 待排序的數組
* @param comp 比較兩個對象的比較器
*/
	public <T> void sort(T[] list, Comparator<T> comp);
}
import java.util.Comparator;
/**
* 冒泡排序
*
* @author 駱昊
*
*/
public class BubbleSorter implements Sorter {
	@Override
	public <T extends Comparable<T>> void sort(T[] list) {
		Boolean swapped = true;
		for (int i = 1, len = list.length; i < len && swapped; ++i) {
			swapped = false;
			for (int j = 0; j < len - i; ++j) {
				if (list[j].compareTo(list[j + 1]) > 0) {
					T temp = list[j];
					list[j] = list[j + 1];
					list[j + 1] = temp;
					swapped = true;
				}
			}
		}
	}
	@Override
	public <T> void sort(T[] list, Comparator<T> comp) {
		Boolean swapped = true;
		for (int i = 1, len = list.length; i < len && swapped; ++i) {
			swapped = false;
			for (int j = 0; j < len - i; ++j) {
				if (comp.compare(list[j], list[j + 1]) > 0) {
					T temp = list[j];
					list[j] = list[j + 1];
					list[j + 1] = temp;
					swapped = true;
				}
			}
		}
	}
}

95、用 Java 寫一個折半查找。

折半查找,也稱二分查找、二分搜索,是一種在有序數組中查找某一特定元素的搜索算法。搜素過程從數組的中間元素開始,如果中間元素正好是要查找的元素,則搜素過程結束;如果某一特定元素大於或者小於中間元素,則在數組大於或小於中間元素的那一半中查找,而且跟開始一樣從中間元素開始比較。如果在某一步驟數組已經爲空,則表示找不到指定的元素。這種搜索算法每一次比較都使搜索範圍縮小一半,其時間複雜度是 O(logN)。

import java.util.Comparator;
public class MyUtil {
	public static <T extends Comparable<T>> int binarySearch(T[] x, T
	key) {
		return binarySearch(x, 0, x.length- 1, key);
	}
	// 使用循環實現的二分查找
	public static <T> int binarySearch(T[] x, T key, Comparator<T> comp)
	{
		int low = 0;
		int high = x.length - 1;
		while (low <= high) {
			int mid = (low + high) >>> 1;
			int cmp = comp.compare(x[mid], key);
			if (cmp < 0) {
				low= mid + 1;
			} else if (cmp > 0) {
				high= mid - 1;
			} else {
				return mid;
			}
		}
		return -1;
	}
	// 使用遞歸實現的二分查找
	private static<T extends Comparable<T>> int binarySearch(T[] x, int
	low, int high, T key) {
		if(low <= high) {
			int mid = low + ((high -low) >> 1);
			if(key.compareTo(x[mid])== 0) {
				return mid;
			} else if(key.compareTo(x[mid])< 0) {
				return binarySearch(x,low, mid - 1, key);
			} else {
				return binarySearch(x,mid + 1, high, key);
			}
		}
		return -1;
	}
}

說明:上面的代碼中給出了折半查找的兩個版本,一個用遞歸實現,一個用循環實現。需要注意的是計算中間位置時不應該使用(high+ low) / 2 的方式,因爲加法運算可能導致整數越界,這裏應該使用以下三種方式之一:low + (high - low)/ 2 或 low + (high – low) >> 1 或(low + high) >>> 1(>>>是邏輯右移,是不帶符號位的右移)

 

最後

歡迎大家關注我的公種浩【程序員追風】,整理了1000道2019年多家公司java面試題400多頁pdf文檔,文章都會在裏面更新,整理的資料也會放在裏面。喜歡文章記得關注我點個贊喲,感謝支持!

發佈了174 篇原創文章 · 獲贊 2290 · 訪問量 24萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章