7. 一维数组
7.1 数组的基础知识
一旦数组被创建,它的大小是固定的。使用一个数组引用变量,通过下标来访问数组中的元素。
7.1.1 声明数组变置
为了在程序中使用数组,必须声明一个引用数组的变量,并指明数组的元索类型。下面是声明数组变量的语法:
elementType[] arrayRefVar;(元素类型口数组引用变量 ;)
elementType 可以是任意数据类型,但是数组中所有的元素都必须具有相同的数据类型。例如:下面的代码声明变量 myList,它引用一个具有 double 型元素的数组。
double[] myList;
注意:也可以用 elementType arrayRefVar[ ])(元素类型数组引用变量[ ]) 声明数组变量。这种来自 C/C++ 语言的风格被 Java 采纳以适用于 C/C++ 程序贞。推荐使用 elementType[] arrayRefVar (元素类型 [ ]数组引用变量)风格。
7.1.2 创建数组
不同于基本数据类型变量的声明,声明一个数组变量时并不在内存中给数组分配任何空间。它只是创建一个对数组的引用的存储位置。如果变量不包含对数组的引用,那么这个变量的值为 null。除非数组已经被创建,否则不能给它分配任何元素。声明数组变量之后,可以使用下面的语法用 new 操作符创建数组,并且将它的引用赋给一个变量:
arrayRefVar = new e1ementType[arraySize]:
这条语句做了两件事情:1 ) 使用 new elementType[arrayS"ize]创建了一个数组;2 ) 把这个新创建的数组的引用赋值给变暈 arrayRefVar。
声明一个数组变量、创建数组、然后将数组引用赋值给变量这三个步驟可以合并在一条语句里,如下所示:
elementType[] arrayRefVar = new elementType[arraySize];
(元素类型[ ]数组引用变量 =new 元素类型[ 数组大小]; )
或
elementType arrayRefVar[] = new e1ementType[arraySize];
(元素类型数组引用变量 =new 元素类型[ 数组大小];)
下面是使用这条语句的一个例子:
double[] myList = new double[10]:
这条语句声明了数组变量 myList, 创建一个由 10 个 double 型元素构成的数组,并将该数组的引用赋值给 myList。使用以下语法给这些元索赋值:
arrayRefVar[index] = value;
例如,下面的代码初始化数组:
myList[0] = 5.6;
myList[l] = 4.5;
myList[2] = 3.3;
myL1st[3] = 13.2;
myList[4] = 4.0;
myList[5] = 34.33;
myList[6] = 34.0;
myList[7] = 45.45;
myL1st[8] = 99.993;
myList[9] = 11123;

注意:一个数组变量看起来似乎是存储了一个数组,但实际上它存储的是指向数组的引用。严格地讲,一个数组变量和一个数组是不同的,但多教情况下它们的差别是可以忽略的。因此,为了简化,通常可以说 myList 是一个数组,而不用更长的陈述:myList 是一个含有 10 个 double 型元素数组的引用变量。
7.1.3 数组大小和默认值
当给数组分配空间时,必须指定该数组能够存储的元素个数,从而确定数组大小。创建数组之后就不能再修改它的大小。可以使用 arrayRefVar.length 得到数组的大小。例如: myList.length 为 10。
当创建数组后,它的元素被赋予默认值,数值型基本数据类型的默认值为 0, char 型的默认值为 '\u0000',boolean 型的默认值为 false。
7.1.4 访问数组元素
数组元素可以通过下标访问。数组下标是基于 0的,也就是说,其范围从 0 开始到 arrayRefVar.length -1结束。例如,在上图的例子中,数组 myList 包含 10 个 double 值,而且下标从 0 到 9。
数组中的每个元素都可以使用下面的语法表示,称为下标变量 (indexed variable) :
arrayRefVar[index];(数组引用变量[下标 ];)
例如:myList[9] 表示数组 myList 的最后一个元素。
警告:一些语言使用圆括号引用数组元素,例如 myList(9)。而 Java 语言使用方括号,例如 myList[9]。
创建数组后,下标变量与正常变量的使用方法相同。例如:下面的代码是将 myList[0] 和 myList[l]的值相加赋给 myList[2]。
myList[2] = myList[0] + myList[l];
下面的循环是将 0 赋给 myList[0], 1赋给 myList[1], ..., 9 赋给 myList[9]:
for (int i = 0;i < myList.length; i++){
myList[i] = i;
}
7.1.5 数组初始化语法
Java 有一个简捷的标记,称作数组初始化语法,它使用下面的语法将声明数组、创建数组和初始化数组结合到一条语句中:
elementType[] arrayRefVar = {valueO, valuel, valuek};
(元索类型 [] 数组引用变量={值 0,值 1,... 值 k};)
例如:
double[] myList = {1.9, 2.9, 3.4, 3.5};
这条语句声明、创建并初始化包含 4 个元素的数组 myList, 它等价于下列语句:
double[] myList = new double[4];
myList[0] = 1.9;
myList[l] = 2.9;
myList[2] = 3.4;
myList[3] = 3.5;
警告:数组初始化语法中不使用操作符 new。使用数组初始化语法时,必须将声明、创建和初始化数组都放在一条语句中。将它们分开会产生语法错误。因此,下面的语句是错误的:
double[] myList;
myList ={1.9, 2.9, 3.4, 3.5};
7.1.6 处理数组
处理数组元素时,经常会用到 for 循环,理由有以下两点:
1 ) 数组中所有元素都是同一类型的。可以使用循环以同样的方式反复处理这些元素。
2 )由于数组的大小是已知的,所以很自然地就使用 for 循环。
7.1.7 foreach 循环
Java 支持一个简便的 for 循环,称为 foreach 循环,即不使用下标变量就可以顺序地遍历整个数组。例如,下面的代码就可以显示数组 myList 的所有元素:
for (double e: myList) {
System.out.println(e);
}
此代码可以读作 “ 对 myList 中每个元素 e 进行以下操作” 。注意,变量 e 必须声明为与 myList 中元素相同的数据类型。
通常,foreach 循环的语法为:
for (elementType element: arrayRefVar) {
// Process the element
}
但是,当需要以其他顺序遍历数组或改变数组中的元素时,还是必须使用下标变量。
警告:越界访问数组是经常会出现的程序设计错误,它会抛出一个运行错误 ArraylndexOut OfBoundsException。为 了 避 免 错 误 的发生,在使用时应确保所使用的下标不超过 arrayRefVar.length -1。
程序员经常错误地使用下标1幻用数组的第一个元素,但其实第一个元素的下标应该是 0。这称为下标过 1 错误(off - by - one error)。它是在循环中该使用 <的地方误用 <= 时会犯的错误。例如,下面的循环是错误的:
for (int i = 0;i <= list.length;i++)
System.out.print(list[i] + "");
应该用 <替换 <=。
7.2 数组的复制
要将一个数组中的内容复制到另外一个中,你需要将数组的每个元素复制到另外一个数组中。
在程序中经常需要复制一个数组或数组的一部分。这种情况下,你可能会尝试使用赋值语句(=), 如下所示:
list2 = list1;
该语句并不能将 listl引用的数组内容复制给 list2, 而只是将listl的引用值复制给了 list2。在这条语句之后,listl和 list2 都指向同一个数组,如下图所示。list2 原先所引用的数组不能再引用,它就变成了垃圾,会被 Java 虚拟机自动收回(这个过程称为垃圾回收)。

在 Java 中,可以使用賦值语句复制基本数据类型的变量,但不能复制数组。将一个数组变量赋值给另一个数组变量,实际上是将一个数组的引用复制给另一个变量,使两个变量都指向相同的内存地址。
复制数组有三种方法:
- 1 ) 使用循环语句逐个地复制数组的元素。
- 2 ) 使用 System 类中的静态方法 arraycopy。
- 3 )使用 clone 方法复制数组。
可以使用循环将源数组中的每个元素复制到目标数组中的对应元素。例如,下述代码使用 for 循环将 sourceArray 复制到 targetArray:
int[] sourceArray = {2 , 3, 1, 5, 10};
int[] targetArray = new int[sourceArray.length]:
for Cint i = 0: i < sourceArray.lenath: i++) {
targetArray[i] = sourceArray[i];
}
另一种方式是使用 java.lang.System 类的 arraycopy 方法复制数组,而不是使用循环。 arraycopy 的语法如下所示:
arraycopy(sourceArray, srcPos, targetArray, tarPos, length);
其中,参数 srcPos 和 tarPos 分别表示在源数组 sourceArray 和目标数组 targetArray 中的起始位置。从 sourceArray 复制到 targetArray 中的元素个数由参数 length 指定。例如,可以使用下面的语句改写上述循环:
System.arraycopy(sourceArray, 0, targetArray, 0,sourceArray.length);
arraycopy 方法没有给目标数组分配内存空间。复制前必须创建目标数组以及分配给它的内存空间。复制完成后,sourceArray 和 targetArray 具有相同的内容,但占有独立的内存空间。
注意:arraycopy 方法违反了 Java 命名习惯。根据命名习慣,该方法应该命名为 arrayCopy(即字母 C 大写)。
7.3 将数组传递给方法
当将一个数组传递给方法时,数组的引用被传给方法。正如前面给方法传递基本数据类型的值一样,也可以给方法传递数组。例如,下面的方法显示 int 型数组的元素:
public static void printArray(int[] array){
for (int i = 0;i < array.length;i++ ){
System.out.print(array[i] + "");
}
}
可以通过传递一个数组调用上面的方法。例如,下面的语句调用 PrintArray 方法显示 3、1、2、6、4 和 2:
printArrayCnew int[]{3, 1, 2, 6, 4, 2};
注意:前面的语句使用下述语法创建数组:
new elementType[ ]{value0, value1, … ,valuek};
该数组没有显式地引用变量,这样的数组称为匿名数组(anonymous array)。
Java 使用按值传递(pass - by - value)的方式将实参传递给方法。传递基本数据类型变量的值与传递数组值有很大的不同。
对于基本数据类型参数,传递的是实参的值。
对于数组类型参数,参数值是数组的引用,给方法传递的是这个引用。从语义上来讲,最好的描述就是参教传递的是共享信息(pass - by - sharing), 即方法中的数组和传递的数组是一样的。所以,如果改变方法中的数组,将会看到方法外的数组也变化了。
例如,采用下面的代码:
public class Test {
public static void main(String[] args){
int x = 1;//x represents an int value
int[] y = new int[l0];// y represents an array of int values
m(x , y); // Invoke m with arguments x and y
System.out.println("x is " + x);
System.out.print!n("y[0] is " + y[0]);
}
public static void m(int number, int[] numbers){
number = 1001;// Assign a new value to number
numbers[0] = 5555;// Assign a new value to numbers[0]
}
}
显示
x is 1
y[0] is 5SS5
你会觉得困惑,为什么在调用 m 之后 x 仍然是1, 但是 y[0]却变成了 SSSS。这是因为尽管 y 和 numbers 是两个独立的变量,但它们指向同一数组,如下图所示。当调用 m(x,y)时,x 和 y 的值传递给 number 和 numbers。因为 y 包含数组的引用值,所以,numbers 现在包含的是指向同一数组的相同引用值。

注意:数组在 Java 中是对象。JVM 将对象存储在一个称作堆(heap) 的内存区域中,堆用于动态内存分配。
7.4 从方法中返回数组
当从方法中返回一个数组时,数组的引用被返回。
可以在调用方法时向方法传递一个数组。方法也可以返回一个数组。例如,下面的方法返回一个与输人数组元素顺序相反的数组:
public static int[] reverse(int[] list) {
int[] result = new int[list.length];
for (int i = 0, j = result.length - 1;
i < list.length; i++, j--) {
result[j]=l1st[i];
}
return result;
}

第 2 行创建了一个新数组 result, 第 4 ~7 行把数组 list的元素复制到数组 result 中。第 9 行返回数组。例如,下面的语句返回元素为 6、5、4、3、2、1的新数组 list2。
int[] listl = {1, 2, 3, 4, 5, 6};
int[] list2 = reverse(list1);
7.5 可变长参数列表
具有同样类型的可变长度的参数可以传递给方法,并将作为数组对待。可以把类型相同但个数可变的参数传递给方法。方法中的参数声明如下:
typeName… parameterName ( 类 型 名 … 参 数 名 )
在方法声明中,指定类型后紧跟着省略号(...)。只能给方法中指定一个可变长参数,同时该参数必须是最后一个参数。任何常规参数必须在它之前。
Java 将可变长参数当成数组对待。可以将一个数组或数目可变的参数传递给可变长参数。当用数目可变的参数调用方法时,Java 会创建一个数组并把参数传给它。
7.6 数组的查找
如果一个数组排好序了,对于寻找數组中的一个元素,二分查找比线性查找更加高效。
査找(searching) 是在数组中寻找特定元素的过程,例如:判断某一特定分数是否包括在成绩列表中。査找是计算机程序设计中经常要完成的任务。有很多用于査找的算法和数据结构。本节讨论两种经常使用的方法:线性查找(linear searching ) 和二分查找( binary searching)。
7.6.1 线性查找法
线性査找法将要査找的关键字 key 与数组中的元素逐个进行比较。这个过程持续到在列表中找到与关键字匹配的元素,或者査完列表也没有找到关键字为止。如果匹配成功,线性査找法返回与关键字匹配的元素在数组中的下标。如果没有匹配成功,则返回 -1。下列程序清单中的linearSearch 方法给出解决方案:
LinearSearch.java
public class LinearSearch {
/**The method for finding a key in the list */
public static int linearSearch(int[] list, int key) {
for (int i = 0;i < list.length;i++){
if (key = list[i]) {
return i;
}
}
return -1;
}
}

为了更好地理解这个方法,对下面的语句跟踪这个方法:
int[] list = {1, 4, 4, 2, 5, -3, 6, 2};
int i = linearSearch(list, 4); // Returns 1
int j- linearSearch(list, -4); // Returns -1
int k - 1inearSearch(list, -3); // Returns 5
线性査找法把关键字和数组中的每一个元素进行比较。数组中的元素可以按任意顺序排列。平均来看,如果关键字存在,那么在找到关键字之前,这种算法必须与数组中一半的元素进行比较。由于线性査找法的执行时间随着数组元素个数的增长而线性增长,所以,对于大数组而言,线性査找法的效率并不高。
7.6.2 二分查找法
二分査找法是另一种常见的对数值列表的査找方法。使用二分査找法的前提条件是数组中的元素必须已经排好序。假设数组已按升序排列。二分査找法首先将关键字与数组的中间元素进行比较。
考虑下面三种情况:
- 如果关键字小于中间元素,只需要在数组的前一半元素中继续査找关键字。
- 如果关键字和中间元素相等,则匹配成功,査找结束。
- 如果关键字大于中间元素,只需要在数组的后一半元素中继续査找关键字。
显然,二分法在每次比较之后就排除掉一半的数组元素,有时候是去掉一半的元素,有时候是去掉一半加 1 个元素。假设数组有个元素。为方便起见,假设 n 是 2 的幂。经过第1 次比较,只剩下 n/2 个元素需要进一步査找;经过第 2次比较,剩下(n/2)/2 个元素需要进— 步査找。经过 k 次比较之后,需要査找的元素就剩下 n/2k 个。当hl0g2n 时,数组中只剩 下 1 个元素,就只需要再比较丨次。因此,在一个已经排序的数组中用二分査找法査找一个元素,即使是最坏的情况,也只需要 login+l次比较。对于一个有 1024 (2^10 ) 个元素的数组,在最坏情况下,二分査找法只需要比较11次,而在最坏的情况下线性査找要比较 1023 次。
每次比较后,数组要査找的部分就会缩小一半。用 low 和 high 分别表示当前査找数组的第一个下标和最后一个下标。初始条件下,low 为 0, 而 high 为 list.length -1。让mid 表示列表的中间元素的下标。这样,mid 就是(low + high)/2。下图显示怎样使用二分法从列表{2,4, 7, 10,11, 4S, S0, 59, 60, 66, 69, 70, 79}中找出关键字 11。

现在知道了二分査找法是如何工作的。下一个任务就是在 Java 中实现它。不要急于给出一个完整的实现。逐步地实现这个程序,一次一步。可以从査找的第一次迭代开始,如下图a 所示。它将关键字 key 和低下标 low 为 0、髙下标 high 为 list.length -1的列表的中间元素进行比较。如果 key<list[nrid], 就将下标 high 设置为 mid -1;如果 key = list[mid], 则匹配成功并返回 mid; 如果 key>list[mid],就将下标 low 设置为 mid+1。

接下来就要考虑增加一个循环,实现这个方法重复地完成査找,如上图b 所示。如果找到这个关键字,或者当 low>high 时还没有找到这个关键字,就结束这个査找。
当没有找到这个关键字时,low 就是一个插入点,这个位置将插入关键字以保持列表的有序性。一种更实用的方法是返回插入点减去1。这个方法必须返回一个负值,表明这个关键字不在该序列中。可以只返回 - low 吗?答案是:不可以。如果关键字小于 list[0], 那么low 就是 0, -0 也是 0。这就表明关键字匹配 list[0]。一个好的选择是,如果关键字不在该序列中,方法返回 - low -1。返回不仅表明关键字不在序列中,而 且 还 给出了关键字应该插人的地方。
下面的表格列出当方法退出时 low 和 high 的值,以及调用该方法的返回值。

注意:线性查找法适用于在较小数组或没有排序的数组中查找,但是对大数组而言效率不高。二分查找法的效率较高,但它要求数组已经排好序。
7.7 数组的排序
假设要按升序排列一个数列。选择排序法先找到数列中最小的数,然后将它和第一个元素交换。接下来,在剩下的数中找到最小数,将它和第二个元素交换,依此类推,直到数列中仅剩一个数为止。下图显示如何使用选择排序法对数列{2,9,5,4,8,1,6}进行排序。

已经知道了选择排序法是如何工作的。现在的任务是用 Java 语言实现它。对初学者来说,很难在第一次尝试时就开发出完整的解决方案。开始编写第一次迭代的代码,找出数列中的最大数,将其与最后一个元素互换,然后观察第二次迭代与第一次的不同之处,接着是第三次,依此类推。通过这样的观察可以写出推广到所有迭代的循环。
for (int i = 0;i < list. length - 1; i++){
select the smallest element in list[i...list.length-1];
swap the smallest with list[i],if necessary;
// list[i] is in its correct position.
// The next iteration applies on list[i+l..list.length-1]
}
7.8 Arrays 类
java.util.Arrays 类包含一些实用的方法用于常见的數组操作,比如排序和查找。
java.util.Arrays 类包括各种各样的静态方法,用于实现数组的排序和査找、数组的比较和填充数组元素,以及返回数组的字符串表示。这些方法都有对所有基本类型的重载方法。
可以使用 sort 或者 parallelSort方法对整个数组或部分数组进行排序。例如,下面的代码对数值型数组和字符型数组进行排序。
double[] numbers = {6.0, 4.4, 1.9, 2.9, 3.4, 3.5};
java.util .Arrays.sort(numbers);// Sort the whole array
java.util .Arrays.parallelSort(numbers): // Sort the whole array
char[] chars = {'a', 'A', '4', 'F', 'D', 'P'};
java.util .Arrays.sort(chars, 1,3);// Sort part of the array
java.util .Arrays.parallelSort(chars, 1, 3); // Sort part of the array
可以调用sort(numbers)对整个数组 numbers 排序。可以调用sort(chars,1, 3)对从 chars[1]到 chars[3-1]的部分数组排序。如果你的计算机有多个处理器,那么 parallelSort 将更加高效。
可以采用二分査找法( binarySearch 方法)在数组中査找关键字。数组必须提前按升序排列好。如果数组中不存在关键字,方法返回 -( 插入点下标 +1)。例如,下面的代码在整数数组和字符数组中査找关键字:
int[] list = {2 , 4, 7, 10, 11, 45 , 50 , 59, 60, 66 , 69, 70, 79 } ;
System.out.println("l. Index is " +
java.util .Arrays.binarySearch(list, 11));
System.out.println("2. Index is " +
java.util .Arrays.binarySearch(list, 12));
char[] chars = {'a' , 'c' , 'g' , 'x' , 'y' , 'z' };
System.out.println("3. Index is " +
java.util .Arrays.binarySearch(chars, 'a'));
System.out.println("4. Index is " +
java.util .Arrays.binarySearch(chars, 't' ));
前面代码的输出为:
1. Index is 4
2. Index is — 6
3. Index is 0
4. Index is — 4
可以采用 equals 方法检测两个数组是否相等。如果它们的内容相同,那么这两个数组相等。在下面的代码中,listl 和 list2 相等,而 list2 和 list3 不相等。
int[] listl = {2 , 4 , 7, 10};
int[] list2 = {2 , 4 , 7 , 10};
int[] Tist3 = {4 , 2 , 7 , 10};
System.out. println(java.uti1.Arrays.equals(listl, list2)); // true
System.out. println(java.uti1.Arrays.equals(list2, list3)); // false
可以使用 fill 方法填充整个数组或部分数组。例如:下列代码将 5 填充到 list1 中,将 8 填充到元素 list2[1] 到 list2 [5-1] 中。
int[] listl = {2 , 4, 7, 10};
int[] list2 = {2 , 4 , 7, 7, 7, 10};
java.util .Arrays.fiil (list1, 5); // Fill 5 to the whole array
java.util .Arrays.fiil (list2, 1, 5, 8); // Fill 8 to a partial array
还可以使用 toString 方法来返回一个字符串,该字符串代表了数组中的所有元素。这是一个显示数组中所有元素的快捷和简便的方法。例如,下面代码
int[] list = {2, 4, 7, 10};
System.out.println(Arrays.toString(list));
显示 [ 2 , 4 , 7, 10]。
编译运算练习
1.数组遍历


2.数组线性查找


3.数组二分查找



4.数组扩容


5.数组选择排序


6.数组冒泡排序


7.数组插入(希尔)排序


455

被折叠的 条评论
为什么被折叠?



