1. 程式人生 > >Java常量池理解和經典總結

Java常量池理解和經典總結

一.相關知識

1. 什麼是常量。

第一種:是一個值,這個值本身,我們就叫做常量。

整型常量:1024

實型常量:1.024

字元常量:'g' 'c' 'w'

字串常量:"gcw"

邏輯常量:true false

這只是我們平時我們的一個說法而已,比如數字1024,我們說它是一個int型別的常量。

第二種:不可變的變數,我們也稱為常量。就是被我們都知道的關鍵字final修飾的變數,final修飾的變數,只要一經賦值,就不可以改變。可能它本身是個變數,但是被final修飾,我們可以認為是個常量。比如:

final int i=1024;

2.常量池

常量池分為兩種:靜態常量池和執行時常量池。

靜態常量池也就是Class檔案中的常量池,我們這裡舉一個簡單的例子,下面是一個HelloWorld的與原始檔和Class檔案。

原始檔:

1
 2
 3
 4
 5
public class HelloWorld{public static void main(String args[]){System.out.println("hello world");}}

Class檔案:

我們一個一個的來分析。

1>    魔數

魔數就是這個檔案的前四個位元組:ca fe ba be(漱壕).它的唯一作用是確定這個屋檔案是否可以被JVM接受。很多檔案儲存標準中都使用魔術來進行身份識別。

2>    版本號

第5和第6個位元組是次版本號,第7個和第8 個是主版本號。這裡的第7和第8位是0034,即:0x0034。0x0034轉為10進位制是52。Java的版本是從45開始的然而從1.0 到1.1 是45.0到45.3, 之後就是1.2 對應46, 1.3 對應47 … 1.6 對應50,我這裡是1.6.0_24對應的是52,就是0x0034;

3>    常量池的入口

由於常量池中的常量的數量不是固定的,所以常量池的入口需要放置一項u2型別的資料,代表常量池的容量計數值。這裡的常量池容量計數值是從1開始的。如圖常量池的容量:0x001d(29)。所以共有29個常量。

4>    常量池

常量池中主要存放兩類常量:字面量和符號引用。字面量比較接近Java語言層面的常量概念。就是我們什麼提到的常量。而符號引用則屬於編譯原理的方面的概念。包括以下三類常量:

     i> 類和介面的全限定名

    ii>欄位的名稱和描述符

   iii>方法的名稱和描述符

(關於Class檔案就先講到這裡,以後有機會再繼續)

執行時常量池:執行時常量池是方法區的一部分。Class檔案中除了有類的版本、欄位、方法、介面等描述資訊外還有一項資訊是常量池。

用於存放編譯期生成的字面量和符號應用,這部分內容將在類載入後進入方法區的時候存到執行時常量池中。執行時常量池還有個更重要的的特徵:動態性。Java要求,編譯期的常量池的內容可以進入執行時常量池,執行時產生的常量也可以放入池中。常用的是String類的intern()方法。

3.常量池的好處

常量池是為了避免頻繁的建立和銷燬物件而影響系統性能,其實現了物件的共享。例如字串常量池,在編譯階段就把所有的字串文字放到一個常量池中。(1)節省記憶體空間:常量池中所有相同的字串常量被合併,只佔用一個空間。(2)節省執行時間:比較字串時,==比equals()快。對於兩個引用變數,只用==判斷引用是否相等,也就可以判斷實際值是否相等。(摘自部落格:http://www.jianshu.com/p/c7f47de2ee80  非常感謝此作者的文章)

4.equals和==的區別

 Java中的資料型別分兩種:基本資料型別和引用資料型別。

1> 基本資料型別 byte short int long char float double boolean

對於這些都是用的==來比較兩者的值是不是相等。

2> 引用資料型別

一般情況下,equals和==是一樣的都是比較的兩者的地址值是不是一樣。但是有特殊的情況:我們都知道我們使用的類都是繼承自Object基類,Object中equals方法中是使用==來實現的,即比較的是兩者的地址值。但是Object的子類可以重寫equals方法,比如Date、String、Integer等類都是重寫了equals都是重寫了,比較的是值是否相等。例如String類的原始碼:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
public boolean equals(Object anObject) {if (this == anObject) {return true;}if (anObject instanceof String) {String anotherString = (String)anObject;int n = count;if (n == anotherString.count) {char v1[] = value;char v2[] = anotherString.value;int i = offset;int j = anotherString.offset;while (n-- != 0) {if (v1[i++] != v2[j++])return false;}return true;}}return false;}

先比較是不是指向同一個地址,如果不是再比較兩者是不是值相等。這個時候equals和==就不一樣了。

二.8種基本資料型別

1.對於8種基本資料型別大部分都有自己的封裝類,其中Byte,Short,Integer,Long,Character,Boolean都實現了常量池技術。

1>不使用new關鍵字

Boolean這個就true和false,這個很容易理解。咱們這裡可以以Integer為例進行講解,仔細檢視Integer原始碼你會發現,裡面有個靜態類IntegerCache。程式碼如下:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
private static class IntegerCache {static final int high;static final Integer cache[];static {final int low = -128;// high value may be configured by propertyint h = 127;if (integerCacheHighPropValue != null) {// Use Long.decode here to avoid invoking methods that// require Integer's autoboxing cache to be initializedint i = Long.decode(integerCacheHighPropValue).intValue();i = Math.max(i, 127);// Maximum array size is Integer.MAX_VALUEh = Math.min(i, Integer.MAX_VALUE - -low);}high = h;cache = new Integer[(high - low) + 1];int j = low;for(int k = 0; k < cache.length; k++)cache[k] = new Integer(j++);}private IntegerCache() {}}

它是對它進行了快取,範圍是[-128,127],只要是這個範圍內的數字都會快取到這個裡面,做成常量池進行管理。我們來看一個例項:

 1
 2
 3
 4
 5
 6
 7
public class test {public static void main(String[] args) {Integer i1=10;Integer i2=10;System.out.println(i1==i2);}}

結果:true

第一次把i1的值快取進去了,當你建立i2的時候,它其實是指向了第一次快取進去的那個10,所以i1和i2指向了同一個地址。

在這裡不得不再提一下基本資料型別和封裝類之間的自動裝箱和自動拆箱。這裡還是以Integer類舉例:

 1
 2
 3
 4
 5
 6
 7
public class test {public static void main(String[] args) {int i1=10;Integer i2=10;// 1.自動裝箱System.out.println(i1==i2);//2.自動拆箱}}

結果:true

對於自動裝箱,我們可以看看原始碼:

 1
 2
 3
 4
 5
 6
 7
public static Integer valueOf(int i) {final int offset = 128;if (i >= -128 && i <= 127) { // must cachereturn IntegerCache.cache[i + offset];}return new Integer(i);}

對應於我們寫的程式碼就是Integer i2=10;其實在內部進行實現的時候是Integer i2=Integer.valueOf(10);把int型別的10封裝成Integer型別的10。當我們比較i1和i2的時候,Integer型別的10又會自己拆箱成int型別的10進行比較。

2>.使用new關鍵字,如果使用了new關鍵字就是在堆記憶體中開闢了一塊記憶體。每次new一個都是在堆中開闢一塊記憶體,我們可以這樣理解,就像我們自己吃蘋果,每次都是new一個,我要吃個新的,不要舊的。所以每一個的地址都不一樣。

例如:

 1
 2
 3
 4
 5
 6
 7
public class test {public static void main(String[] args) {Integer i1=new Integer(10);Integer i2=new Integer(10);System.out.println(i1==i2);}}

結果:false

3.     沒有實現常量池的Float和Double

1
  2
  3
  4
  5
  6
  7
  8
  9
 10
public class test {public static void main(String[] args) {Float f1=10.0f;Float f2=10.0f;System.out.println(f1==f2);Double d1=12.0;Double d2=12.0;System.out.println(d1==d2);}}

結果:

false

false

讓我們來看一個大的例子:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
public class test {public static void main(String[] args) {Integer i1=10;Integer i2=10;Integer i3=20;Integer i11=new Integer(10);Integer i22=new Integer(10);Integer i33=new Integer(20);System.out.println(i1==i2);System.out.println(i1==i11);System.out.println(i11==i22);System.out.println(i3==(i1+i2));System.out.println(i3==(i11+i22));System.out.println(i33==(i1+i2));System.out.println(i33==(i11+i22));}}

結果:

true

false

false

true

true

true

true

三、  String類

1> 沒有使用new關鍵字

 1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
public class test {public static void main(String[] args) {String s1="abc1";//此處是數字1String s2="abc"+1;System.out.println(s1==s2);// 第一次比較String s3="ab";String s4="c";String s5="abc";String s6=s3+s4;System.out.println(s5==s6);// 第二次比較}}

結果:

true

false

解釋:第一次比較的那裡,因為字串abc和數字1都是字面量,所以加起來還是個字面量,又因為常量池中已經有了s1指向的字面量abc1,所以s2也是指向了字面量abc1。第二次比較那裡,這時候的+兩面是物件,其實是這樣的,對於String s6=s3+s4;

其實執行時是這樣的String s6=new StringBuilder().append(s3).append(s4).toString();這裡的過程是通過StringBuilder這個類實現的,我們來看一下StringBuilder類中的toString()的原始碼:

 1
 2
 3
 4
public String toString() {// Create a copy, don't share the arrayreturn new String(value, 0, count);}

它是通過new String()的方式來作為值進行返回的,所以是在堆中開闢的一塊空間。所以和常量池中的不一樣。結果是false。

特例1:

 1
 2
 3
 4
 5
 6
 7
 8
 9
public class test {public static final String s1="abc";public static final String s2="def";public static void main(String[] args) {String s3=s1+s2;String s4="abcdef";System.out.println(s3==s4);}}

結果:

true

解釋:因為s1和s2都是final型別的且在編譯階段都是已經複製了,所以相當於一個常量,當執行Strings3=s1+s2;的時候,s3已經是字串abcdef了,所以相等。

特例2:

1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
public class test {public static final String s1;public static final String s2;static{s1="abc";s2="def";}public static void main(String[] args) {String s3=s1+s2;String s4="abcdef";System.out.println(s3==s4);}}

結果:

false

解釋:雖然s1和s2都是final型別的但是一開始沒有初始化,在編譯期還不可以知道具體的值,還是變數,所以什麼時候賦值,賦什麼值都是個變數。所以是false。

2> 使用new關鍵字

這裡就很簡單了,使用new關鍵字當然是每次都是新建一個,分配自己的空間。

 1
 2
 3
 4
 5
 6
 7
 8
public class test {public static void main(String[] args) {String s1=new String("abc");String s2=new String("abc");System.out.println(s1==s2);}}

結果:

false

*這裡還有個重點就是String s1=newString("abc");到底建立了幾個物件呢?

這裡呢,我們分兩個階段來看。

i> 類載入時,對於一個類,類載入只會進行一次。此類進行載入時,會把字串abc放進全域性的常量池中,進行儲存。

ii> 執行時,當你執行程式的時候,常量池中存在字串abc,於是把字面量abc拿進heap中,使它的引用交給s1。

因此這條語句建立了兩個物件。

3> 動態新增

我們前面說過,執行時常量池具有動態性特點。我們可以向裡面放東西,典型的就是String類的intern()方法。其實intern()方法是這樣工作的。如果常量池中存在這個物件直接返回該物件的引用,如果沒有我就放進去,再返回該物件的引用。結合new關鍵字的知識,我們舉個例子:

 1
 2
 3
 4
 5
 6
 7
 8
public class test {public static void main(String[] args) {String s1=new String("abc");String s2=s1.intern();String s3="abc";System.out.println(s2==s3);}}

結果:

true

解釋:第一次建立s1的時候,建立了兩個物件常量池中一個,heap中一個,比較s2和s3時,由於s2是由s1的intern()方法得來的,s2是指向了常量池中的物件,而建立s3時,發現常量池中有這個物件,她就不建立了,直接指向常量池中的那個物件abc了,所以是兩者是指向了一個物件。

我們來看一個例子:

1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
public class test {private static final String s11="hello";private static final String s22="world";public static void main(String[] args) {String s1="hello";String s2="world";String s3="helloworld";String s4=s11+s22;String s5=s1+"world";String s6=s5.intern();String s7="hello"+new String("world");System.out.println(s3==s4);System.out.println(s3==s5);System.out.println(s4==s5);System.out.println(s4==s6);System.out.println(s3==s7);System.out.println(s4==s7);System.out.println(s5==s7);}}

結果:

true

false

false

true

false

false

false

相關推薦

Java常量理解經典總結

一.相關知識1. 什麼是常量。第一種:是一個值,這個值本身,我們就叫做常量。整型常量:1024實型常量:1.024字元常量:'g' 'c' 'w'字串常量:"gcw"邏輯常量:true false這只是

Java常量理解總結

final java 地址 表達式 語句 www 表示 new 基礎 一.相關概念 什麽是常量用final修飾的成員變量表示常量,值一旦給定就無法改變!final修飾的變量有三種:靜態變量、實例變量和局部變量,分別表示三種類型的常量。 Class文件中的常

多執行緒:執行緒理解使用總結

建立和銷燬執行緒非常損耗效能,那有沒有可能複用一些已經被建立好的執行緒呢?答案是肯定的,那就是執行緒池。 另外,執行緒的建立需要開闢虛擬機器棧、本地方法棧、程式計數器等執行緒私有的記憶體空間,線上程銷燬時需要回收這些系統資源,頻繁地建立銷燬執行緒會浪費大量資源,而通過複用已有執行緒可以更好地管理和協調執行緒的

Java常量你怎麼理解,看我如何總結

一.相關概念 什麼是常量 用final修飾的成員變量表示常量,值一旦給定就無法改變! final修飾的變數有三種:靜態變

Java常量的大概理解

     java常量池是一個經久不衰的話題,也是面試官的最愛,題目花樣百出,小菜早就對常量池有所耳聞,這次好好總結一下。 理論      小菜先拙劣的表達一下jvm虛擬記憶體分佈:      程式計數器是jvm執行程式的流水線,存放一些跳轉指令,這個太高深,小菜不懂。      本地方法棧是jv

深入理解java常量

原文連結 理論 小菜先拙劣的表達一下jvm虛擬記憶體分佈: 程式計數器是jvm執行程式的流水線,存放一些跳轉指令,這個太高深,小菜不懂。 本地方法棧是jvm呼叫作業系統方法所使用的棧。 虛擬機器棧是jvm執行java程式碼所使用的棧。 方法區

Java的字串常量理解String的intern()

# 前言 逛知乎遇到一個剛學Java就會接觸的字串比較問題: ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20201005200445906.png#pic_center) 通常,根據"**==比較的是地址,equals比較的是值**"介個定理就能得到結果。但是Strin

Java常量詳解

回收 array 數值 編譯期 二進制格式 new 保持 占用 get 轉自:http://www.cnblogs.com/iyangyuan/p/4631696.html jvm虛擬內存分布圖: 程序計數器:JVM執行程序的流水線。 本地方法棧:JVM調用操作系統方法所

java 常量詳解

例如 ava color str blog 註意 如果 .cn 運行 參考資料:http://www.cnblogs.com/holos/p/6603379.html 註意: 1.首先,我們平時在討論字符串新建問題時所說的常量池其實指的是全局字符串常量池。並不是運行時常量池

深入淺出java常量

源文件 -c == val 數量 ava else java字符串 移除 理論 jvm虛擬內存分布: 程序計數器是jvm執行程序的流水線,存放一些跳轉指令。 本地方法棧是jvm調用操作系統方法所使用的棧。 虛

Java 常量

之間 等於 con 分配 計算 prope 解析 base null 最近在網上看到一些Android的面試題,關於String和Integer常量池的,就總結了一下,暫時先記錄下來,以後說不定能用到 1 public class JavaBase { 2

Java byte資料轉換處理總結

一.byte和int相互轉換的方法 java程式或Android程式的socket資料傳輸,都是通過byte陣列,但是int型別是4個byte組成的,如何把一個整形int轉換成byte陣列,同時如何把一個長度為4的byte陣列轉換為int型別。 /** * int到byte[]

IIS7應用程式整合經典的區別 IIS7應用程式整合經典的區別

IIS7應用程式池整合和經典的區別   IIS7應用程式池整合和經典的區別    IIS7應用程式池有整合和經典兩種模式,根據微軟官方的介紹, 整合模式,如果託管應用程式在採用整合模式的應用程式池中執行,伺服器將使用 II

Java程式設計學習技巧方法總結

乾貨:必須要有反饋,不斷調整,多讀書,多些筆記。 解釋:不練習你以為你能掌握?笑話,只有自己根據一個個小目標不斷的敲,執行,給予你反饋,這樣才會真的進步。 紙上談Java,是永遠停止在口。   關於筆記:我其實一直有些筆記的習慣,高中到現在15年了;心情不好,寫寫筆記,學到重點

java常量細節

public class IntegerTest { public static void main(String[] args) { objPoolTest(); } public static void objPoolTest() {

java常量概念,String,Integer等包裝類對常量的應用

2016年06月27日 15:47:59 V核桃V 閱讀數:2794更多 在class檔案中,“常量池”是最複雜也最值得關注的內容。   Java是一種動態連線的語言,常量池的作用非常重要,常量池中除了包含程式碼中所定義的各種基本型別(如int、long等等)和物

Java--泛型理解使用 (List<String> list = new ArrayList<String>(); )

List<String> list = new ArrayList<String>(); 第一次看到這行程式碼是一頭霧水,查了好久才弄清楚這是什麼東西,怎麼用,所以記錄下來,方便以後查閱。   首先看一段程式碼 public class Gener

資料庫連線理解對比

資料庫連線池的實現及原理:https://www.cnblogs.com/wym789/p/6374440.html 使用jdbc-pool替代dbcp、c3p0等連線池的原因: https://blog.csdn.net/u011267070/article/details/5218593

Java垃圾回收機制【經典總結

Java垃圾回收機制 綜合了若干人的blog~ 1. 垃圾回收的意義  在C++中,物件所佔的記憶體在程式結束執行之前一直被佔用,在明確釋放之前不能分配給其它物件;而在Java中,當沒有物件引用指向原先分配給某個物件的記憶體時,該記憶體便成為垃圾。JVM的一個系統

Java常量的申明使用

常量就是從程式開始執行到結束都不變的量。在 Java 程式設計中,使用關鍵字“final”來宣告一個常量,例如下面的程式程式碼。///這裡的 x 是一個常量,但是是在某個方法內的常量,也可以稱為成員常量(作者給它取的名字)public class var{    publi