二進制指數退避算法
1)确定基本退避時間(基數),一般定為2τ,也就是一個争用期時間,對于以太網就是51.2μs
2)定義一個參數K,為重傳次數,K=min[重傳次數,10],可見K≤10
3)從離散型整數集合[0,1,2,……,(2^k-1)]中,随機取出一個數記做R
那麼重傳所需要的退避時間為R倍的基本退避時間:即:T=R×2τ。
4)同時,重傳也不是無休止的進行,當重傳16次不成功,就丢棄該幀,傳輸失敗,報告給高層協定
編譯器為什麼要做指令重排呢
而一段代碼并不是由單條指令就可以執行完畢的,而是通過流水線來執行多條指令。
計算機對指令的執行使用到了流水線技術。流水線技術是一種将指令分解為多步,并讓不同指令的各步操作重疊,進而實作幾條指令并行處理。
指令1 IF ID EX MEN WB
指令2 IF ID EX MEN WB
指令的每一步都由不同的硬體完成,假設每一步耗時1ms,執行完一條指令需耗時5ms,
每條指令都按順序執行,那兩條指令則需10ms。
但是通過流水線在指令1剛執行完IF,執行IF的硬體立馬就開始執行指令2的IF,這樣指令2隻需要等1ms,兩個指令執行完隻需要6ms,效率是不是提升巨大!
現有R1,R2,R3三個寄存器,
LW R1,B IF ID EX MEN WB(加載B到R1中)
LW R2,C IF ID EX MEN WB(加載C到R2中)
ADD R3,R2,R1 IF ID × EX MEN WB(R1,R2相加放到R3)
SW A,R3 IF ID x EX MEN WB(把R3 的值儲存到變量A)
在ADD指令執行中有個x,表示中斷、停頓,ADD為什麼要在這裡停頓一下呢?因為這時C還沒加載到R2中,隻能等待,而這個等待使得後邊的所有指令都會停頓一下。
這個停頓可以避免嗎?
當然是可以的,通過指令重排就可以實作,再看一下下面的例子:
要執行
A=B+C;
D=E-F;
通過将D=E-F執行的指令順序提前,進而消除因等待加載完畢的時間。
通過指令重排可以優化指令的執行順序,如消除緩存,減少指令的執行時間。
HTTPS如何實作加密,安全傳輸的?(SSL/TSL協定的運作方式)
1、作用
不使用SSL/TSL通信的HTTP,都是使用的明文進行通信的,是不安全的可能帶來以下安全問題
(1)、竊聽風險:中間人擷取通信内容
(2)、篡改風險:中間人修改通信内容
(3)、冒充風險:中間人冒充通信對方
使用SSL/TSL通信的HTTPS,針對上面HTTP産生的安全問題,希望解決
(1)、将資訊由明文傳輸變成加密傳輸,解決竊聽風險
(2)、具有校驗機制,被篡改可以及時發現,解決篡改風險
(3)、使用數字證書,解決冒充風險
2、關鍵概念
對稱加密
對稱加密使用通俗的語言講,就像我們平時使用的鑰匙,要把鎖,使用A加密之後,就得使用A進行解鎖,加鎖與解鎖都是使用A密鑰
非對稱加密
非對稱加密的意思是,有兩把密鑰,分别是公鑰(public key)A 和 私鑰(private key)A',使用A加密,隻用使用A'才能解密;反之,使用A'加密,隻有使用A才能解密;它們二者都具有加密與解密的功能。
3、幾種加密方式的比較以及存在的問題
現有浏覽器(Browser)與 伺服器(Server)
使用“對稱加密”方式
伺服器以“明文”的方式傳給浏覽器自己的密鑰A,浏覽器收到後,使用密鑰A對資料包進行加密,然後傳輸給伺服器,伺服器收到後使用自己的密鑰A進行解密。過程簡單,但是安全問題很明顯,我在中間截獲,輕而易舉就會獲得密鑰A,然後對于後來傳輸的資料随便處理了!!
問題出在哪?是因為明文傳輸公鑰A,可能會被截獲!!而浏覽器又不可能實作存儲所有網站的密鑰!!
使用“非對稱加密”方式
伺服器以“明文”的方式傳給浏覽器自己的公鑰A,浏覽器收到後,使用公鑰A對資料包進行加密,然後傳輸給伺服器,伺服器收到後使用自己的密鑰A'進行解密。過程簡單,但是安全問題很明顯,我在中間截獲,輕而易舉就會獲得密鑰A,然後對于後來傳輸的資料随便處理了!!
問題出在哪?是因為明文傳輸公鑰A,可能會被截獲!!而浏覽器又不可能實作存儲所有網站的公鑰!!
使用“改良的非對稱加密”方式
現在浏覽器和伺服器雙方各自有有屬于自己的公鑰與私鑰,浏覽器:公鑰A,私鑰A' ; 伺服器:公鑰B,私鑰B' ;
(1)、伺服器使用“明文”的方式給浏覽器自己的公鑰B
(2)、浏覽器使用“明文”的方式給伺服器自己的公鑰A
(3)、伺服器給浏覽器發資料,伺服器使用A進行加密,由于隻有浏覽器有私鑰A',是以隻用浏覽器才能解密,才能檢視資料
(4)、浏覽器給伺服器發資料,浏覽器使用B進行加密,由于隻有伺服器有私鑰B',是以隻有伺服器才能解密,才能檢視資料
問題出在哪?HTTPS并沒有使用這一種方式,原因在于非對稱加密方式比較耗費時間,特别是較大資料的時候,而對稱加密的方式相比之下要快的多,于是是否能将 對稱加密和非對稱加密 這兩種方式結合起來呢??
使用“對稱加密+非對稱加密”的方式
現在伺服器有非對稱密鑰:公鑰B和密鑰B'; 浏覽器有對稱密鑰X
(1)、伺服器使用“明文”的方式傳給浏覽器自己的公鑰B
(2)、浏覽器使用公鑰B加密,攜帶自己的對稱密鑰X,傳給伺服器
(3)、因為隻用伺服器端擁有私鑰B',是以隻用伺服器能夠解密使用公鑰B加密後的資料,進而拿到浏覽器的對稱密鑰X
(4)、至此,伺服器和浏覽器之間的資料傳送都使用對稱密鑰進行加密,實作安全傳送
問題出在哪?有什麼安全問題?(中間人攻擊)
(1)、伺服器使用“明文”的方式傳給浏覽器自己的公鑰B
(2)、中間人劫持,儲存伺服器的公鑰B,并且,莫名頂替将自己的公鑰M傳給浏覽器
(3)、浏覽器收到後,誤以為是伺服器傳送過來的,使用公鑰M加密,攜帶自己的對稱密鑰X,傳給伺服器
(4)、中間人繼續劫持,使用自己的私鑰解密浏覽器傳來的資料,進而得到浏覽器的對稱密鑰X,然後使用伺服器的公鑰B加密,将浏覽器的對稱密鑰X傳送給伺服器
(5)、伺服器,收到中間人傳來的加密資料,使用自己的私鑰B’解密,得到浏覽的對稱密鑰X,還傻呵呵的以為是浏覽器發給自己的呢!!
(6)、浏覽器與伺服器絲毫沒有察覺到中間人的存在,故發送的資料中間人可以随便支配!!
是以問題是?浏覽器并不知道“明文”傳送給自己的公鑰是中間人的還是伺服器的,中間人可以擷取公鑰并且更改公鑰,進而對雙向傳輸的内容進行篡改僞造等,如果确定自己收到的公鑰是伺服器的呢?由此引入“數字證書”!
4、數字證書的引入
數字證書概念
數字證書的引入就是确認傳給浏覽器的公鑰就是伺服器的,即證明公鑰是屬于伺服器,就像我們的身份證一樣。伺服器的網站在使用HTTPS之前,必須想CA機構申請一份“數字證書”,數字證書上包含:證書持有者、證書持有者的公鑰資訊。
伺服器的網站将數字證書發送給浏覽器就可以了,浏覽器就會擷取其公鑰資訊。但是,數字證書被截獲了怎麼辦?裡面的公鑰資訊并篡改了怎麼辦?是以我們引入了“數字簽名”!!
數字簽名概念
數字簽名是用來確定明文 數字證書 沒有被修改的一種手段,如何證明?見下面數字簽名的生成過程。
數字簽名的生成
如圖所示,數字簽名生成步驟如下:
(1)、CA機構擁有一對非對稱密鑰
(2)、CA機構對明文的數字證書進行hash,得到散列值
(3)、CA機構,使用自己的私鑰加密以上生成的散列值
CA機構
通俗的講,CA機構即 Certificate Authority證書授權機構,就像我們的辦理身份證的政府一樣是可以信賴的機構
5、如何配合數字證書實作HTTPS(流程)?
浏覽如何驗證收到的數字證書是伺服器發過來的,沒有并修改呢?
如圖所示浏覽收到傳來的數字證書(數字證書明文+數字簽名)後,
(1)、處理數字簽名,使用CA機構的公鑰解密獲得散列hash值
(2)、處理數字證書,使用證書明文裡面說的hash算法對數字證書明文進行hash,得到散列hash值
(3)、比對兩者的散列值是否相等,如果相等,則表明證書可以信賴,沒有被中間人截獲篡改
假設數字證書被中間人劫持修改裡面的明文?
如果中間人劫持了數字證書,修改了裡面數字證書的明文,但是注意,中間人并沒有CA機構的“私鑰”,是以并不能産生相對應的數字簽名,是以即使修改了裡面的明文,到了浏覽器這邊也會被出現,進而,判斷此證書不可信(也就是此時傳來的公鑰可能不是伺服器網站的公鑰)
你怎麼擁有CA機構的公鑰?你怎麼确定你自己的公鑰是正确可信的?
上文直接寫了使用CA機構的公鑰解密,但是我怎麼會有CA機構的公鑰呢?原因是作業系統和浏覽器會預裝一下它們信任 的“根證書”,其中有CA機構的根證書,是以也就有了CA機構的公鑰了!!而除非你的CA機構被篡改了,否則一定是正确可信的!!
假設數字證書被中間人劫持将證書換成自己伺服器網站的證書呢( 将數字證書(明文+數字簽名)整個掉包換了怎麼辦 )?
這樣就更不需要擔心了,數字證書裡面包含有關于伺服器網站的資訊,浏覽器可以通過這個數字整數的持有者判斷是不是我們請求的伺服器網站
6、HTTPS在每次的請求中都要使用SSL/TLS,進行握手傳送密鑰?
不用,可以使用session技術,每次請求都要進行一次密鑰傳輸顯然是比較耗時的,伺服器會為每一個和自己建立連接配接的浏覽器維護一個session,在TSL握手階段傳給浏覽器session id,浏覽器生成密鑰之後,将密鑰傳給伺服器網站,伺服器網站會将傳來的密鑰資訊儲存在session中,之後浏覽器的每次請求隻需要攜帶session id即可,伺服器網站就會根據session id查找密鑰資訊進行加密與解密的操作,這樣就不需要每次都重新制作與傳送密鑰了!!
強、軟、弱、虛引用的差別和使用
強引用(StrongReference)
強引用在 java.lang.ref 中并沒有實際的對應類型,但我們程式中幾乎所有的引用使用的都是強引用。
StringBuildersb=newStringBuilder();上面通過在堆中建立執行個體,然後指派給棧中局部變量 sb 的方式 就是 強引用。強引用有如下特點:
- 強引用可以直接通路目标對象
- 強引用(存在)指向的對象任何時候都不會被回收,JVM甯願抛出OOM異常,也不會回收。
- 強引用可能會導緻記憶體洩漏
解釋: 1. 記憶體溢出(out of memory) 是指 程式在申請記憶體時,沒有足夠的記憶體空間供其使用,出現 out of memory.
記憶體洩漏(memory leak) 是指 程式申請記憶體後,無法釋放已申請的記憶體空間,這樣的洩漏積少成多,memory leak 會導緻 out of memory .
作者:王不二
連結:https://zhuanlan.zhihu.com/p/85576999
來源:知乎
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
軟引用(SoftReference)
軟引用對應的類為 java.lang.ref.SoftReference, 一個軟引用中的對象,不會很快被JVM回收,JVM會根據目前堆的使用情況來判斷何時回收,當堆的使用率超過門檻值時,才回去回收軟引用中的對象。
先通過一個例子來了解一下軟引用:
Object obj = new Object();
SoftReference softRef = new SoftReference<Object>(obj);
//删除強引用
obj = null;
//調用gc
System.gc();
System.out.println("gc之後的值:" + softRef.get()); // 對象依然存在
軟引用也可以和一個引用隊列聯合使用,如果軟引用中的對象(obj)被回收,那麼軟引用會被 JVM 加入關聯的引用隊列中。
ReferenceQueue<Object> queue = new ReferenceQueue<>();
Object obj = new Object();
SoftReference softRef = new SoftReference<Object>(obj,queue);
//删除強引用
obj = null;
//調用gc
System.gc();
System.out.println("gc之後的值: " + softRef.get()); // 對象依然存在
//申請較大記憶體使記憶體空間使用率達到門檻值,強迫gc
byte[] bytes = new byte[100 * 1024 * 1024];
//如果obj被回收,則軟引用會進入引用隊列
Reference<?> reference = queue.remove();
if (reference != null){
System.out.println("對象已被回收: "+ reference.get()); // 對象為null
}
引用隊列(ReferenceQueue)作用
Queue的意義在于我們在外部可以對queue中的引用進行監控,當引用中的對象被回收後,我們可以對引用對象本身繼續做一些清理操作,因為我們引用對象(softRef)也占有一定的資源。
作者:王不二
連結:https://zhuanlan.zhihu.com/p/85576999
來源:知乎
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
弱引用(WeakReference)
弱引用中的對象具有很短的聲明周期,因為在系統GC時,隻要發現弱引用,不管堆空間是否足夠,都會将對象進行回收。由于垃圾回收器是一個優先級很低的線程,是以不一定會很快發現那些隻具有弱引用的對象。
弱引用的簡單使用:
Object obj = new Object();
WeakReference weakRef = new WeakReference<Object>(obj);
//删除強引用
obj = null;
System.out.println("gc之後的值:" + weakRef.get()); // 對象依然存在
//調用gc
System.gc();
System.out.println("gc之後的值:" + weakRef.get()); // 對象為null
弱引用也可以和一個引用隊列聯合使用,如果弱引用中的對象(obj)被回收,那麼軟引用會被 JVM 加入關聯的引用隊列中。
ReferenceQueue<Object> queue = new ReferenceQueue<>();
Object obj = new Object();
WeakReference weakRef = new WeakReference<Object>(obj,queue);
//删除強引用
obj = null;
System.out.println("gc之後的值: " + weakRef.get()); // 對象依然存在
//調用gc
System.gc();
//如果obj被回收,則軟引用會進入引用隊列
Reference<?> reference = queue.remove();
if (reference != null){
System.out.println("對象已被回收: "+ reference.get()); // 對象為null
}
軟引用和弱引用都非常适合儲存那些可有可無的緩存資料,當記憶體不足時,緩存資料被回收(再通過備選方案查詢),當記憶體充足時,也可以存在較長時間,起到加速的作用。
應用
- WeakHashMap
當key隻有弱引用時,GC發現後會自動清理鍵和值,作為簡單的緩存表解決方案。
- ThreadLocal
ThreadLocal.ThreadLocalMap.Entry 繼承了弱引用,key為目前線程執行個體,和WeakHashMap基本相同。
虛引用(PhantomReference)
虛引用 就是 形同虛設 ,它并不能決定 對象的生命周期。任何時候這個隻有虛引用的對象都有可能被回收,因為虛引用的對象可以看做沒有引用對象指向他,是以随時可以被回收。是以,虛引用主要用來跟蹤對象被垃圾回收器的回收,清理被銷毀對象的相關資源。PhantomReference的 get() 方法永遠傳回null ,而且隻提供了與引用隊列同用的構造函數。是以虛引用必須和引用隊列一同使用,當垃圾回收器回收對象時發現還有虛引用就把虛引用放到隊列裡面,引用隊列的引用對象也占用資源,可以進一步進行清理。
Map<Object, String> map = new HashMap<>();
ReferenceQueue<Object> queue = new ReferenceQueue<>();
Object obj = new Object();
PhantomReference phantomRef = new PhantomReference<Object>(obj,queue);
map.put(obj,"obj val");
new CheckRefQueue(queue,map).start();
//删除強引用
obj = null;
Thread.sleep(1000);
int i = 1;
while (true){
System.out.println("第"+i+"次gc");
System.gc();
Thread.sleep(1000);
}
public class CheckRefQueue extends Thread {
private ReferenceQueue queue;
private Map<Object, String> map;
public CheckRefQueue(ReferenceQueue queue, Map<Object, String> map) {
this.queue = queue;
this.map = map;
}
@Override
public void run() {
// 等待,直到對象呗回收
Reference reference = queue.remove();
// 釋放引用對象的引用
map.remove(reference.get());
}
}
總結
如何判斷一個對象是否可以垃圾回收器被回收?
兩大算法
1.引用計數法
每個對象都維護一個引用計數器,每個此對象被引用的時候就将計數器加一,當對象被取消引用的時候就将計數器減一,如果計數器為0,那麼認為這個對象是垃圾,可以被回收。
引用計數法的缺點:
每個對象都有一個引用計數器,維護這個引用計數器有一定的資源消耗
無法解決循環引用的問題(假如說A對象中引用了B,B對象中引用了A,其他的所有對象都沒有引用A和B對象,這個時候A和B對象其實就是垃圾,可以被回收,但是由于采用引用計數法,A和B對象的引用計數器都為1,是以垃圾回收器認為A和B對象不是垃圾,無法對他們進行回收)
2.GC roots可達性算法
目前主流的商用JVM都是通過可達性分析來判斷對象是否可以被回收的。
這個算法的基本思路是:
通過一系列被稱為「GC
Roots」的根對象作為起始節點集,從這些節點開始,通過引用關系向下搜尋,搜尋走過的路徑稱為「引用鍊」,如果某個對象到GC
Roots沒有任何引用鍊相連,就說明該對象不可達,即可以被回收
初看這段話是不是一臉懵呢?筆者當初也是的,完全不知道什麼意思,後面才慢慢了解。
要想了解可達性算法,首先要想明白幾個問題:
1、什麼是對象可達?
對象可達指的就是:雙方存在直接或間接的引用關系。 根可達或GC Roots可達就是指:對象到GC Roots存在直接或間接的引用關系。
如下代碼:
public class MyObject {
private String objectName;//對象名
private MyObject refrence;//依賴對象
public MyObject(String objectName) {
this.objectName = objectName;
}
public MyObject(String objectName, MyObject refrence) {
this.objectName = objectName;
this.refrence = refrence;
}
public static void main(String[] args) {
MyObject a = new MyObject("a");
MyObject b = new MyObject("b");
MyObject c = new MyObject("c");
a.refrence = b;
b.refrence = c;
new MyObject("d", new MyObject("e"));
}
}
建立了5個對象,他們之間的引用關系如圖:
假設a是GC Roots的話,那麼b、c就是可達的,d、e是不可達的。
2、GC Roots是什麼?
垃圾回收時,JVM首先要找到所有的GC Roots,這個過程稱作 「枚舉根節點」 ,這個過程是需要暫停使用者線程的,即觸發STW。
然後再從GC Roots這些根節點向下搜尋,可達的對象就保留,不可達的對象就回收。
那麼,到底什麼是GC Roots呢?
GC Roots就是對象,而且是JVM确定目前絕對不能被回收的對象(如方法區中類靜态屬性引用的對象 )。
隻有找到這種對象,後面的搜尋過程才有意義,不能被回收的對象所依賴的其他對象肯定也不能回收嘛。
當JVM觸發GC時,首先會讓所有的使用者線程到達安全點SafePoint時阻塞,也就是STW,然後枚舉根節點,即找到所有的GC
Roots,然後就可以從這些GC Roots向下搜尋,可達的對象就保留,不可達的對象就回收。
即使是号稱幾乎不停頓的CMS、G1等收集器,在枚舉根節點時,也是要暫停使用者線程的。
GC Roots是一種特殊的對象,是Java程式在運作過程中所必須的對象,而且是根對象。
那麼,哪些對象可以成為GC Roots呢?
3、哪些對象可以作為GC Roots?
可以作為GC Roots的對象可以分為兩大類:全局對象和執行上下文。
下面就一起來了解一下為什麼這幾類對象可以被作為GC Roots。
1、方法區靜态屬性引用的對象
全局對象的一種,Class對象本身很難被回收,回收的條件非常苛刻,隻要Class對象不被回收,靜态成員就不能被回收。
2、方法區常量池引用的對象
也屬于全局對象,例如字元串常量池,常量本身初始化後不會再改變,是以作為GC Roots也是合理的。
3、方法棧中棧幀本地變量表引用的對象
屬于執行上下文中的對象,線程在執行方法時,會将方法打包成一個棧幀入棧執行,方法裡用到的局部變量會存放到棧幀的本地變量表中。隻要方法還在運作,還沒出棧,就意味這本地變量表的對象還會被通路,GC就不應該回收,是以這一類對象也可作為GC Roots。
4、JNI本地方法棧中引用的對象
和上一條本質相同,無非是一個是Java方法棧中的變量引用,一個是native方法(C、C++)方法棧中的變量引用。
5、被同步鎖持有的對象
被synchronized鎖住的對象也是絕對不能回收的,目前有線程持有對象鎖呢,GC如果回收了對象,鎖不就失效了嘛。
尾巴
總結,可達性分析就是JVM首先枚舉根節點,找到一些為了保證程式能正常運作所必須要存活的對象,然後以這些對象為根,根據引用關系開始向下搜尋,存在直接或間接引用鍊的對象就存活,不存在引用鍊的對象就回收。
java四種權限修飾符
通路權限修飾符
①public:意為公開的,通路權限最高,可以跨包通路。
②protect:意為受保護的,權限次之,可以在同包和子/父類中通路。
③default:意為預設的,一般不寫,權限次之,可以在同包中通路。
④private:意為私有的,權限最低,隻能在本類中通路。
是以,為了保證安全性,一般把通路權限降到最低。
四種通路權限修飾符如下圖:
2.abstract(抽象)修飾符
①abstract修飾類,會使這個類成為一個抽象類,這個類将不能生成對象執行個體,但可以做為對象變量聲明的類型,也就是編譯時類型,抽象類就像當于一類的半成品,需要子類繼承并覆寫其中的抽象方法。
②abstract修飾方法,會使這個方法變成抽象方法,也就是隻有聲明(定義)而沒有實作,實作部分以”;”代替。需要子類繼承實作(覆寫)。
舉例:
abstract class E{
public void method1(){
/*code*/
};
public abstract void method2();//public abstract 可以省略
}
class F extends E{
//實作抽象方法method2
void method2(){
//寫具體實作的代碼
}
//method1不是抽象方法,不需要具體實作,但如果寫了具體實作的方法會覆寫原有方法
最後再主方法裡面定義一個父類引用指向子類對象,就會發生多态現象,比如
E e=new F();//E是抽象父類,F是E的繼承類
e.show();//實際調用了子類裡面的method2()方法
總結:
①抽象類和普通類差不多,隻是不能用new執行個體化,必須要繼承。
②抽象的方法所在類一定是抽象類;抽象類中的方法不一定都是抽象方法。
③抽象類中的抽象方法必須在子類中寫具體實作的方法;如果抽象類中的方法是具體方法,那麼重寫該方法會覆寫原方法。
3.final修飾符
final變量必須被顯式初始化,并且隻能被指派一次值
final修飾基本類型變量的時候, 該變量不能重新指派
final修飾引用類型變量的時候, 該變量不能重新指向其他對象
final修飾的方法為最終的方法, 該方法不能被重寫
private類型的方法都預設為是final方法,因而也不能被子類重寫
final修飾的類為最終的類, 不能被繼承
4.static修飾符
如果聲明了靜态方法或變量,值是放在方法區,因為方法區是一個資料共享區;是以不管什麼變量通路它,都是同一份.
在靜态方法中不能直接通路執行個體方法和執行個體變量.
在靜态方法中不能使用this和super關鍵字.
靜态方法不能被abstract修飾.
靜态的成員變量可以使用類名或者是對象進行通路,非靜态成員變量隻能使用對象進行通路.
靜态函數可以直接通路靜态的成員,但是不能夠直接通路非靜态成員.,非靜态函數可以通路靜态和非靜态成員.
當類被加載時,靜态代碼塊隻能被執行一次。類中不同的靜态方法代碼塊按他們在類中出現的順序被依次執行.
當多個修飾符連用時,修飾符的順序可以颠倒,不過作為普遍遵守的編碼規範,通常把通路控制修飾符放在首位,其次是static或abstact修飾符,接着就是其他的修飾符.
//這些修飾符放在一起是無效的
abstract與private
abstract與final
abstract與static
Java intern() 方法
它遵循以下規則:對于任意兩個字元串 s 和 t,當且僅當 s.equals(t) 為 true 時,s.intern() == t.intern() 才為 true。
文法
publicString()
參數
- 無
傳回值
一個字元串,内容與此字元串相同,但一定取自具有唯一字元串的池。
執行個體
publicclassTest{publicstaticvoid(String[]){StringStr1=newString("www.runoob.com");StringStr2=newString("WWW.RUNOOB.COM");System.out.print("規範表示:");System.out.println(Str1.intern());System.out.print("規範表示:");System.out.println(Str2.intern());}}
以上程式執行結果為:
規範表示:www.runoob.com
規範表示:WWW.RUNOOB.COM
-
盡管在輸出中調用intern方法并沒有什麼效果,但是實際上背景這個方法會做一系列的動作和操作。在調用”ab”.intern()方法的時候會傳回”ab”,但是這個方法會首先檢查字元串池中是否有”ab”這個字元串,如果存在則傳回這個字元串的引用,否則就将這個字元串添加到字元串池中,然會傳回這個字元串的引用。
可以看下面一個範例:
String="a";String="b";String="ab";String=+;String=newString("ab");System.out.println(str5.equals(str3));System.out.println(str5 ==);System.out.println(str5.intern()== str3);System.out.println(str5.intern()==);
得到的結果:
truefalsetruefalse
為什麼會得到這樣的一個結果呢?我們一步一步的分析。
- 第一、str5.equals(str3)這個結果為true,不用太多的解釋,因為字元串的值的内容相同。
- 第二、str5 == str3對比的是引用的位址是否相同,由于str5采用new String方式定義的,是以位址引用一定不相等。是以結果為false。
- 第三、當str5調用intern的時候,會檢查字元串池中是否含有該字元串。由于之前定義的str3已經進入字元串池中,是以會得到相同的引用。
- 第四,當str4 = str1 + str2後,str4的值也為”ab”,但是為什麼這個結果會是false呢?先看下面代碼:
String=newString("ab");String=newString("ab");String="ab";String="a"+"b";String="b";String="a"+;System.out.println(b.intern()==);System.out.println(b.intern()==);System.out.println(b.intern()==);System.out.println(b.intern()==);System.out.println(b.intern()==.intern());
運作結果:
falsetruetruefalsetrue
由運作結果可以看出來,b.intern() == a和b.intern() == c可知,采用new 建立的字元串對象不進入字元串池,并且通過b.intern() == d和b.intern() == f可知,字元串相加的時候,都是靜态字元串的結果會添加到字元串池,如果其中含有變量(如f中的e)則不會進入字元串池中。但是字元串一旦進入字元串池中,就會先查找池中有無此對象。如果有此對象,則讓對象引用指向此對象。如果無此對象,則先建立此對象,再讓對象引用指向此對象。
當研究到這個地方的時候,突然想起來經常遇到的一個比較經典的Java問題,就是對比equal和==的差別,當時記得老師隻是說“==”判斷的是“位址”,但是并沒說清楚什麼時候會有位址相等的情況。現在看來,在定義變量的時候指派,如果指派的是靜态的字元串,就會執行進入字元串池的操作,如果池中含有該字元串,則傳回引用。
執行下面的代碼:
String="abc";String="abc";String="a"+"b"+"c";String="a"+"bc";String="ab"+"c";System.out.println(a ==);System.out.println(a ==);System.out.println(a ==);System.out.println(a ==);System.out.println(c ==);System.out.println(c ==);
運作的結果:
truetruetruetruetruetrue
Java 判斷兩個對象是否相等
== : 它的作用是判斷兩個對象的位址是不是相等。即,判斷兩個對象是不是同一個對象。(基本資料類型==比較的是值,引用資料類型==比較的是記憶體位址)
equals() : 它的作用也是判斷兩個對象是否相等。但它一般有兩種使用情況:
- 情況1:類沒有覆寫equals()方法。則通過equals()比較該類的兩個對象時,等價于通過“==”比較這兩個對象。
- 情況2:類覆寫了equals()方法。一般,我們都覆寫equals()方法來兩個對象的内容相等;若它們的内容相等,則傳回true (即,認為這兩個對象相等)。
舉個例子:
說明:
- String中的equals方法是被重寫過的,因為object的equals方法是比較的對象的記憶體位址,而String的equals方法比較的是對象的值。
- 當建立String類型的對象時,虛拟機會在常量池中查找有沒有已經存在的值和要建立的值相同的對象,如果有就把它賦給目前引用。如果沒有就在常量池中重新建立一個String對象。
為什麼重寫equals()方法就必須重寫hashcode()方法?
equals()方法和hashcode()方法都屬于Object類,在Java中,所有的類都是Object類的子類,也就是說,任何Java對象都可調用Object類的方法。
equals()方法:
public boolean equals(Object obj) {
return (this == obj);
}
很明顯,該方法就是用來判斷兩個對象是否是同一個對象。在Object類源碼中,其底層是使用了“==”來實作,也就是說通過比較兩個對象的記憶體位址是否相同判斷是否是同一個對象,實際上,該equals()方法通常沒有太大的實用價值。而我們往往需要用equals()來判斷 2個對象在邏輯上是否等價,而非驗證它的唯一性。這樣我們在實作自己的類時,就要重寫equals()方法。
hashcode()方法:
public native int hashCode();
一提到hashcode,很自然就想到哈希表。将某一key值映射到表中的一個位置,進而達到以O(1)的時間複雜度來查詢該key值。Object類源碼中,hashCode()是一個native方法,哈希值的計算利用的是記憶體位址。
我們看一下Object類中關于hashCode()方法的注釋
1.在 Java 應用程式執行期間,在對同一對象多次調用 hashCode 方法時,必須一緻地傳回相同的整數,前提是将對象進行 equals 比較時所用的資訊沒有被修改。從某一應用程式的一次執行到同一應用程式的另一次執行,該整數無需保持一緻。
2.如果根據 equals(Object) 方法,兩個對象是相等的,那麼對這兩個對象中的每個對象調用 hashCode 方法都必須生成相同的整數結果。
3.如果根據 equals(java.lang.Object) 方法,兩個對象不相等,那麼對這兩個對象中的任一對象上調用 hashCode 方法不要求一定生成不同的整數結果。但是,程式員應該意識到,為不相等的對象生成不同整數結果可以提高哈希表的性能。
通俗的講,注釋中第二點和第三點的含義就是equals()和hashcode()方法要保持相當程度的一緻性,equals()方法相等,hashcode()必須相等;反之,equals方法不相等,hashcode可以相等,可以不相等。但是兩者的一緻有利于提高哈希表的性能。
是以,源碼注釋來看,兩方法的同時重寫是很必要的。
實際來看,不同時重寫如何?
equals()相等的的兩個等價對象因為hashCode不同,是以在hashmap中的table數組的下标不同,進而這兩個對象就會同時存在于集合中,在調用hashmap集合中的方法時就會出現邏輯的錯誤,也就是,你的equals()方法也“白白”重寫了。
是以,對于“為什麼重寫equals()就一定要重寫hashCode()方法?”這個問題應該是有個前提,就是你需要用到HashMap,HashSet等Java集合。用不到哈希表的話,其實僅僅重寫equals()方法也可以吧。而工作中的場景是常常用到Java集合,是以Java官方建議重寫equals()就一定要重寫hashCode()方法。
hashCode()與equals()的相關規定
- 如果兩個對象相等,則hashcode一定也是相同的
- 兩個對象相等,對兩個對象分别調用equals方法都傳回true
- 兩個對象有相同的hashcode值,它們也不一定是相等的
- 是以,equals方法被覆寫過,則hashCode方法也必須被覆寫
- hashCode()的預設行為是對堆上的對象産生獨特值。如果沒有重寫hashCode(),則該class的兩個對象無論如何都不會相等(即使這兩個對象指向相同的資料)
Java中Integer類型裝箱拆箱及其注意點
Java中,一般地,我們使用資料類型的時候,會直接使用int,double,byte,long等内置類型,但是在某些場合下,我們會遇到需要使用對象而非内置資料類型的情況,需要引用對象(為啥要拆箱和裝箱)。 Java語言為每一個内置資料類型提供了包裝類,這些包裝類包括Intger,Double等,此文我們拿Integer進行分析。
這種由編譯器特别支援的包裝稱為裝箱,是以當内置資料類型被當做對象使用的時候,編譯器會把内置類型裝箱為包裝類。相似的,編譯器也可以把一個對象拆箱為内置類型。
public class Test{
public static void main(String args[]){
Integer x = 5;
x = x + 10;
System.out.println(x);
}}
在以上代碼中,當我們為Integer類型的x指派5的時候,編譯器對5進行了裝箱操作,相當于調用了Integer.valueOf(5)。而在x=x+10時候,編譯器又對x進行了拆箱操作,因為隻有内置類型才支援+操作。特别要注意的一點是,對于-128到127之間的數值,裝箱後都會放在記憶體裡進行重用,也就是說Integer a = 5和Integer b = 5,系統都會去複用同一個對象,是以a和b實際上指向同一個對象的位址。但如果超過了這個規定的通路,系統會重新new一個Integer對象出來,如Integer a = 555和Integer b = 555,此時a和b兩個對象便指向了不同對象的位址。
資料庫中char與varchar類型的差別
sql中的字段,char與varchar這個是很多初學者都會疑惑的,今天我特地總結了一下,也是當作複習.
首先明确的是,char的長度是不可變的,而varchar的長度是可變的,定義一個char[10]和varchar[10],如果存進去的是‘zhihu’,那麼char所占的長度依然為10,初‘zhihu’外,後面跟5個空格,而varchar就立馬把長度變為5了,取資料的時候,char類型的要用trim()去掉多餘的空格,而varchar是不需要的。
盡管如此,char的存取數度還是要比varchar要快得多,因為其長度固定,友善程式的存儲與查找;但是char也為此付出的是空間的代價,因為其長度固定,是以難免會有多餘的空格占位符占據空間,可謂是以空間換取時間效率,而varchar是以空間效率為首位的。char的存儲方式是,對英文字元(ASCII)占用1個位元組,對一個漢字占用兩個位元組;而varchar的存儲方式是,對每個英文字元占用2個位元組,漢字也占用2個位元組。兩者的存儲資料都非unicode的字元資料。
日期類型datetime和timestamp差別在哪裡?
一、相同點
datetime和timestamp都可以表示 YYYY-MM-DD HH:MM:SS 這種年月日時分秒格式的資料。
并且從MySQL5.6.4之後這兩者都可以包含秒後的小數部分,精度最高為微妙(6位)。
這裡有一個點需要注意,就是在MySQL5.6.4之前,這兩個是都表示不了小數的。
二、不同點
接下來來說下他們的不同點。
1)存儲範圍不同:
datetime的存儲範圍是 1000-01-01 00:00:00.000000 到 9999-12-31 23:59:59.999999,而timestamp的範圍是 1970-01-01 00:00:01.000000到 2038-01-19 03:14:07.999999(準備的來講應該是UTC範圍);
如果我們存儲timestamp的時候,存了不在它範圍内的時間值時,會直接抛出異常。
2)時區相關:
datetime存儲與時區無關(準備來說是datetime隻支援一個時區,就是存儲時目前伺服器的時區),而timestamp存儲的是與時區有關。MySQL在存儲TIMESTAMP時,會先将時間從目前伺服器的時區轉換為UTC(世界協調時)以進行存儲,然後查詢時從UTC轉換為目前時區以進行傳回。也就是說使用timestamp進行存儲的時間傳回的時候會随着資料庫的時區而發生改變。而datetime的存儲則與時區無關,資料是什麼就存儲什麼,也就傳回什麼。
3)存儲大小:
在5.6.4之前,datetime存儲占用8個位元組,而timestamp是占用4位元組;但是在5.6.4之後,由于這兩個類型允許有小數部分,是以占用的存儲空間和以前不同;MySQL規範規定,datetime的非小數部分需要5個位元組,而不是8個位元組,而timestamp的非小數部分是需要4個位元組,并且這兩個部分的小數部分都需要0到3個位元組,具體取決于存儲值的小數秒精度。
聚簇索引和非聚簇索引
Mysql 索引實作:
聚簇索引: 索引 和 資料檔案為同一個檔案。非聚簇索引: 索引 和 資料檔案分開的索引。
MyISAM & InnoDB 都使用B+Tree索引結構。但是底層索引存儲不同,MyISAM 采用非聚簇索引,而InnoDB采用聚簇索引。
MyISAM索引原理:采用非聚簇索引-MyISAM myi索引檔案和myd資料檔案分離,索引檔案僅儲存資料記錄的指針位址。葉子節點data域存儲指向資料記錄的指針位址。(底層存儲結構: frm -表定義、 myi -myisam索引、 myd-myisam資料)
InnoDB索引原理:
采用聚簇索引- InnoDB資料&索引檔案為一個idb檔案,表資料檔案本身就是主索引,相鄰的索引臨近存儲。 葉節點data域儲存了完整的資料記錄(資料[除主鍵id外其他列data]+主索引[索引key:表主鍵id])。 葉子節點直接存儲資料記錄,以主鍵id為key,葉子節點中直接存儲資料記錄。(底層存儲結構: frm -表定義、 ibd: innoDB資料&索引檔案)
主鍵索引和普通索引有什麼差別?
在 MySQL 中, 索引是在存儲引擎層實作的, 是以并沒有統⼀的索引标準, 由于 InnoDB 存儲引擎在 MySQL資料庫中使⽤最為⼴泛, 下⾯以 InnoDB 為例來分析⼀下其中的索引模型.在 InnoDB 中, 表都是根據主鍵順序以索引的形式存放的, InnoDB 使⽤了 B+ 樹索引模型,是以資料都是存儲在 B+ 樹中的, 如圖所示:
從圖中可以看出, 根據葉子節點内容不同,索引類型分為主鍵索引和非主鍵索引.
主鍵索引也被稱為聚簇索引,葉子節點存放的是整行資料; 而非主鍵索引被稱為二級索引,葉子節點存放的是主鍵的值.
如果根據主鍵查詢, 隻需要搜尋ID這顆B+樹
而如果通過非主鍵索引查詢, 需要先搜尋k索引樹, 找到對應的主鍵, 然後再到ID索引樹搜尋一次, 這個過程叫做回表.
總結, 非主鍵索引的查詢需要多掃描一顆索引樹, 效率相對更低.
作者:你的雷哥
本文版權歸作者所有,歡迎轉載,但未經作者同意必須在文章頁面給出原文連接配接,否則保留追究法律責任的權利。