我的JAVA筆記004----JAVA陣列
我的JAVA筆記
----------------------------------第一章JAVA陣列
----------------------------------2018.12.23
1.什麼是陣列?
- 程式=演算法+資料結構
流程控制解決的問題即為演算法問題。資料結構,就是把資料按照特定的某種結構來儲存,陣列就是一種基本的資料結構。 - 陣列:相同資料型別的元素組成的集合。元素按照線性順序排列,即除了第一個和最後一個元素外,每個元素都有前驅和後繼。
- Java語言是典型的靜態語言,因此Java陣列是靜態的,即當陣列被初始化之後,該陣列所佔的記憶體空間和陣列長度都是不可改變
2.定義陣列(宣告陣列變數)
根據陣列元素的資料型別,可以將其分為基本型別陣列和引用型別陣列。
Java語言支援兩種語法格式來定義陣列:
type[ ] arrayName;
type arrayName[ ]; (不建議使用,為了和C語法相容的寫法)
- 基本型別陣列
- 引用型別陣列
public class TEST02 { public static void main(String[] args) { Person[] students; System.out.println("動態初始化"); students = new Person[2]; Person zhang = new Person(); zhang.age = 19; zhang.height = 180; zhang.info(); Person lee = new Person(); lee.age = 20; lee.height = 165; lee.info(); System.out.println("陣列變數指向Person物件"); students[0] = zhang; students[1] = lee; students[0].info(); students[1].info(); System.out.println("陣列變數修改Person物件的屬性"); students[0].age = 20; students[0].info(); } } class Person{ public int age; public double height; public void info() { System.out.println("我的年齡是:"+age+",我的身高是:"+height); } }
此時,完成陣列定義,但暫時還沒初始化。
陣列是一種引用型別的變數,因此使用它定義一個變數時,僅僅表示定義了一個引用變數(也就是定義了一個指標),這個引用變數還未指向任何有效的記憶體,因此定義陣列時不能指定陣列的長度(初始化的工作任務)。
3.陣列初始化
包括兩種方式:靜態初始化和動態初始化。
靜態初始化
- 完整寫法:
宣告陣列變數與陣列初始化分開寫
type[] arrayName;
arrayName= new type[]{data1,data2,data3};
宣告陣列變數時初始化
type[] arrayName = new type[]{data1,data2,data3};
其中,arrayName為陣列變數;{data1,data2,data3}為陣列物件;data1,data2,data3為陣列元素。
- 簡化形式:
type[] arrayName = {data1,data2,data3};(這種寫法只能在宣告陣列變數時同時初始化)
動態初始化
type[] arrayName = new type[ length];
對於靜態初始化方式,無須指定陣列長度,指定了陣列的陣列元素,由系統來決定該陣列的長度即可。
對於動態初始化,僅需要指定陣列的長度(new關鍵字分配空間時需指定分配的間大小),即為每個陣列元素指定所需的記憶體空間,系統將為這些元素分配初始值。
基本型別:
- 整數型別(byte、short、int、long)則陣列元素的初始值為0;
- 浮點型別(float、double)則陣列元素的初始值為0.0;
- 布林型別(boolean)則陣列元素的初始值為false;
- 字元型別(char)則陣列元素的初始值為’\u0000’;
引用型別:
- 引用型別(類、介面、陣列)則陣列元素的初始值為null。
4.使用陣列
4.1陣列元素的訪問、遍歷
- 訪問
陣列物件的大小是固定的,長度為n,下標範圍為0~n-1。
通常使用arrayName[下標]訪問陣列元素,當下標範圍不在0~n-1時出現數組索引越界異常(執行時異常)
編譯時異常和執行時異常,簡單來說就是編譯時異常就是在寫程式碼時就會提示異常,執行時異常是在執行之後異常。
- 遍歷,輸出陣列
(1)普通for迴圈
.length屬性,獲取陣列物件長度。
for(int i=0;i<arrayName.length;i++) {
System.out.println(arrayName[i]);
}
(2)foreach迴圈(Java5)
無須獲取陣列長度,無須迴圈迭代條件,無須根據索引來訪問陣列元素,由系統完成。
for(type variableName :arrayName) {
System.out.println(variableName);
}
variableName為一個臨時變數,系統依次把陣列元素賦給這個臨時變數,這個臨時變數不是陣列元素(僅儲存),所以不能對其進行賦值修改陣列。
(3).toString()
System.out.println(Arrays.toString(arrayName));
4.2複製
首先為什麼要複製呢?
因為單純的陣列賦值無法使陣列物件隔離,這樣子容易使陣列物件不小心被修改。
4.2.1賦值
public static void main(String[] args) {
int[] arr1 = new int[]{1,2,3,4};
int[] arr2 = arr1;
System.out.println("arr1:"+Arrays.toString(arr1));
System.out.println("arr2:"+Arrays.toString(arr2));
arr2[0]=0;
System.out.println("arr1:"+Arrays.toString(arr1));
System.out.println("arr2:"+Arrays.toString(arr2));
}
執行結果為:
arr1:[1, 2, 3, 4]
arr2:[1, 2, 3, 4]
arr1:[0, 2, 3, 4]
arr2:[0, 2, 3, 4]
以上為陣列賦值,兩個引用變數指向同一個陣列物件,兩個引用變數均可以修改陣列元素的值。
修改arr2的陣列元素,會使該陣列物件的元素值被修改,所以會發現arr1的列印結果和arr2一樣。
如此,陣列物件沒有隔離性,不小心就會被修改。
4.2.2拷貝
拷貝:實現陣列物件隔離性
public static void main(String[] args) {
int[] arr1 = new int[]{1,2,3,4};
int[] arr3 = new int[arr1.length];
System.out.println("初始化::");
System.out.println("arr1:"+Arrays.toString(arr1));
System.out.println("arr3:"+Arrays.toString(arr3));
for(int i=0;i<arr1.length;i++) {
arr3[i] = arr1[i];
}
System.out.println("拷貝後:");
System.out.println("arr1:"+Arrays.toString(arr1));
System.out.println("arr3:"+Arrays.toString(arr3));
arr3[0]=0;
System.out.println("修改arr3陣列元素值:");
System.out.println("arr1:"+Arrays.toString(arr1));
System.out.println("arr3:"+Arrays.toString(arr3));
}
結果為:
初始化::
arr1:[1, 2, 3, 4]
arr3:[0, 0, 0, 0]
拷貝後:
arr1:[1, 2, 3, 4]
arr3:[1, 2, 3, 4]
修改arr3陣列元素值:
arr1:[1, 2, 3, 4]
arr3:[0, 2, 3, 4]
可以看到arr3的修改並不會影響到arr1,這就是實現了陣列物件的隔離。
兩個陣列變數實際指向不同的陣列物件。
4.2.3陣列拷貝的其他方法
public static void main(String[] args) {
int[] src = new int[]{1,2,3,4};
int[] dest1 = new int[src.length];
System.arraycopy(src,0,dest1,0,src.length);
System.out.println("src:"+Arrays.toString(src));
System.out.println("dest1:"+Arrays.toString(dest1));
int[] dest2 = Arrays.copyOf(src, src.length);
System.out.println("dest2:"+Arrays.toString(dest2));
}
結果為:
src:[1, 2, 3, 4]
dest1:[1, 2, 3, 4]
dest2:[1, 2, 3, 4]
需要import java.util.Arrays;
System.arraycopy(); Java API提供,底層使用C++寫的,速度快,比for迴圈實現陣列拷貝效率更高,推薦使用。
Arrays.copyOf(); JDK1.6版本提供,底層是arraycopy()方法,但需要注意JDK版本。
4.2.4利用拷貝進行陣列擴容
陣列的長度在初始化後是不可改變的,可以建立一個更大的新陣列並將小陣列複製到其中,實現“陣列擴容”。
public static void main(String[] args) {
int[] a = new int[] {1,2,3,4,5,6};
System.out.println("原始a:"+Arrays.toString(a));
System.out.println(a);
a = Arrays.copyOf(a, a.length+5);
System.out.println("擴容後a:"+Arrays.toString(a));
System.out.println(a);
}
結果為:
原始a:[1, 2, 3, 4, 5, 6]
[[email protected]
擴容後a:[1, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0]
[[email protected]
實際上,只是將陣列變數指向另一個新的陣列物件,原始的陣列長度並沒有發生變化。
4.3陣列的記憶體管理
4.3.1基本型別陣列的初始化
程式先為陣列分配記憶體空間,再將陣列元素的值存入對應記憶體中。
int[] a;
在main方法棧區定義了一個數組變數,還未指向陣列物件。
a= new int[] {1,2,3,4,5,6};
靜態初始化之後,確定了陣列長度,將陣列元素填入堆區陣列物件的記憶體中(若是動態初始化,預設值)。
注意:
指定型別的變數只能儲存指定型別的值。
所有區域性變數都存放在棧記憶體中,不管是基本型別的變數還是引用型別的變數,都是儲存在各自的方法棧記憶體中的;但引用型別的變數所引用的物件(包括陣列、普通的Java物件)則是儲存在堆記憶體中。
4.3.2引用型別陣列的初始化
引用型別陣列的陣列元素依然是引用型別的,因此陣列元素裡儲存的還是引用,它指向另一塊記憶體,這塊記憶體裡儲存了該引用變數所引用的物件。
4.4易混淆的點
4.4.1陣列物件和陣列變數
陣列變數只是一個引用變數,陣列物件就是儲存在堆記憶體中的連續記憶體空間。所以陣列初始化並不是對陣列變數執行初始化,而是在堆記憶體中建立陣列物件(為該陣列物件分配一塊連續的記憶體空間)。
就像是一個數組物件可以看做一間房間,我們可以給它取不同的名字,但是我要是把這個名字掛到另一個房間去,那這個名字對應的就是另一間房間了,也就是指向了另一個數組物件(陣列賦值)。當然,我一個房間可以取很多名字,但是它們中哪怕一個一旦修改了這個房間的屬性,那麼別的名字看到的房間屬性也就變了,這就是陣列物件的不隔離性。
從記憶體來看,陣列變數儲存在棧記憶體區,陣列物件儲存在堆記憶體區,兩者是不一樣的。
4.4.2陣列長度
只要型別相互相容,可以讓一個數組變數指向另一個實際的陣列,這種操作會產生陣列的長度可變的錯覺(陣列物件改變了)。
由於陣列變數整體賦值(上面講的陣列擴容)導致的陣列的長度可以改變,只是一個假相(因為陣列物件不是同一個物件)。
4.4.3多維陣列
Java實際上沒有多維陣列,所謂多維陣列其實就是陣列元素依然是陣列的一維陣列,N維陣列是陣列元素為(N-1)維陣列的陣列。