零基礎學Java(8)陣列

2022-07-16 18:01:53

陣列

陣列儲存相同型別值的序列。
 

宣告陣列

陣列是一種資料結構,用來儲存同一型別值的集合。通過一個整型下標(index,或稱索引)可以存取陣列中的每一個值。例如,如果a是一個整型陣列,a[i]就是陣列中下標為i的整數。
在宣告陣列變數時,需要指出陣列型別(資料元素型別緊跟[])和陣列變數的名字。下面宣告了整型陣列a:

int[] a;

不過,這條語句只宣告了變數a,並沒有將a初始化為一個真正的陣列。應該使用new操作符建立陣列

int[] a = new int[100];

這條語句宣告並初始化了一個可以儲存100個整數的陣列。
陣列長度不要求是常數:new int[n]會建立一個長度為n的陣列。
一旦建立了陣列,就不能再改變它的長度。如果程式執行中需要經常擴充套件陣列的大小,就應該使用另一種資料結構---陣列列表(array list)
 

在Java中,提供了一種建立陣列物件並同時提供初始值的簡寫形式。如下:

int[] a = {2, 3, 5, 7, 11, 13};

請注意,這個語法不需要使用new,甚至不用指定長度。
最後一個值後面允許有逗號,如果你要不斷為陣列增加值,這會很方便:

String[] authors = {"James", "Kobe", "Curry", "Durant",};

當然,我們也可以宣告一個匿名陣列;

new int[] {17, 19, 23, 29, 31, 37}

這會分配一個新陣列並填入大括號中提供的值。它會統計初始值的個數,並相應地設定陣列大小。可以使用這種語法重新初始化一個陣列而無須建立新變數。例如:

smallPrimes = new int [] {17, 19, 23, 29, 31, 37};

 
注意
在Java中,允許有長度為0的陣列。在編寫一個結果為陣列的方發時,如果碰巧結果為空,這樣一個長度為0的陣列就很有用。可以如下建立長度為0的陣列:

new elementType[0];
或
new elementType2[] {};

 

存取陣列

前面的陣列元素的下標為從0~99(不是1~100)。一旦建立了陣列,就可以在陣列中填入元素,例如,使用一個迴圈:

int[] a = new int[100];
for (int i = 0; i < 100; i++) {
    a[i] = i;
}

建立一個數位陣列時,所有元素都初始化為0,boolean陣列的元素會初始化為false。物件陣列的元素則初始化為一個特殊值null,表示這些元素還未存放任何物件。剛開始我們可能有些不瞭解,例如:

String[] names = new String[10];

我們會建立一個包含10個字串的陣列,所有字串都為null。如果希望這個陣列包含空串,必須為元素指定空串:

for (int i=0; i < 10; i++) names[i] = "";

注意:如果建立了一個100個元素的陣列,並且試圖存取元素a[100](或在0~99之間的任何下標),就會引發array index out of bounds異常。
如果我們想獲得陣列中的元素個數,可以使用array.length。例如:

for (int i=0; i<a.length; i++) {
  System.out.println(a[i]);
}

 

for each迴圈

Java有一種功能很強的迴圈結構,可以用來依次處理陣列(或者其他元素集合)中的每個元素,而不必考慮指定下標值。這種增強的for迴圈的語句格式為:

for (variable: collection) statement

它定義一個變數用於暫存集合中的每一個元素,並執行相應的語句(當然,也可以是語句塊)。collection這一集合表示式必須是一個陣列或者是一個實現了Iterable介面的類物件(例如ArrayList),例如:

int[] a = {2, 3, 4, 5, 6};
for (int element: a) {
    System.out.println(element);
}

列印陣列a的每一個元素,一個元素佔一行。

2
3
4
5
6

這個迴圈應該讀作"迴圈a中的每一個元素"(for each element in a)。當然,使用傳統的for迴圈也可以獲得同樣的效果:

for (int i = 0;i < a.length; i++) {
    System.out.println(a[i]);
}

但是,for each迴圈語句顯得更加簡潔、更不易出錯,因為你不必為下標的起始值和終止值而操心。
for each迴圈語句的迴圈變數將會遍歷陣列中的每個元素,而不是下標值
總結:如果需要處理一個集合中的所有元素,for each迴圈語句相對於傳統迴圈語句所做的改進很讓人欣喜。然而,很多情況下還是需要使用傳統的for迴圈。例如,如果不希望變數整個集合,或者在迴圈內部需要使用下標值時。
 

陣列拷貝

在Java中,允許將一個陣列變數拷貝到另一個陣列變數。這時,兩個變數將參照同一個陣列:

public class SevenSample {
    public static void main(String[] args) {
        int[] smallPrimes = {2, 3, 4, 5, 6, 7, 8};
        // 拷貝smallPrimes
        int[] luckyNumbers = smallPrimes;
        System.out.println(Arrays.toString(luckyNumbers));
    }
}

結果

[2, 3, 4, 5, 6, 7]

下圖顯示了拷貝的結果。

如果希望將一個陣列的所有值拷貝到一個新的陣列中去,就要使用Arrays類的copyOf方法:

import java.util.Arrays;

public class SevenSample {
    public static void main(String[] args) {
        int[] smallPrimes = {2, 3, 4, 5, 6, 7};
        int[] copiedLuckyNumbers = Arrays.copyOf(smallPrimes, smallPrimes.length);
        System.out.println(Arrays.toString(copiedLuckyNumbers));
    }
}

結果如下:

[2, 3, 4, 5, 6, 7]

Array.copyOf方法中,第1個引數是拷貝的物件,第2個引數是新陣列的長度。這個方法通常用來增加陣列的大小:

luckNumbers = Arrays.copyOf(luckyNumbers, 2 * luckyNumbers.length);

①如果陣列元素是數值型,那麼額外的元素將被賦值為0;

②如果陣列元素是布林值,那麼額外的元素將被賦值為false

③如果長度小於原始陣列的長度,則只拷貝前面的值

 

命令列引數

每一個Java應用程式都有一個帶String args[]引數的main方法。這個參數列明main方法將接收一個字串陣列,也就是命令列上指定的引數。
例如,來看下面的程式:

public class EightSample {
    public static void main(String[] args) {
        if (args.length == 0 || args[0].equals("-h"))
            System.out.print("Hello, ");
        else if (args[0].equals("-g"))
            System.out.print("Goodbye, ");
        for (int i = 1; i < args.length; i++)
            System.out.print(" " + args[i]);
        System.out.println("!");
    }
}

如果使用下面這種形式呼叫這個程式:

java EightSample -g cruel world

args陣列將包含以下內容:

args[0]: "-g"
args[1]: "cruel"
args[2]: "world"

這個程式會輸出下面的資訊:

 

陣列排序

要想對數值型陣列進行排序,可以使用Arrays類中的sort方法:

int[] a = new int[10000];
...
Arrays.sort(a)

這個方法使用了優化的快速排序演演算法。快速排序演演算法對於大多數資料集合來說都是效率比較高的。
 

實戰

寫一個程式,它產生一個抽彩遊戲中的亂數字組合,我們加入抽彩是從49個數位中抽取6個,那麼輸出的結果為:

下注以下組合,它會使你發財
8
30
32
43
46
49

具體程式碼如下:

public class NinthSample {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);

        System.out.println("你要抽多少次?");
        int k = in.nextInt();

        System.out.println("你能抽取的最高數位是什麼?");
        int n = in.nextInt();

        // 從數位1 2 3...n填入陣列numbers
        int[] numbers = new int[n];
        for (int i = 0; i < numbers.length; i++)
            numbers[i] = i + 1;

        // 抽取k次並放入第二個陣列中
        int[] result = new int[k];
        for (int i = 0; i < result.length; i++)
        {
            // 0到n-1之間建立一個隨機索引
            int r = (int) (Math.random() * n);

            // 從隨機的位置獲取元素
            result[i] = numbers[r];

            // 將最後一個元素移動到隨機位置
            numbers[r] = numbers[n - 1];
            n--;
        }

        Arrays.sort(result);
        System.out.println("下注以下組合,它會使你發財");
        for (int r: result)
            System.out.println(r);
    }
}

程式碼邏輯分析
  要想選擇一個隨機的數位集合,就要首先將值1,2,...,n存入陣列numbers中:

int[] numbers = new int[n];
for (int i=0; i < numbers.length; i++)
    numbers[i] = i + 1;

  用第二個陣列存放抽取出來的數:

int[] result = new int[k];

  現在,就可以開始抽取k個數了。Math.random方法返回一個0到1之間(包含0,不包含1)的隨機浮點數。用n乘以浮點數,就可以得到從0到n-1之間的一個亂數。

int r = (int) (Math.random() * n);

  下面將result的第i個元素設定為numbers[r]存放的數值,最初是r+1。但正如所看到的,numbers陣列的內容在每一次抽取之後都會發生變化。

result[i] = numbers[r];

  現在,必須確保不會再次抽取到那個數,因為所有抽彩的數必須不相同。因此,這裡用陣列中的最後一個數覆蓋number[r],並將n減1。

numbers[r] = numbers[n - 1];
n--; 

  關鍵在於每次抽取的都是下標,而不是實際的值。下標指向陣列中包含尚未抽取過的值。
  在抽取了k個數之後,可以對result陣列進行排序,這樣可以讓輸出效果更好:

Arrays.sort(result);
for (int r: result)
    System.out.println(r);

Arrays API

static String toString(xxx[] a)
返回包含a中元素的一個字串,這些元素用中括號包圍、並用逗號分隔。在這個方法以及後面的方法中,陣列元素型別xxx可以是`int`、`long`、`short`、`char`、`byte`、`boolean`、`float`或`double`。

static xxx[] copyOf(xxx[] a, int end)
static xxx[] copyOfRange(xxx[] a, int start, int end)
返回與a型別相同的一個陣列,其長度為`length`或者`end-start`,陣列元素為a的值。如果end大於`a.length`,結果會填充0或false值。

static void sort(xxx[] a)
使用優化的快速排序演演算法對陣列進行排序

static int binarySearch(xxx[] a, xxx v)
static int binarySearch(xxx[] a, int start, int end, xxx v)
使用二分查詢演演算法在有序陣列a中查詢值v。如果找到v,則返回相應的下標:否則返回同一個負數值r。`-r-1`是v應插入的位置(為保持a有序)。

static void fill(xxx[] a, xxx v)
將陣列的所有資料元素設定為v。

static boolean equals(xxx[] a, xxx[] b)
如果兩個陣列大小相同,並且下標相同的元素都對應相等,返回true

 

多維陣列

多維陣列將使用多個下標存取陣列元素,它適用於表示表格或更加複雜的排列形式。
假設需要建立一個數值表格,用來顯示不同利率下投資10000美元會增長多少,我們可以使用一個二維陣列(也稱為矩陣)來儲存這些資訊。陣列命名為balances
在Java中,宣告一個二維陣列很簡單,如下:

double[][] balances;

對陣列進行初始化之前是不能使用的。我們可以像下面一樣進行初始化:

balances = new double[NYEARS][NRATES];

另外,如果知道陣列元素,就可以不呼叫new,而直接使用簡寫形式對多維陣列進行初始化:

int[][] magicSquare = 
{
  {1, 2, 3, 4},
  {5, 6, 7, 8}
};

一旦陣列初始化,就可以利用兩個中括號存取各個元素,例如,balance[i][j]
 

實戰

public class ElevenSample {
    public static void main(String[] args) {
        // 初始利率
        final double STARTRATE = 10;
        // 列數
        final int NRATES = 6;
        // 行數
        final int NYEARS = 10;

        // 定義利息陣列,陣列長度為6
        double[] interestRate = new double[NRATES];
        for (int j = 0; j < interestRate.length; j++)
            interestRate[j] = (STARTRATE + j) / 100.0;

        // 定義餘額陣列,一維表示年,二維表示利率
        double[][] balances=  new double[NYEARS][NRATES];
        Arrays.fill(balances[0], 10000);

        for (int i = 1; i < balances.length; i++) {
            for (int j = 0; j < balances[i].length; j++) {
                double oldBalance = balances[i - 1][j];
                double interest = oldBalance * interestRate[j];
                balances[i][j] = oldBalance + interest;
            }
        }

        for (int j = 0; j < interestRate.length; j++)
            System.out.printf("%9.0f%%", 100 * interestRate[j]);
        System.out.println();

        for (double[] row : balances) {
            for (double b: row)
                System.out.printf("%10.2f", b);
            System.out.println();
        }
    }
}

結果

 

不規則陣列

暫不講解