Java基礎(chǔ)系列之?dāng)?shù)據(jù)結(jié)構(gòu)和運(yùn)算

導(dǎo)語

初遇Java之后,我們對Java的歷史、特征以及垃圾回收機(jī)制有了初步認(rèn)識,接下來我們需要了解下數(shù)據(jù)結(jié)構(gòu)和運(yùn)算。程序是什么?就是幫人類干事情的工具(程序員就是開發(fā)這個(gè)工具的人?。3绦虻慕M成是什么呢?我的理解是數(shù)據(jù)結(jié)構(gòu)和算法,這里我們不談算法那么高大上的東西只談基本運(yùn)算,畢竟單講算法就能出一本很厚很厚的書了。

數(shù)據(jù)結(jié)構(gòu)


前面我們說到,Java是一門面向?qū)ο蟮恼Z言。有人說過這么一句話,“萬物皆對象”,其實(shí)這也不絕對。Java有8種基本數(shù)據(jù)類型便不算對象,我們先說說這八種基本數(shù)據(jù)類型吧。

  • 整型
    • byte 在內(nèi)存中占8位,取值-128 ~127 即 (-2^7) ~ (2^7-1)
    • short 在內(nèi)存中占16位,取值 -32768~32767 即(-2^{15})~ (2^{15}-1)
    • int 在內(nèi)存中占32位,取值(-2^{31})~ (2^{31}-1)
    • long 在內(nèi)存中占64位,取值(-2^{63})~ (2^{63}-1)
  • 浮點(diǎn)型
    • float 32位單精度浮點(diǎn)數(shù),最多存儲(chǔ)4個(gè)字節(jié)
    • double 64位雙精度浮點(diǎn)數(shù),最多存儲(chǔ)8個(gè)字節(jié)
  • 字符型 char
  • 布爾型 boolean 取值true,false
    說完基本數(shù)據(jù)類型,我們再談?wù)勔粋€(gè)新概念,直接量。直接量通常有三種類型:基本類型、字符串類型、null。基本類型我們剛才已經(jīng)說過了就是上面的八位,那么字符串類型呢?就是java.lang.String這個(gè)類的類型,這是個(gè)特殊的類,用來存儲(chǔ)字符串。
    String類型的特點(diǎn):字符串直接量不能賦值給其他類型如int,long。這和null類型恰恰相反,null類型可以直接復(fù)制任何引用類型和其他基本類型(除了boolean類型)。關(guān)于String字符串還有一點(diǎn)需要指出,當(dāng)程序第一次使用時(shí),JVM會(huì)使用常量池來維護(hù)這個(gè)直接量。當(dāng)再次使用的時(shí)候,Java會(huì)直接取常量池中的字符串。
  • 引用數(shù)據(jù)類型 Date date = new Date(),其中date就是個(gè)引用數(shù)據(jù)類型,這個(gè)會(huì)在后續(xù)的面向?qū)ο笳鹿?jié)進(jìn)行詳細(xì)介紹。

運(yùn)算


什么是運(yùn)算?小學(xué)生都知道的加、減、乘、除?沒錯(cuò)!就是這些,然而,程序執(zhí)行加、減、乘、除卻是一件很費(fèi)勁的事情,這就引出了我們的位運(yùn)算。
我們知道,計(jì)算機(jī)數(shù)字系統(tǒng)是由0和1組成的,即二進(jìn)制。非0即1,逢2進(jìn)1,因此處理位運(yùn)算效率會(huì)更高。
目前Java支持的位運(yùn)算有7個(gè),它們分別是:與、或、非、異或、左移、右移、無符號右移。

  1. 按位與(&)
    System.out.print(5 & 9),我的得到的結(jié)果是1.具體運(yùn)算如下
    \frac{0101}{1001}=0001
    運(yùn)算規(guī)則是同1為1,非1為0
    這里有一個(gè)經(jīng)典算法~求一個(gè)整數(shù)的奇偶:
//我們知道,偶數(shù)是能被2整除,那么它的二進(jìn)制表示最后一位一定是0
//根據(jù)與運(yùn)算,同1為1,非1為0,那么一個(gè)一個(gè)二進(jìn)制數(shù)和1進(jìn)行與運(yùn)算就能獲得最后一位值了
int i;
return (i & 1)==0;//偶數(shù)
  1. 按位或(|)
    System.out.print(5 | 9) 運(yùn)算結(jié)果是13,具體運(yùn)算如下
    \frac{0101}{1001}=1101
    運(yùn)算規(guī)則:有1為1,無1為0
  2. 非(~)
    System.out.print(~5) 運(yùn)算結(jié)果是:-6
5的原碼:0000 0101
~5 就是:1111 1010
由于這是一個(gè)負(fù)數(shù),這顯示的是它的補(bǔ)碼,我們先求得它的反碼,即補(bǔ)碼-1那就是:
1111 1001
然后翻轉(zhuǎn)過來,高位取1即:
1000 0110
得到結(jié)果是-6

這里有幾個(gè)概念:原碼、反碼、補(bǔ)碼。
正數(shù)的原碼、補(bǔ)碼、反碼相同
負(fù)數(shù)的反碼是其的絕對值按位求反
負(fù)數(shù)的補(bǔ)碼等于其反碼的尾數(shù)加1

  1. 按位異或(^)
    System.out.print(5 ^ 9) 運(yùn)算結(jié)果是12,具體運(yùn)算如下
    \frac{0101}{1001}=1100
    運(yùn)算規(guī)則:同假異真,就是說相同為0,不同為1
  2. 左移(<<)
    System.out.print(4<<1) 運(yùn)算結(jié)果是8,運(yùn)算如下
4: 0100
8: 1000 (0100左移一位后得到01000,舍棄高位的0即1000) 
  1. 右移(>>)
    System.out.print(4>>1)運(yùn)算結(jié)果是2
4: 0100
2: 0010 (0100右移一位后得到0010,舍棄高位的0即10) 
  1. 無符號右移(>>>)
    System.out.print(-5>>>2)結(jié)果是1073741822
-5的原碼: 1000 0101
-5的補(bǔ)碼: 1111 1011 
無符號右移后:0011...1111 1110(中間省略掉20個(gè)1)
這個(gè)應(yīng)該是2的30次方減去2就是我們的運(yùn)算結(jié)果

這個(gè)計(jì)算的原因是-5默認(rèn)為int類型32位,有興趣可以試試-5L(long類型)無符號右移2,會(huì)得到一個(gè)更大的值。

這些運(yùn)算是計(jì)算機(jī)底層的運(yùn)算,比如說程序5*2,我們寫成5<<1效率會(huì)更高些。
除了位運(yùn)算,我們比較常用的還有如=(賦值)運(yùn)算,比較運(yùn)算(>,<,==,!=),邏輯運(yùn)算(&&,|,!,^),三目運(yùn)算(? :)等。

Java也提供了加減乘除的算術(shù)運(yùn)算,但是需要注意的是對于double和float浮點(diǎn)型運(yùn)算時(shí)可能會(huì)失去精度,如1.2+1.9,得到的結(jié)果是3.0999999999999996,不必驚訝,不止Java,我們在谷歌的console中執(zhí)行(JavaScript),也是得到了這個(gè)結(jié)果。很多中語言都存在這個(gè)問題。為了更為精確的計(jì)算,Java提供了一個(gè)java.math包,專門作為算術(shù)計(jì)算的。上面這個(gè)我們換成BigDecimal.valueOf(1.2).add(BigDecimal.valueOf(1.9))就沒有這個(gè)問題了。需要注意的是,這里不要使用BigDecimal的double類型構(gòu)造器創(chuàng)建對象,否則還是會(huì)出現(xiàn)精度問題

再談String


String對象可謂是面試官的寵兒,各種題目層出不窮,我們這里談?wù)凷tring對象。首先我們看下String對象在jdk源碼的樣子


image.png

由于篇幅有限,我們的圖上只能看到類的修飾信息,看不到Java開發(fā)人員寫的注釋,我這里可以把一些關(guān)鍵信息貼出來下。

The  class represents character strings. 
All string literals in Java programs, such as  "abc", are
 implemented as instances of this class.
Strings are constant; their values cannot be changed after they
are created. String buffers support mutable strings.
Because String objects are immutable they can be shared.

不難看出,String是一個(gè)字符串對象,代表所有的字符串實(shí)例,創(chuàng)建后不能修改,所以可以共享。上面我們也說過,String對象賦值后,JVM會(huì)將該字符串緩存到常量池中,當(dāng)下次使用的時(shí)候,直接從常量池獲取。

String a = "helloworld";
String b = "hello"+"world";
String c = new String("helloworld");

這里面a==b,但是a!=c(這個(gè)比較容易理解,a為字符串直接量,c為字符串對象的地址)。我們將以上代碼編譯,打開class文件,發(fā)現(xiàn)結(jié)果是

image.png

我們可以這樣理解,當(dāng)編譯器能確定字符串的時(shí)候,系統(tǒng)不會(huì)產(chǎn)生helloworld的備份,直接產(chǎn)生helloworld,如果我們把hello放在一個(gè)變量e里面,然后將b改成String b = e+"world"; 這個(gè)時(shí)候編譯結(jié)果就不一樣了,a==b返回的就是false了。

問題升級

        String str2 = new String("str")+new String("01");
        str2.intern();
        String str1 = "str01";
        System.out.println(str2==str1);

首先我們了解下intern()方法,看源碼解釋:

Returns a canonical representation for the string object.
A pool of strings, initially empty, is maintained privately by the class
When the intern method is invoked,
 if the pool already contains a string equal to this {@code String} object as determined by the {@link #equals(Object)} method, then the string from the pool is returned. Otherwise, this {@code String} object is added to the pool and a reference to this {@code String} object is returned.

這個(gè)方法返回的是字符串的規(guī)范化形式(其實(shí)就是直接量),根據(jù)equals方法判斷常量池中是否存在這個(gè)字符串,如果存在則,返回對象的引用,不存在,先將字符串緩存到常量池中然后返回該對象的引用。
jdk1.6中常量池在永久代,如果字符串在常量池中找不到會(huì)將字符串拷貝到常量池中。所以str2指向的應(yīng)該還是原字符串,常量池中的是拷貝。str1指向的是常量池中的拷貝,所以返回false。
jdk1.7以上版本,(jdk1.8干脆常量池移到了堆中),如果字符串在常量池中找不到不再拷貝到常量池中,而會(huì)重新生成一個(gè)對原字符串的引用,因此,str2指向的就是原字符串,而str1指向的也是原字符串,因此,返回true。

尾聲

本章節(jié),我們了解了Java的基本數(shù)據(jù)類型和引用類型(一筆帶過,面向?qū)ο蟮臅r(shí)候著重提及)和一些常用的運(yùn)算。要知道,早期的計(jì)算機(jī)功能單一,只有計(jì)算功能,因此運(yùn)算是最為核心的一塊功能(雖然國內(nèi)Java開發(fā)人員對于位運(yùn)算并不是很熟練)。后面我們又提及了String類,分析了String類的特殊性,和常見的一些面試題,這些面試題其實(shí)都是基于Java內(nèi)存模型的,需要多JVM較為深入才能理解,而不是記下答案就能應(yīng)付過去的。下一個(gè)章節(jié)是面向?qū)ο?,那個(gè)時(shí)候我們根據(jù)面向?qū)ο蟮乃枷朐僬剶?shù)據(jù)結(jié)構(gòu),再見。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容