1. 面向過程和麪向對象
面向過程:
分析出解決問題所需要的步驟,然後用函數把這些步驟一步一步實現,使用的時候一個一個依次調用。
優點:性能
比面向對象高,因爲類調用時需要實例化,開銷比較大,比較消耗資源;比如嵌入式開發、 Linux/Unix 等一般採用面向過程開發,性能是最重要的因素。
缺點:沒有面向對象易維護、易複用、易擴展。
面向對象:
構成問題事務分解成各個對象,建立對象的目的不是爲了完成一個步驟,而是爲了描敘某個事物在整個解決問題的步驟中的行爲。
優點:易維護、易複用、易擴展
,由於面向對象有封裝、繼承、多態性的特性,可以設計出低耦合的系統。
缺點:性能比面向過程低。
2. 繼承和多態
繼承:
繼承是指:保持已有類的特性而構造新類的過程。繼承後,子類能夠利用父類中定義的變量和方法,就像它們屬於子類本身一樣。
- 單繼承:java
類
是單繼承的,一個類只允許有一個父類。
public class A extends B{ } //繼承單個父類
- 多繼承:java
接口
多繼承的,一個類允許繼承多個接口。
public class A extends B implements C{ } //同時繼承父類和接口
public class A implements B,C{ } //繼承多個接口
多態:
多態是指:在父類中定義的屬性和方法被子類繼承之後,可以具有不同的數據類型或表現出不同的行爲,這使得同一個屬性或方法在父類及其各個子類中具有不同的含義。
子類對象的多態性使用前提:
- 有類的
繼承
; - 由子類對父類方法的
重寫
封裝、繼承和多態三者的主要功能:
封裝 | 繼承 | 多態 |
---|---|---|
隱藏內部代碼 | 複用現有代碼 | 改寫對象行爲 |
3. 抽象類和接口
抽象類:
Java語言中,用
abstract
關鍵字來修飾一個類時,這個類叫作抽象類。抽象類是它的所有子類的公共屬性的集合,是包含一個或多個抽象方法的類。抽象類可以看作是對類的進一步抽象。在面向對象領域,抽象類主要用來進行類型隱藏。
比如創建一個Animal抽象類:
public abstract class Animal {
public abstract void eat();
public abstract void sleep();
}
注意: 抽象方法不能有方法體,在方法後面加一個大括號而裏面什麼都不寫也是不行的,編譯器會報 abstract methods do not specify a body
這樣一個錯誤。
public class Cat extends Animal{
@Override //重寫抽象類中的eat()方法
public void eat() {
System.out.println("我是貓,我喫的是貓糧呀");
}
@Override //重寫抽象類中的sleep()方法
public void sleep() {
System.out.println("我是貓,我比你們人類睡的時間短!");
}
}
在這裏需要注意的是:當一個類繼承抽象類的時候,這個類必須去重寫所繼承的抽象類的抽象方法,否則編譯器會報 The type Cat must implement the inherited abstract method Animal.eat()
的錯誤。
兩個注意點:
- 如果一個類中有一個抽象方法,那麼當前類一定是抽象類;但抽象類中不一定有抽象方法。
- 抽象類中的抽象方法,需要有子類實現,如果子類不實現,則子類也需要定義爲抽象的。
接口:
Java接口是一系列方法的聲明,是一些方法特徵的集合,一個接口只有方法的特徵沒有方法的實現,因此這些方法可以在不同的地方被不同的類實現,而這些實現可以具有不同的行爲(功能)。
public interface Eat {
public abstract void ioEat();
}
public interface Study {
public void ioStudy();
}
public class Cat implements Sleep,Eat{
@Override
public void ioSleep(int i) {
System.out.println("我是貓,我每天都不用睡覺!!!");
}
@Override
public void ioEat() {
System.out.println("我是貓,我喫貓糧!!!");
}
}
接口中的所有屬性
默認爲:public static final xxx
;
接口中的所有方法
默認爲:public abstract xxx
;
抽象類和接口的區別:
- 抽象類可以有
構造方法
,接口沒有構造方法 - 抽象類可以有
普通成員變量
,接口沒有普通成員變量 - 抽象類可以有非抽象的
普通方法
,接口中的方法必須是抽象的 - 抽象類中的抽象方法訪問類型可以是public,protected,接口中抽象方法必須是
public
類型的 - 抽象類可以包含
靜態方法
,接口中不能包含靜態方法 - 一個類可以實現
多個接口
,但是隻能繼承一個抽象類
- 接口中基本數據類型的數據成員,都默認爲
static final
,抽象類則不是
3. JRE、JDK、JVM
一句話:JDK包含JRE,JRE包含JVM。有JRE即可運行程序了。
JRE(JavaRuntimeEnvironment,Java運行環境):
JRE也就是Java平臺
。所有的Java程序都要在JRE下才能運行。普通用戶只需要運行已開發好的java程序,安裝JRE即可。
JDK(Java Development Kit,Java開發工具包):
是程序開發者用來編譯、調試Java程序
用的開發工具包。JDK的工具也是Java程序,也需要JRE才能運行。爲了保持JDK的獨立性和完整性,在JDK的安裝過程中,JRE也是安裝的一部分。所以,在JDK的安裝目錄下有一個名爲jre的目錄
,用於存放JRE文件。
JVM(JavaVirtualMachine,Java虛擬機):
JVM是JRE的一部分
。它是一個虛構出來的計算機,是通過在實際的計算機上仿真模擬各種計算機功能
來實現的。JVM有自己完善的硬件架構,如處理器、堆棧、寄存器等,還具有相應的指令系統。Java語言最重要的特點就是跨平臺運行。使用JVM就是爲了支持與操作系統無關,實現跨平臺
。
4. 堆和棧
大多數JVM將內存區域劃分爲:方法區、堆、程序計數器、虛擬機棧、本地方法棧。其中方法區和堆是線程共享的,而另外三個是非線程共享的。
JVM初始運行時都會分配好方法區和堆,而JVM每遇到一個線程,就會爲其分配一個程序計數器、虛擬機棧、本地方法棧。當線程終止時,三者所佔的內存空間就會被釋放掉。也就是說,非線程共享的那三個區域的生命週期與所屬線程相同,而線程共享的區域與JAVA程序運行的生命週期相同,所以這也是系統垃圾回收的場所只發生在線程共享的區域(實際上對大部分虛擬機來說只發生在堆上)的原因。
方法區:
方法區是各個線程共享的內存區域,它用於存儲已經被虛擬機加載的類信息
、常量
、靜態變量
、即編譯器編譯後的代碼等數據
。方法區域又被稱爲“永久代”。Java堆中還必須包含能查找到此對象類型數據的地址信息
(如對象類型、父類、實現的接口、方法等),這些類型數據則保存在方法區中。
運行時常量池
是方法區的一部分,class文件除了有類的字段、接口、方法等描述信息之外,還有常量池用於存放編譯期間生成的各種字面量和符號引用。
堆:
堆是Java虛擬機所管理的內存中最大的一塊,它是所有線程共享的一塊內存區域。幾乎所有的對象實例和數組
都在這裏分配內存。堆是垃圾收集器管理的主要區域
,因此很多時候也被稱爲“GC堆
”。根據Java虛擬機規範的規定,Java堆可以處在物理上不連續的內存空間中,只要邏輯上是連續的即可。如果在堆中沒有內存可分配時,並且堆也無法擴展時,將會拋出OutOfMemoryError
異常。
程序計數器:
程序計數器是一塊較小的內存空間,它的作用可以看做是當前線程所執行的字節碼的行號指示器。字節碼解釋器工作時就是通過改變這個計數器的值來選取下一條需要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能都需要依賴這個計數器來完成。爲了線程切換後能恢復到正確的執行位置,每條線程都需要有一個獨立的程序計數器
,各條線程之間的計數器互不影響,獨立存儲,我們稱這類內存區域爲“線程私有”的內存。當線程在執行的是Native方法(調用本地操作系統方法)時,該計數器的值爲空。另外,該內存區域是唯一一個在Java虛擬機規範中沒有規定任何OOM(內存溢出:OutOfMemoryError)
情況的區域。
虛擬機棧:
系統自動分配與回收內存,效率較高,快速,存取速度比堆要快;是一塊連續的內存的區域,有大小限制,如果超過了就會棧溢出
,並拋出棧溢出的異常StackOverflowError
;Java會自動釋放
掉爲該變量所分配的內存空間。
注意,JVM棧是每個線程私有
的!每個線程創建的同時都會創建JVM棧,JVM棧中存放的爲當前線程中局部基本類型的變量(java中定義的八種基本類型:boolean、char、byte、short、int、long、float、double)、部分的返回結果以及Stack Frame,非基本類型的對象在JVM棧上僅存放一個指向堆上的地址。
每個方法被執行的時候都會同時創建一個棧幀,對於執行引擎來講,活動線程中,只有棧頂的棧幀是有效的
,稱爲當前棧幀,這個棧幀所關聯的方法稱爲當前方法,執行引擎所運行的所有字節碼指令都只針對當前棧幀進行操作。
棧幀用於存儲局部變量表、操作數棧、動態鏈接、方法返回地址和一些額外的附加信息。在編譯程序代碼時,棧幀中需要多大的局部變量表、多深的操作數棧都已經完全確定了,並且寫入了方法表的Code屬性之中(class文件中是屬性表裏,加載後是方法區裏)。
本地方法棧:
本地方法棧與虛擬機棧所發揮的作用是非常相似的,其區別不過是虛擬機棧爲虛擬機執行Java方法(也就是字節碼)服務,而本地方法棧則是爲虛擬機使用到的Native方法服務。
Q: 那麼,到底堆和棧的區別是什麼呢?
棧內存
:棧內存首先是一片內存區域,存儲的都是局部變量
,凡是定義在方法中的
都是局部變量(方法外的是全局變量),for循環內部定義的
也是局部變量,是先加載方法才能進行局部變量的定義,所以方法先進棧,然後再定義變量,變量有自己的作用域,一旦離開作用域,變量就會被釋放。棧內存的更新速度很快
,因爲局部變量的生命週期都很短。
堆內存
:存儲的是數組和對象
(其實數組就是對象),凡是new
建立的都是在堆中,堆中存放的都是實體(對象),實體用於封裝數據,而且是封裝多個(實體的多個屬性),如果一個數據消失,這個實體也沒有消失,還可以用,所以堆是不會隨時釋放的,但是棧不一樣,棧裏存放的都是單個變量,變量被釋放了,那就沒有了。堆裏的實體雖然不會被釋放,但是會被當成垃圾,Java有垃圾回收機制不定時的收取。
- 主函數裏的語句
int [] arr=new int [3];
在內存中是怎麼被定義的?
首先,主函數先進棧,在棧中定義一個變量arr,接下來爲arr賦值,但是右邊的堆中並不是一個具體值,而是一個實體
。實體創建在堆裏,在堆裏首先通過new關鍵字開闢一個空間,內存在存儲數據的時候都是通過地址來體現的,地址是一塊連續的二進制,然後給這個實體分配一個內存地址。數組都是有一個索引,數組這個實體在堆內存中產生之後每一個空間都會進行默認的初始化
(這是堆內存的特點,未初始化的數據是不能用的,但在堆裏是可以用的,因爲默認初始化過了,但是在棧裏沒有),不同的類型初始化的值不一樣。所以堆和棧裏就創建了變量和實體:
- 那麼堆和棧是怎麼聯繫起來的呢?
由於已經給堆分配了一個地址,那麼把堆的地址賦給arr,arr就通過地址指向了數組。所以arr想操縱數組時,就通過地址
,而不是直接把實體都賦給它。這種我們不再叫它基本數據類型,而叫引用數據類型
。稱爲:arr引用了堆內存當中的實體。
- 如果當
int [] arr=null;
又是如何的呢?
則arr不做任何指向,null
的作用就是取消引用數據類型的指向
。
注意: 當一個實體,沒有引用數據類型指向的時候,它在堆內存中不會被釋放,而被當做一個垃圾
,在不定時
的時間內自動回收
,因爲Java有一個自動回收機制,(而c++沒有,需要程序員手動回收,如果不回收就越堆越多,直到撐滿內存溢出,所以Java在內存管理上優於c++)。自動回收機制自動監測堆裏是否有垃圾,如果有,就會自動的做垃圾回收的動作,但是什麼時候收不一定。
如此一來,堆與棧的區別就很明顯:
棧
內存存儲的是局部變量
;而堆
內存存儲的是實體
;棧
內存的更新速度要快於堆內存
,因爲局部變量的生命週期很短;棧
內存存放的變量生命週期一旦結束就會被釋放
,而堆
內存存放的實體會被垃圾回收機制不定時的回收
。
5. 垃圾回收機制
垃圾回收機制(GC)是用來釋放內存中的資源的,可以有效地防止內存泄露,有效地使用空閒的內存。
6. 多線程的實現方式
- 繼承Thread類。
Thread
類本質上是實現了Runnable
接口的一個實例
,代表一個線程的實例。讓自己的類直接extends Thread
,並在此類中複寫run()
方法。啓動線程的方法就是通過Thread類的start()
實例方法,start()
方法將啓動一個新線程,並執行其中的run()
方法。
public class MyThread extends Thread { //繼承Thread類
public void run() { //複寫run()方法
System.out.println("MyThread.run()");
}
}
MyThread myThread1 = new MyThread(); //創建一個myThread實例
MyThread myThread2 = new MyThread();
myThread1.start(); //啓動線程
myThread2.start();
- 實現Runnable接口。
如果自己的類已經extends
另一個類了,就無法再直接extends Thread
,此時,可以通過讓它來實現Runnable
接口來創建多線程。
public class MyThread extends OtherClass implements Runnable { //實現Runnable接口
public void run() { //複寫run()方法
System.out.println("MyThread.run()");
}
}
MyThread myThread = new MyThread(); //創建一個myThread實例
Thread thread = new Thread(myThread); //將自己的myThread傳入Thread實例中
thread.start(); //啓動線程
- 實現Callable接口,重寫call函數。
繼承Thread
類實現多線程,但重寫run()
方法時沒有返回值也不能拋出異常,使用Callable
接口就可以解決這個問題。Callable接口和Runnable接口的不同之處:
Callable
規定的方法是call()
,而Runnable
是run()
;call()
方法可以拋出異常
,但是run()
方法不行;Callable
對象執行後可以有返回值,運行Callable任務可以得到一個Future
對象,通過Future對象可以瞭解任務執行情況,可以取消任務的執行,而Runnable
不可有返回值。
public interface Callable<V> { //Callable接口
V call() throws Exception;
}
public class SomeCallable<V> extends OtherClass implements Callable<V> {
@Override //@Override註解表明重寫call()方法
public V call() throws Exception {
// TODO Auto-generated method stub
return null;
}
}
Callable<V> oneCallable = new SomeCallable<V>(); //由Callable<Integer>創建一個FutureTask<Integer>對象:
FutureTask<V> oneTask = new FutureTask<V>(oneCallable); //FutureTask<Integer>是一個包裝器,它通過接受Callable<Integer>來創建,它同時實現了Future和Runnable接口。
Thread oneThread = new Thread(oneTask); //由FutureTask<Integer>創建一個Thread對象
oneThread.start(); //至此,一個線程就創建完成了。
- 基於線程池的方式。
- Spring的 @Async 註解。
使用Spring比使用JDK原生的併發API更簡單。而且我們的應用環境一般都會集成Spring,我們的Bean也都交給Spring來進行管理,那麼使用Spring來實現多線程更加簡單,更加優雅。只需要在配置類中添加@EnableAsync
就可以使用多線程。在希望執行的併發方法中使用@Async
就可以定義一個線程任務。
7. 多線程中run方法和start方法的區別
run()方法:
是在主線程
中執行方法,和調用普通方法一樣(按順序執行,同步
執行)。
start()方法:
是創建了新的線程
,在新的線程中執行(異步
執行),只有通過調用線程類的start()
方法可能真正達到多線程的目的。單獨調用run()
方法,是同步執行;通過start()
調用run()
,是異步執行。
8. 同步和異步
同步:
發送一個請求,等待返回,然後再發送下一個請求。實現:1. synchronized
修飾;2. wait()
和notify()
。同步可以避免出現死鎖,讀髒數據
的發生,一般共享某一資源的時候用,如果每個人都有修改權限,同時修改一個文件,有可能使一個人讀取另一個人已經刪除的內容,就會出錯,同步就會按順序來修改。
public void countAdd() { //比如一個計算數字和的方法,可能就需要是同步的,否則會讀到髒數據或者死鎖等問題。
synchronized(this) { //使用synchronized修飾,表明它是一個同步的方法。
... //方法體
}
}
或者寫成:
public synchronized void countAdd() {
... //方法體
}
異步:
發送一個請求,不等待返回,隨時可以再發送下一個請求。
同步和異步最大的區別就在於:一個需要等待,一個不需要等待。比如廣播,就是一個異步例子。發起者不關心接收者的狀態,不需要等待接收者的返回信息。電話,就是一個同步例子。發起者需要等待接收者,接通電話後,通信纔開始,需要等待接收者的返回信息。
8. 內存泄漏和內存溢出
內存泄露:
是指分配出去的內存沒有被回收回來,由於失去了對該內存區域的控制,因而造成了資源的浪費。
Java中一般不會產生內存泄露,因爲有垃圾回收器自動回收垃圾,但這也不絕對,當我們new
了一個對象,並保存了其引用,但是後面一直沒用它,而垃圾回收器又不會去回收它,這便會造成內存泄露。
內存溢出:
是指程序所需要的內存超出了系統所能分配的內存(包括動態擴展)的上限。
9. 重寫和重載
重寫:
在方法前加上@Override
註解。其實就是在子類中把父類本身有的方法重新寫一遍。子類繼承了父類原有的方法,但有時子類並不想原封不動的繼承父類中的某個方法,所以在方法名
,參數列表
,返回類型
(除過子類中方法的返回值是父類中方法返回值的子類時)都相同的情況下, 對方法體進行修改或重寫,這就是重寫。但要注意子類函數的訪問修飾權限不能少於父類的。
重載:
在一個類中,同名
的方法如果有不同的參數列表
(參數類型
不同、參數個數
不同甚至是參數順序
不同)則視爲重載。同時,重載對返回類型沒有要求,可以相同也可以不同,但不能通過返回類型是否相同來判斷重載。
方法的重載和重寫都是實現多態
的方式,但區別在於:
- 重載實現的是編譯時的多態性;而重寫實現的是運行時的多態性。
- 重載發生在一個類中;重寫發生在子類與父類之間。
10. static
static
:靜態。是一個修飾符,用於修飾成員(成員變量和成員函數)
當成員被靜態修飾後,就多了一種調用方式,除了可以被對象調用外,還可以直接被類名調用格式:類名.靜態成員
I. 靜態的特點:
- 隨着類的加載而加載。
也就是說,靜態會隨着類的消失而消失,說明靜態的生命週期最長 - 優先於對象的存在。
明確一點:靜態是先存在的,對象是後存在的 - 被所有對象共享。
- 可以直接被類名多調用。
II. 類變量和實例變量的區別:
- 存放位置
類變量
隨着類的加載存在於方法區
中;實例變量
隨着對象的對象的建立存在於堆內存
裏 - 生命週期
類變量生命週期最長,隨着“類”的加載而加載,隨着類的消失而消失;實例變量隨着“對象”的消失而消失
III. 靜態的使用注意事項:
靜態方法
只能訪問靜態成員
(包括成員變量和成員方法)
非靜態方法可以訪問靜態也可以訪問非靜態- 靜態方法中不可以定義this,super關鍵字
因爲靜態優先於對象存在,所以靜態方法中不可以出現this,super關鍵字 - 主函數(
main
)是靜態的。
IV. 靜態的利與弊:
- 利:對對象的共享數據進行單獨空間的存儲,節省空間,沒有必要沒一個對象中都存儲一份,可以直接被類名所調用。
- 弊:生命週期過長,訪問出現侷限性(只能訪問靜態)。
11. final
I. final修飾類:
被final修飾的類,是不可以被繼承
的,這樣做的目的可以保證該類不被修改,Java的一些核心的API都是final類,例如String、Integer、Math等。
II. final修飾方法:
子類不可以重寫
父類中被final修飾的方法。
III. final修飾實例變量:(類的屬性,定義在類內,但是在類內的方法之外)
final修飾實例變量時必須初始化
,且不可再修改
。
IV. final修飾局部變量:(方法體內的變量)
final修飾局部變量時只能初始化(賦值)一次,但也可以不初始化。
V. final修飾方法參數:
final修飾方法參數時,是在調用方法傳遞參數時候初始化的。
12. String、StringBuilder、StringBuffuer
String 字符串常量(長度不可變)
StringBuilder 字符串變量(長度可變、非線程安全)
StringBuffer 字符串變量(長度可變、線程安全)
從上圖中可以看到,初始 String str = “hello”;
,然後在這個字符串後面加上新的字符串“world”,執行 str = str + "World";
這個過程是需要重新在棧堆內存中開闢內存空間的,最終得到了“hello world”字符串也相應的需要開闢內存空間,這樣短短的兩個字符串,卻需要開闢三次
內存空間,不得不說這是對內存空間的極大浪費。爲了應對經常性的字符串相關的操作,谷歌引入了兩個新的類——StringBuilder
類和StringBuffer
類來對此種變化字符串進行處理。
和 String
類不同的是,StringBuilder
和 StringBuffer
類的對象能夠被多次的修改,並且不產生新的未使用對象。
StringBuilder sa = new StringBuilder("This is only a"); //創建StringBuilder對象
sa.append(" simple").append(" test"); //使用append()方法添加字符串
StringBuffer sb = new StringBuffer("123"); //創建StringBuffer對象
sb.append("456"); //使用append()方法添加字符串
三者在執行速度方面的比較:StringBuilder
> StringBuffer
> String
由於 StringBuilder
相較於 StringBuffer
有速度優勢,所以多數情況下建議使用 StringBuilder
類。然而在應用程序要求線程安全的情況下,則必須使用 StringBuffer
類。
對於三者使用的總結:
- 如果要操作少量的數據用
String
單
線程操作字符串緩衝區下操作大量數據用StringBuilder
(非線程安全)多
線程操作字符串緩衝區下操作大量數據用StringBuffer
(線程安全)
13. sleep() 和 wait()
sleep()
是線程被調用時,佔着cpu休眠,其他線程不能佔用cpu,OS認爲該線程正在工作,不會讓出系統資源,wait()
是進入等待池等待,讓出系統資源,其他線程可以佔用cpu。
14. & 和 &&
&:邏輯與(and) 運算符兩邊的表達式均爲true時,整個結果才爲true。
&&:短路與 如果第一個表達式爲false時,第二個表達式就不會計算了。
15. == 和equals
==:
- 基本數據類型比較的是
值
- 引用類型比較的是
地址值
equals(Object o):
- 不能比較基本數據類型,基本數據類型不是類類型
- 比較引用類型時(該方法繼承自Object,在object中比較的是地址值)
等同於”==”
;
public boolean equals (Object x){
return this == x;
}
- 如果自己所寫的類中已經重寫了
equals()
方法,那麼就按照用戶自定義的方式來比較兩個對象
是否相等,如果沒有重寫過equals()
方法,那麼會調用父類(Object)中的equals()
方法進行比較,也就是比較地址值
。
注意:equals(Object o)
方法只能是一個對象來調用,然後參數也應傳一個對象。
Q: 什麼時候用==
,什麼時候用equals()
呢?
- 如果是
基本數據
類型那麼就用==
比較 - 如果是引用類型的話,想按照自己的方式去比較,就要
重寫
這個類中的equals()
方法;如果沒有重寫,那麼equals()
和==
比較的效果是一樣的,都是比較引用的地址值 - 如果是比較字符串,那麼直接用equals就可以了,因爲String類裏面已經重寫了
equals()
方法,比較的是字符串的內容,而不是引用的地址值了。
int 和 Integer
見 “LeetCode經典算法題目” 四-12-Q2。
16. public、protected、缺省、private
- public修飾的成員變量和函數可以被類、子類、同一個包中的類以及任意其他類訪問。
- protected修飾的成員變量和函數能被類本身、子類及同一個包中的類訪問。
- 缺省情況(不寫)下,屬於一種包訪問,即能被類本身以及同一個包中的類訪問。
- private修飾的成員變量和函數只能在類本身和內部類中被訪問。
17. List、Set、Map、Queue
見“LeetCode經典算法題目” 四-12。
18. Hashtable 和 HashMap
Hashtable:
是線程安全
的,Hashtable中的方法都是synchronized
修飾的,在多線程併發的環境下,可以直接使用Hashtable,不需要自己爲它的方法實現同步。
HashMap:
是非線程安全
的,HashMap中的方法在缺省情況下是非synchronized
的。在多線程併發的環境下,可能會產生死鎖等問題。需要自己手動增加同步處理。雖然HashMap不是線程安全的,但是它的效率
會比Hashtable要高
很多。這樣設計是合理的。在我們的日常使用當中,大部分時間是單線程操作的。HashMap把這部分操作解放出來了。
Hashtable 和 HashMap的區別:
- 繼承的父類不同
Hashtable繼承自Dictionary
類,而HashMap繼承自AbstractMap
類。但二者都實現了Map接口
。 - 線程安全性不同
Hashtable是線程安全的,HashMap是非線程安全的。 - 是否提供
contains()
方法
Hashtable保留了contains()
,containsValue()
和containsKey()
三個方法,其中contains()
和containsValue()
功能相同。
HashMap把Hashtable的contains()
方法去掉了,改成containsValue()
和containsKey()
,因爲contains()
方法容易讓人引起誤解。 - key和value是否允許null值
Hashtable既不支持Null key也不支持Null value;
HashMap支持null作爲鍵,這樣的鍵只有一個,同時,它還支持可以有一個或多個鍵所對應的值爲null。
19. error和exception
Error(錯誤):
表示系統級的錯誤 和 程序不必處理的異常,是java運行環境中的內部錯誤或者硬件問題。比如:內存資源不足等。對於這種錯誤,程序基本無能爲力,除了退出運行外別無選擇,它是由Java虛擬機拋出
的。
Exception(違例):
表示需要捕捉 或 需要程序進行處理 的異常,它處理的是因爲程序設計的瑕疵而引起的問題或者在外的輸入等引起的一般性問題,是程序必須處理
的。它分爲兩類:
- 運行時異常
runtime exception
,表示無法讓程序恢復的異常,導致的原因通常是因爲執行了錯誤的操作,建議終止程序,因此,編譯器不檢查這些異常。運行時異常我們可以不處理。這樣的異常由虛擬機接管
。出現運行時異常後,系統會把異常一直往上層拋,一直遇到處理代碼。如果不對運行時異常進行處理,那麼出現運行時異常之後,要麼是線程中止,要麼是主程序終止。
常見的運行時異常:數組越界、空指針、數據存儲異常(操作數組時類型不一致)等等。 - 受檢查異常(一般異常)
checked exception
,是表示程序可以處理的異常,也即表示程序可以修復(由程序自己接受異常並且對其進行catch
處理),所以稱之爲受檢查異常。
20. ArrayList 和 LinkList
見 “LeetCode經典算法題目” 四-12。
21. Socket 和 HTTP
HTTP已經很熟悉了,是應用層協議。而Socket不屬於協議範疇,而是一個調用接口
,Socket是對TCP/IP協議的封裝
,通過調用Socket,才能使用TCP/IP協議。Socket連接是長連接,理論上客戶端和服務器端一旦建立連接將不會主動斷開此連接,它屬於請求-響應形式,服務端可主動將消息推送給客戶端。
22. java創建對象的方式
- 使用new關鍵字
ObjectName obj = new ObjectName();
- 使用反射機制
- 使用Class類的
newInstance()
方法: - java.lang.reflect.Constructor類裏也有一個
newInstance()
方法(需要import這個包):
- 使用Class類的
//方式一:
ObjectName obj = ObjectName.class.newInstance();
//方式二:
Class classA = Class.forName("ClassName");
ObjectName obj = (ObjectName) classA.newInstance();
ObjectName obj = ObjectName.class.getConstructor.newInstance();
- 使用clone方法
類必須先實現Cloneable
接口並重寫其clone()
方法,纔可使用該方法。
ObjectName obj = obj.clone();
- 使用反序列化
使用反序列化ObjectInputStream
的readObject()
方法:類必須實現Serializable
接口
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(FILE_NAME))) {
ObjectName obj = ois.readObject();
}
23. JDBC使用步驟過程
- 加載JDBC驅動程序
- 提供JDBC連接的URL
- 創建數據庫的連接
- 創建一個Statement
- 執行SQL語句
- 處理結果
- 關閉JDBC對象