上次提前說(shuō)了java中的面向?qū)ο笈ⅲ饕菫榱耸褂眠@些常見(jiàn)類做打算,畢竟Java中一切都是對(duì)象舌界,要使用一些系統(tǒng)提供的功能必須得通過(guò)類對(duì)象調(diào)用方法综芥。其實(shí)Java相比于C來(lái)說(shuō)強(qiáng)大的另一個(gè)原因是Java中提供了大量可用的標(biāo)準(zhǔn)庫(kù)
字符串
字符串可以說(shuō)是任何程序都離不開(kāi)的東西,就連一個(gè)簡(jiǎn)單的hello world程序都用到了字符串反砌,當(dāng)時(shí)C語(yǔ)言中對(duì)字符串的支持并不太好雾鬼,C語(yǔ)言中的字符串實(shí)質(zhì)上是一個(gè)字符數(shù)組。為了方便不同的C/C++庫(kù)都有自己的字符串實(shí)現(xiàn)宴树。而Java中內(nèi)置了對(duì)字符串的支持策菜,Java中的字符串是一個(gè)叫做String的對(duì)象。
根據(jù)jdk文檔的描述酒贬,我們需要注意一下幾點(diǎn):
- Java程序中的所有字符串文字(例如"abc" )都被實(shí)現(xiàn)為此類的實(shí)例
- String對(duì)象是可以共享的
- String對(duì)象是不可變的
字符串的內(nèi)存分布
一般把類似于 "abc" 這樣直接通過(guò)字面值表示的字符串作為字面常量又憨,這種在Java中也是一個(gè)字符串,只是它與普通的new出來(lái)的字符串在內(nèi)存的存儲(chǔ)上有點(diǎn)不一樣锭吨,下面請(qǐng)看下面的代碼
class StringDemo{
public static void main(String[] args){
String a = "abc";
String b = "abc";
String c = new String("abc");
System.out.println(a == b);
System.out.println(a == c);
System.out.println(b == c);
}
}
針對(duì)字符串來(lái)說(shuō) == 比較的是它們的地址是否相同蠢莺,這個(gè)程序分別輸出的是 true、false零如、false躏将,也就是說(shuō)a b 是指向的同一個(gè)地址空間,而c則不是考蕾。它們的內(nèi)存分布如下:
一般程序在加載到內(nèi)存地址空間后祸憋,會(huì)被劃分為4個(gè)部分,全局?jǐn)?shù)據(jù)段肖卧、代碼段蚯窥、堆、棧。而全局代碼段是用來(lái)存放全局變量的拦赠。在C中如果我們寫(xiě)下這樣的代碼:
char* psz1 = "abc";
char* psz2 = "abc";
那么在程序加載到內(nèi)存中時(shí)巍沙,在全局?jǐn)?shù)據(jù)段中會(huì)存在一個(gè)連續(xù)的內(nèi)存空間保存的是 'a','b','c','\0' 這4個(gè)值,一旦有char型指針指向"abc" 這樣的字符串矛紫,那么系統(tǒng)會(huì)自動(dòng)將這段內(nèi)存的地址給賦值到對(duì)應(yīng)的指針變量中赎瞎,而且這個(gè)內(nèi)存是只讀內(nèi)存,如果嘗試往里面寫(xiě)入數(shù)據(jù)颊咬,則會(huì)造成程序崩潰务甥。
Java中也是類似的,當(dāng)出現(xiàn) "abc" 的時(shí)候喳篇,其實(shí)系統(tǒng)早就為它在堆中創(chuàng)建了一個(gè)String對(duì)象敞临,如果去閱讀String的源碼就會(huì)發(fā)現(xiàn)String中負(fù)責(zé)保存字符串的是一個(gè) byte型的數(shù)組,所以在初始化的時(shí)候會(huì)再創(chuàng)建一個(gè)byte型數(shù)組麸澜,然后由字符串中成員變量保存它的地址挺尿,所以在內(nèi)存圖中看到有String也有byte[]。而且這個(gè)字符串是保存在堆中的常量字符串池中的炊邦,它的生命周期與程序相同(或者說(shuō)與主線程相同)编矾。
每當(dāng)直接使用 "abc" 這樣的字面常量的時(shí)候會(huì)自動(dòng)將常量字符串池中相關(guān)的字符串對(duì)象的指針賦值給對(duì)應(yīng)的對(duì)象。這樣造成了上述程序中 a == b 為true的情況馁害。而c是通過(guò)new關(guān)鍵字在程序運(yùn)行期間動(dòng)態(tài)創(chuàng)建的窄俏。所以JVM會(huì)在程序執(zhí)行到這步的時(shí)候額外創(chuàng)建一個(gè)對(duì)象,并將 "abc" 這個(gè)字符串對(duì)應(yīng)的byte[] 中的值拷貝到新的內(nèi)存中碘菜。
這樣就很容易理解上面的前兩條了凹蜈,至于字符串不可變,可以參考我之前寫(xiě)的關(guān)于類型中的說(shuō)明(字符串的值發(fā)生改變時(shí)忍啸,在內(nèi)存中其實(shí)是開(kāi)辟了一塊新的內(nèi)存用于保存新的字符串內(nèi)容仰坦,而丟棄了從前的字符串)
常見(jiàn)字符串方法
這里再簡(jiǎn)單的列舉一下字符串中常見(jiàn)的方法,這些方法都可以在JDK文檔都可以查到。
String(); //初始化新創(chuàng)建的 String對(duì)象计雌,使其表示空字符序列
String(byte[] bytes); //通過(guò)使用平臺(tái)的默認(rèn)字符集解碼指定的字節(jié)數(shù)組來(lái)構(gòu)造新的 String
String(byte[] bytes, int offset, int length); //從bytes[] 數(shù)組中的第offset 位置開(kāi)始悄晃,截取length個(gè)成員來(lái)初始化一個(gè)String
char charAt(int index); //返回指定位置處的索引
int compareTo(String anotherString); // 按字典順序比較兩個(gè)字符串的大小,為0表示兩個(gè)字符串相同
int compareToIgnoreCase(String str); //比較兩個(gè)字符串的大小凿滤,忽略大小寫(xiě)
String concat(String str) ; //字符串拼接
byte[] getBytes(Charset charset); //將字符串轉(zhuǎn)化為byte型數(shù)組传泊,并返回新的byte數(shù)組
int indexOf(String str); //返回字串第一次出現(xiàn)的位置
int length() ; //返回字符串的長(zhǎng)度
String[] split(String regex); //按正則表達(dá)式進(jìn)行分割,并返回對(duì)應(yīng)的字符串?dāng)?shù)組
注意一下鸭巴,這里返回?cái)?shù)組或者新字符串的,都是在函數(shù)內(nèi)部新建的拦盹,與原來(lái)的無(wú)關(guān)鹃祖。所以這里是沒(méi)辦法拿到字符串底層的數(shù)組對(duì)象再來(lái)修改內(nèi)存值的。
數(shù)組
java中數(shù)組的定義如下:
int[] Array1 = new int[10]; //定義了一個(gè)擁有10個(gè)整型數(shù)據(jù)的數(shù)組
int[] Array2 = new int[]{1, 2, 3, 4, 5, 6, 7,8, 9, 0}; //創(chuàng)建數(shù)組并初始化
int[] Array3 = {1,2 ,3,4,5,6,7,8,9,0};
相比于C中數(shù)組的定義來(lái)說(shuō)普舆,Java中的定義更容易讓人理解恬口,對(duì)應(yīng)數(shù)據(jù)類型后面加一對(duì) [] 就是對(duì)應(yīng)的數(shù)組類型了校读。而C中,中括號(hào)是寫(xiě)在變量后面的祖能,相比于Java中的定義來(lái)說(shuō)就顯的有點(diǎn)怪異了歉秫。或者說(shuō)C中從根本上來(lái)說(shuō)數(shù)組并不算是一種特別的數(shù)據(jù)類型养铸,僅僅只是開(kāi)辟相同數(shù)據(jù)類型的一塊連續(xù)的內(nèi)存而已雁芙。至于[] 在C中應(yīng)該只是表示尋址而已,畢竟匯編中我們經(jīng)吵看到類似于 esp:[eax]
這樣的東西兔甘。
Java中的數(shù)組是一種單獨(dú)的數(shù)據(jù)類型,它是一種引用類型鳞滨,也就是說(shuō)它的變量名中保存的是它的地址洞焙。
它的使用十分的簡(jiǎn)單,與C/C++中數(shù)組的使用基本相同拯啦,注意事項(xiàng)也是基本相同澡匪。但是有一點(diǎn)很重要的不同,Java中的數(shù)組允許動(dòng)態(tài)指定長(zhǎng)度褒链,也就是通過(guò)變量來(lái)指定長(zhǎng)度唁情,而C中必須靜態(tài)的指定長(zhǎng)度,也就是在程序運(yùn)行之前就需要知道它的長(zhǎng)度碱蒙。這是因?yàn)镴ava中數(shù)組是引用類型荠瘪,是new在堆上的,而C中數(shù)組是分配在全局變量區(qū)或者棧上的赛惩,在程序運(yùn)行之初就需要為數(shù)組分配內(nèi)存哀墓。
//這樣的代碼是可以編譯通過(guò)的
int length = 10;
int array[] = new int[length];
數(shù)組作為函數(shù)參數(shù)
import java.util.Arrays;
class Demo{
public static void main(String[] args){
int length = 10;
int array[] = new int[length];
System.out.println(Arrays.toString(array));
test(array);
System.out.println(Arrays.toString(array));
}
public static void test(int[] array){
for(int i = 0; i < array.length; i++)
{
array[i] = i;
}
}
}
運(yùn)行上述的代碼,發(fā)現(xiàn)函數(shù)中修改array的值在函數(shù)結(jié)束后也可以生效喷兼,這是因?yàn)閿?shù)組是一個(gè)引用類型篮绰,在C中我們說(shuō)要想改變實(shí)參的值,需要傳入對(duì)應(yīng)的引用或者指針季惯。在函數(shù)中通過(guò)引用訪問(wèn)吠各,實(shí)際上在訪問(wèn)對(duì)應(yīng)的內(nèi)存,所以這里其實(shí)是在修改對(duì)應(yīng)內(nèi)存的值勉抓。當(dāng)然可以修改實(shí)參的值了贾漏。
ArrayList類
之前在數(shù)組中,我們說(shuō)數(shù)組一旦定義藕筋,是不能改變大小的纵散,那么如果我后續(xù)需要使用可變大小的數(shù)組呢?Java中提供了ArrayList這樣的容器。由于它是一個(gè)通用的容器伍掀,而java又是一個(gè)強(qiáng)類型的語(yǔ)言掰茶,所以在定義的時(shí)候需要事先指定我們需要使用容器存儲(chǔ)何種類型的數(shù)據(jù)。一般ArrayList的定義如下:
ArrayList<String> array = new ArrayList();
<String> 表示容器內(nèi)部存儲(chǔ)的是字符串蜜笤。
需要注意的是容器中只能存儲(chǔ)引用類型濒蒋,不能存儲(chǔ)像int、double把兔、char這樣的基本類型沪伙,如果要存儲(chǔ)這樣的數(shù)據(jù),需要存儲(chǔ)它們對(duì)應(yīng)的封裝類垛贤。比如int 類型對(duì)應(yīng)的封裝類為 Integer焰坪。
它的常用方法如下:
ArrayList(); //構(gòu)造方法
boolean add(E e);//添加元素
void clear(); //清空
E get(int index); //獲取指定位置的元素
int indexOf(Object o); //查詢?cè)氐谝淮纬霈F(xiàn)的位置
E remove(int index); //刪除指定位置的元素
E remove(Object o); //從列表中刪除指定元素的第一個(gè)出現(xiàn)(如果存在)
int size(); //獲取容器中元素個(gè)數(shù)
void sort(Comparator<? super E> c); //使用提供的 Comparator對(duì)此列表進(jìn)行排序
鍵盤(pán)輸入
Java中的鍵盤(pán)輸入主要通過(guò)Scanner類來(lái)實(shí)現(xiàn),Scanner需要提供一個(gè)輸入流聘惦,從輸入流中獲取輸入某饰。一般常用的輸入流是 System.in
表示從鍵盤(pán)輸入,例如:
Scanner sc = new Scanner(System.in);
Scanner類中常用方法是一系列的next方法,next方法主要功能是根據(jù)指定 的分割符善绎,從輸入流中取出下一個(gè)輸入并做相應(yīng)的轉(zhuǎn)化黔漂,比如nextInt()會(huì)轉(zhuǎn)化為int,nextBoolean() 會(huì)轉(zhuǎn)化為boolean類型等等禀酱,next()方法會(huì)直接轉(zhuǎn)化為字符串炬守。
默認(rèn)情況下next函數(shù)會(huì)通過(guò)空格進(jìn)行轉(zhuǎn)化。
import java.util.Scanner;
import java.util.ArrayList;
class Demo{
public static void main(String[] args){
ArrayList<String> array = new ArrayList<String>();
Scanner sc = new Scanner(System.in);
String str = sc.next();
array.add(str);
while(sc.hasNext()){
array.add(sc.next());
}
for(int i = 0; i < array.size(); i++)
{
System.out.println(array.get(i));
}
}
}
這段代碼剂跟,會(huì)將輸入的數(shù)據(jù)依次存儲(chǔ)到ArrayList容器中减途。因?yàn)槌绦蚴孪炔恢烙脩魰?huì)輸入多少數(shù)據(jù),所以這里采用可以可變長(zhǎng)度的容器來(lái)存儲(chǔ)
//輸入(> 表示cmd的提示符)
>hello world python java c++ c lisp
// 輸入ctrl + c來(lái)退出sc.next的輸入
> ctrl+c
//輸出
>hello
world
python
java
c++
c
lisp
上述代碼首先執(zhí)行到sc.next
位置曹洽,并且中斷下來(lái)鳍置,我們輸入上述的一些字符串,然后回車(chē)送淆,然后程序繼續(xù)執(zhí)行税产,在循環(huán)中根據(jù)空格,依次從里面取出每一個(gè)值偷崩,并放到容器中辟拷。當(dāng)沒(méi)有值時(shí),程序會(huì)再次中斷在sc.next()
的位置阐斜,這個(gè)時(shí)候輸入 ctrl + c
衫冻,此時(shí)程序再次執(zhí)行到 sc.hasNext()
這個(gè)地方會(huì)返回false,這個(gè)時(shí)候循環(huán)退出谒出,并依次打印這些內(nèi)容羽杰。
這個(gè)程序證明了上面說(shuō)的渡紫,next方法會(huì)根據(jù)指定的分割符,依次從輸入流中取出下一個(gè)輸入考赛。當(dāng)然如果想要一次讀取一行,可以使用 nextLine
方法莉测。
更多內(nèi)容請(qǐng)查閱JDK文檔颜骤。
<hr />