数组
#概念
一个数组就是一组数据的序列,该序列中每个元素的类型相同,可以是基本类型,也可以是引用类型。如果是基本类型,每个数组元素所在的内存空间中存放的是基本类型的数值;如果是引用类型,每个数组元素所在的内存空间中存放的是引用。如图:
-
数组的定义形式(两种):
- int[] a1; 这种形式表明a1这个变量是数组变量,它的数组元素类型为int类型
- int a1[]; 这种形式表明a1[]数组元素的类型为int类型,a1是数组变量
不管哪种形式,我们在定义的时候都不能像C语言一样指定数组的大小,我们通过下面的这个例子,来进一步说明它们之间的区别:
int[] a, b, c; 这里我们可以知道a、b、c这三个变量均为数组变量 int a[], b, c; 这里我们知道只有a是数组变量,而b、c均为一般变量,而非数组变量
-
数组变量的意义:数组变量是引用类型的变量,这意味着,数组变量中存放的是数组的引用,而非数组本身,数组的存储空间是在初始化的时候在堆(所谓堆,大家可以理解做一个大仓库)中分配的,这一点同C语言有很大区别,这也成为Java数组的一个优势,数组的大小可以在运行的时候确定,而不必在定义的时候就确定下来。
-
数组的初始化:数组的初始化像其他类型的变量一样,既可以在定义的同时初始化,也可以在定义以后,在第一次使用的使用初始化。初始化的形式用两种:
-
int a[] = new int[10]; 这种形式,在堆中分配一段能够放下10个int类型数据的存储空间,并将其引用放在a这个数组变量中;
-
int a [] = { 1, 2, 3, 4, 5 }; 这种形式其实是把数组{ 1, 2, 3, 4, 5 }的引用放入了a中,而且这种形式只能在定义数组的同时进行。
- 如果数组元素为引用类型,有两种使用大括号的对数组初始化的形式:
public class ArrayInit { public static void main(String[] args) { Integer[] a = { new Integer(1), new Integer(2), new Integer(3), }; Integer[] b = new Integer[] { new Integer(1), new Integer(2), new Integer(3), }; } }
-
-
数组元素的引用:数组元素的引用也是通过下标进行的,下标可以是一个int类型的表达式,但是值的范围必须在
0
至数组大小-1
这个范围内。数组元素的类型既是定义数组时所指定的类型。
#多维数组
二维以上的数组就看作多维数组,数组在Java中的实现是采用链式存储实现的,如图:
多维数组的定义和初始化原则同一维是一样的,如下:
-
第一种形式,
int[][] a1 = { { 1, 2, 3}, { 4, 5, 6}, { 7, 8, 9} }; //每个向量用大括号括起来。
-
使用new定义a2的大小:
int[][][] a2 = new int[2][2][4];
由于在Java中采用链式存储数组,数组中向量的大小不必相同,比如:
int[][] a1 = {
{ 1, 2},
{ 3, 4, 5, 6},
{ 7, 8, 9}
};
甚至还可以如下例:
int b[][]; //定义一个二维数组
b = new int[ 2 ][ ]; // b引用一个具有两个子数组的数组
b[ 0 ] = new int[ 5 ]; // b[0]引用一个具有5个元素的数组
b[ 1 ] = new int[ 3 ]; // b[1]引用一个具有3个元素的数组
#数组作为方法的参数
方法的参数可以是数组,在使用数组参数时需要注意以下事项:
- 在形参表中,数组名后的方括号不能省略,方括号个数和数组的维数相等
- 实参表中,数组名后不需括号
- 参数是数组时,形参和实参传递的是引用
示例:
class A{
void f(int va[]){
for(int i = 0; i < va.length; i++)//va.length为va这个数组的大小
va[i]++;
}
public static void main(String args[]){
int[] aa = new int[10];
A ta = new A();
for(int i = 0; i < aa.length; i++)
aa[i] = i;
System.out.println("执行f()之前");
for(int i = 0; i < aa.length; i++)
System.out.print(aa[i] + " ");
//把aa作为实参传递给f方法
ta.f(aa); //f这个方法的调用必须使用对象,因为它是一个非静态方法
System.out.println("\n执行f()之后");
for(int i = 0; i < aa.length; i++)
System.out.print(aa[i] + " ");
}
}
#数组的复制
把一个数组中的内容复制到另一个数组不能使用赋值语句a = b
,这种形式使得a
引用和b
相同的数组。如果需要复制数组,我们可以使用System
类中的 arraycopy
方法,它的方法首部如下:
public static void arraycopy(Object src,
int srcPos,
Object dest,
int destPos,
int length)
从指定源数组src
中复制一个数组,从指定位置srcPos
开始,srcPos
到 srcPos+length-1
之间的length
个数组元素,到目标数组dest
的指定位置destPos
开始,destPos
到 destPos+length-1
位置。
如果参数 src
和 dest
引用相同的数组对象,则复制的执行过程就好像首先将 srcPos
到 srcPos+length-1
位置的元素复制到一个有 length
个元素的临时数组,然后再将此临时数组的内容复制到目标数组的 destPos
到 destPos+length-1
位置一样。
以下三种情况会抛出异常:
- 如果
src
或者dest
为null
,则抛出NullPointerException
异常。 - 只要下列任何情况为真,则抛出
ArrayStoreException
异常并且不会修改目标数组:src
参数不是数组对象。dest
参数不是数组对象。src
和dest
引用的数组元素的类型是不一致的基本类型。src
和dest
参数引用的数组的元素为一个为基本类型,另一个为引用类型
- 如果源数组中
srcPos
到srcPos+length-1
位置上的实际元素通过分配转换并不能全部转换成目标数组的元素类型,则抛出ArrayStoreException
异常。在这种情况下,假设复制过程已经进行到k
个(k < length)这么多,此时抛出异常,从srcPos
到srcPos+k-1
位置上的源数组元素已经被复制到目标数组中的destPos
到destPos+k-1
位置,而目标数组中的其他位置不会被修改。 - 只要下列任何情况为真,则抛出
IndexOutOfBoundsException
异常,并且不会修改目标数组:srcPos
、destPos
、length
参数为负。srcPos+length
大于src.length
,即源数组的长度。destPos+length
大于dest.length
,即目标数组的长度
#String与字符数组
在Java中字符数组不能当作字符串来看待,但是我们可以使用字符数组作为模板来创建字符串,如下:
char data[] = {'a', 'b', 'c'}; //这里data不能当作字符串
String str = new String(data); //str引用的既是字符串 "abc"
#对数组的操作
#对数组遍历
所谓遍历(Traversal),是指按照某种方式,依次对某种数据结构中的每个元素做一次且仅做一次的访问。对数组进行遍历通常可以使用循环语句,这里我们再介绍一个专门针对遍历的foreach语句,它的语法格式如下:
//这里type为被遍历结构中元素的类型名,x为结构中的元素,collection为被遍历的结构对象
for(type x : collection){
...//循环体
}
如下例:
int[] a = new int[10];
//这里为一般的for循环
for(int i = 0; i < a.length; i++) a[i] = i;
//这里为foreach语句
for(int x : a){//foreach语句中无法使用下标
System.out.print(x + " ");
}
#对数组的排序
对数组的排序,我们当然可以自己写出各种标准的排序算法,这里介绍一个工具类java.util.Arrays
(注意是复数)。此类包含用来操作数组(比如排序和搜索)的各种方法。除非特别注明,否则如果该类中的方法的数组参数引用值为 null
,则会抛出 NullPointerException
。
#升序排序
该类中有一系列对数组进行排序的方法,方法名为sort
,它的一系列重载实现,可以针对各种数组元素类型的数组进行升序排序。典型的,我们看下面的方法首部:
public static void sort(int[] a)
该方法对传入的 int
型数组a
按数字升序进行排序。该排序算法是一个经过调优的快速排序算法。
我们也可以只对数组中的某一部分进行排序,方法首部如下:
public static void sort(int[] a,
int fromIndex,
int toIndex)
该方法对传入的 int
型数组a
中从fromIndex
到toIndex-1
的元素按数字升序进行排序。同样,它也是一个经过调优的快速排序算法。 该方法可能会抛出下面的异常:
IllegalArgumentException
- 如果fromIndex > toIndex
ArrayIndexOutOfBoundsException
- 如果fromIndex < 0
或toIndex > a.length
上面的两个方法,经过重载,第一个参数可以是其他各种类型,包括基本类型和引用类型。
大家可能注意到了,上述的sort只能进行升序的排序,如果是其他复杂的排序方式,则就不适用了。
#带有 Comparator的排序
JDK为我们提供了强大的排序支持,因为涉及到一些我们尚未接触的知识,这里我先只做了解。
public static <T> void sort(T[] a, Comparator<? super T> c)
与
public static <T> void sort(T[] a,
int fromIndex,
int toIndex,
Comparator<? super T> c)
这两个的区别在于第一个对整个数组进行排序,第二个可以选择排序范围。
#数组元素的查找
对数组中元素进行查找,我们最简单但是效率可能最低下的方法就是对数组进行遍历。同样工具类java.util.Arrays
也为我们提供了可以直接使用的查找方法binarySearch
,该方法也有一系列的重载。使用该方法的前提,该数组必须是通过sort
进行过排序的。它的方法首部如下:
public static int binarySearch(int[] a, int key)
或者
public static int binarySearch(int[] a,
int fromIndex,
int toIndex,
int key)
这两个的区别在于第一个对整个数组进行排序,第二个可以选择排序范围。经过重载,第一个参数可以是其他各种类型,包括基本类型和引用类型。
方法中a
为被查找数组,key
是需要在此数组中查找的键值,fromIndex
为起始位置,toIndex-1
为终止位置。
如果key
值包含在数组中,则返回它的索引值;否则返回 (-(插入点) - 1
)。插入点
被定义为将键插入数组的那一点:即第一个大于此键的元素索引,如果数组中的所有元素都小于指定的键,则为 a.length
或者toIndex
,这保证了当且仅当此键被找到时,返回的值将 >= 0,否则为负值。
同样,该方法也有二个带有Comparator的方法重载,这里不再赘述。
关于工具类java.util.Arrays
中的其他方法,大家可以查看JDK说明书。
#问题
用筛法求1000以内的素数,并按每行10个输出出来。