介紹
享元模式
享元模式可以減少創建對象的數量,從而減少內存佔用。享元模式本質上就是一個對象池,利用享元模式創建對象的邏輯也很簡單,創建之前,首先去對象池裡看看是不是存在;如果已經存在,就利用對象池李的對象,如果不存在,就會新創建一個對象,並且把這個新創建出來的對象放進線程池裡。
為什麼要有常量池?
常量池是為了避免頻繁的創建和銷燬對象而影響系統性能,其實現了對象的共享。
例如字符串常量池,在編譯階段就把所有的字符串文字放到一個常量池中。
(1)節省內存空間:常量池中所有相同的字符串常量被合併,只佔用一個空間。
(2)節省運行時間:比較字符串時,== 比equals()快。對於兩個引用變量,只用==判斷引用是否相等,也就可以判斷實際值是否相等。
基本數據類型的包裝類和常量池
Java有8種基本數據類型
整數類型:byte,short,int,long。包裝類型為Byte,Short,Integer,Long
浮點類型:float、double。包裝類型為Float,Double
字符類型:char。包裝類型為Character
布爾類型:boolean。包裝類型為Boolean
8種包裝類型中除了Float,Double沒有實現常量池,剩下的都實現了
為了更方便理解後面的內容,這裡先解釋一下自動裝箱和拆箱
自動裝箱和拆箱
自動裝箱就是Java自動將原始類型值轉換成對應的對象,比如將int的變量轉換成Integer對象,這個過程叫做裝箱,反之將Integer對象轉換成int類型值,這個過程叫做拆箱。因為這裡的裝箱和拆箱是自動進行的非人為轉換,所以就稱作為自動裝箱和拆箱
自動裝箱時編譯器調用valueOf將原始類型值轉換成對象,同時自動拆箱時,編譯器通過調用類似intValue(),doubleValue()這類的方法將對象轉換成原始類型值
<code>// jdk1.5 之前的寫法
Integer tempNum1 = Integer.valueOf(5);
int num1 = tempNum1.intValue();
// jdk1.5之後的寫法
Integer tempNum2 = 5;
int num2 = tempNum2;/<code>
Integer類常量池
這個是我原來面試問到的一個問題,讓我判斷如下代碼的輸出,並解釋原因
<code>Integer a1 = 40;
Integer a2 = 40;
// true
System.out.println(a1 == a2);
Integer a3 = 200;
Integer a4 = 200;
// false
System.out.println(a3 == a4);/<code>
由自動裝箱和拆箱可以知道這2種寫法是等價的
<code>Integer a1 = 40;
Integer a1 = Integer.valueOf(40);/<code>
看一下Integer的valueOf方法
<code>public static Integer valueOf(int i) {
// i的取值範圍為[-128,127]
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}/<code>
IntegerCache是Ingeter的靜態內部類,默認創建了[-128,127]的對象,並放到IntegerCache內部的一個cache數組中,在[-128,127]這個範圍內的整數對象,不用創建。直接從IntegerCache中的cache數組中根據下標拿就可以,超出這個範圍的每次去創建新的對象。其他幾種包裝類型的常量池和Integer思路都差不多,源碼都很相似。
因此a1和a2指向的是同一個對象,a3和a4指向的是不同的對象
<code>Integer i1 = 40;
Integer i2 = 40;
Integer i3 = 0;
Integer i4 = new Integer(40);
Integer i5 = new Integer(40);
Integer i6 = new Integer(0);
// true
System.out.println(i1 == i2);
// true
System.out.println(i1 == i2 + i3);
// false
System.out.println(i1 == i4);
// false
System.out.println(i4 == i5);
// true
System.out.println(i4 == i5 + i6);
// true
System.out.println(40 == i5 + i6);/<code>
解釋:語句i4 == i5 + i6,因為+這個操作符不適用於Integer對象,首先i5和i6進行自動拆箱操作,進行數值相加,即i4 == 40。然後Integer對象無法與數值進行直接比較,所以i4自動拆箱轉為int值40,最終這條語句轉為40 == 40進行數值比較
String類和常量池
字符串常量池放在哪?
jdk1.7之前的不討論,從jdk1.7開始,字符串常量池就開始放在堆中
下面這個代碼初學者還是經常被問到的
<code>String str1 = "abc";
String str2 = "abc";
String str3 = new String("abc");
String str4 = new String("abc");
// true
System.out.println(str1 == str2);
// false
System.out.println(str1 == str3);
// false
System.out.println(str3 == str4);/<code>
內存中的結構如下
其中常量池中存的是引用,引用一下R大在知乎上的解釋
如果您說的確實是runtime constant pool(而不是interned string pool / StringTable之類的其他東西)的話,其中的引用類型常量(例如CONSTANT_String、CONSTANT_Class、CONSTANT_MethodHandle、CONSTANT_MethodType之類)都存的是引用,實際的對象還是存在Java heap上的。
解釋一下上面代碼的輸出,Java中有2種創建字符串對象的方式
<code>String str1 = "abc";
String str2 = "abc";
// true
System.out.println(str1 == str2);/<code>
採用字面值的方式創建一個字符串時,JVM首先會去字符串池中查找是否存在"abc"這個對象
如果不存在,則在字符串池中創建"abc"這個對象,然後將池中"abc"這個對象的地址賦給str1,這樣str1會指向池中"abc"這個字符串對象
如果存在,則不創建任何對象,直接將池中"abc"這個對象的地址返回,賦給str2。因為str1、str2指向同一個字符串池中的"abc"對象,所以結果為true。
<code>String str3 = new String("abc");
String str4 = new String("abc");
// false
System.out.println(str3 == str4);/<code>
採用new關鍵字新建一個字符串對象時,JVM首先在字符串池中查找有沒有"abc"這個字符串對象,
如果沒有,則首先在字符串池中創建一個"abc"字符串對象,然後再在堆中創建一個"abc"字符串對象,然後將堆中這個"abc"字符串對象的地址賦給str3
如果有,則不在池中再去創建"abc"這個對象了,直接在堆中創建一個"abc"字符串對象,然後將堆中的這個"abc"對象的地址賦給str4。這樣,str4就指向了堆中創建的這個"abc"字符串對象;
因為str3和str4指向的是不同的字符串對象,結果為false。
Java面試手冊
目錄
一、性能優化面試專欄
1.1、tomcat性能優化整理
1.2、JVM性能優化整理
1.3、Mysql性能優化整理
二、微服務架構面試專欄
2.1、SpringCloud面試整理
2.2、SpringBoot面試整理
2.3、Dubbo面試整理
三、併發編程高級面試專欄
四、開源框架面試題專欄
4.1、Spring面試整理
4.2、SpringMVC面試整理
4.3、MyBatis面試整理
五、分佈式面試專欄
5.1、分佈式限流面試整理
5.2、分佈式通訊面試整理
5.3、分佈式數據庫面試整理
有需要獲取面試體系文檔的朋友可以轉發文章並關注作者,然後私信回覆“Java面試”即可獲得以上所有面試PDF文檔資料的領取方式!以前沒獲取到的粉絲或者小夥伴們都可以獲取參考哦
如何獲取?
轉發這篇文章,關注我,私信回覆“java面試”即可獲取高清大綱,以上 spring,MyBatis,Netty源碼分析,高併發、高性能、分佈式、微服務架構的原理,JVM性能優化、分佈式架構
如何私信?
關注我後,在手機,點進頭像進我的主頁,主頁上方右上角有個私信,點擊私信,如何回覆關鍵字“java面試”即可
閱讀更多 java互聯網高級架構 的文章