在程序的执行过程中通常会需要执行大量的数据,比如要读取100个数来求得平均值这个时候则会想到要把这100个数分别用变量先保存起来这样就需要在程序中声明100个变量,这是非常不方便的。
Java与其它的高级语言一样也提供了一种称作为数组的数据结构,用它要以用来存储一个元素个数固定且元素类型相同的有序集,那么如说的就要以用一个数组把这100个数存储在一个数组当中然后通过一个一维数组的变量访问它。
数组的作用:
它是用来存储数据的集合,存储的数有相同的类型
声明数组变量:
在程序中要使用数组则要声明一个引用数组的变量,并且要指明数组的元素类型
元素类型[] arrRefVar;
这里的元素类型可以是任意的数据类型,但要注意的就是数组中的各个元素的类型都要是一样的
double[] myList;
创建数组:
声明一个数组变量时并不在内存中给数组分配内存空间,只是创建一个对数组的引用的存储位置,如果变量不包含对数组的引用则这个变量的值就是null,除非数组是已创建要不然不能给它分配任何元素,声明数组变量之后可以通过下面的语法来创建数组
arrayRefVar = new 元素类型[数组大小];
这里的过程就是:1,使用new来创建一个数组;2,把这新创建的数组的变量赋值给变量arrayRefVar
其实对于声明一个数组变量、创建数组、把数组引用赋值给变量三个步骤是可以一起进行的
elementType[] arrayRefVar = new elementType[arraySize];
对于上面的例子我们可以创建一个数组并赋值给变量
double[] myList = new double[100];
上面的语句是创建了数组及数组变量但并未对数组中的元素进行初始化,初始化的过程可以通过数组的下标(从0开始)一个一个元素进行赋值
myList[0] = 1.0;
myList[1] = 2.0;
myList[2] = 3.0;
…
myList[99] = 100.0;
一个数组变量比如上面的myList看起来是存储的数组,但实际上是存储的是指向数组的引用
数组的大小及默认值:
对一个数组分配空间时必须通过指定这个数组能够存储的元素的个数来确定数组的大小,一旦数组创建了就不可以改变这个大小了。
或以使用数组变量的属性length来得到数组的大小比如myList.lengthà100
在创建数组后如果元素没有被赋值则会给定它们默认的初始值:
对基本类型来说是0,char类型是’\u0000’,boolean类型是false
数组的下标:
数组中的元素可以通过下标来进行访问,数组的下标是基于0的也就是说它的范围是从0到数组的长度-1为止那么数组中的每一个元素可以通过arrayRefVar[index];来取得
数组的初始化语法:
在Java当中可以使用下面的形式来进行初始化数组并创建它
elementType[] arrayRefVar = {value0,value1,…,valuek};
这个时候不需指定它的长度Java会自动根据后面初始化的元素个数来判断数组的长度,而且一定要注意的就是也不使用new运算符进行创建
处理数组:
通常对数组的处理会使用for循环进行
1, 数组中的元素都是同一类型,这样可以使用循环来反复处理这些数据
2, 由于数组的大小已定则最为方便的不是使用for
我们看一个打乱数组中的元素的方法:
这在应用中是常常会用到的比如我们要从一组数池中产生一些随机数生成一个数组则可以先把这个数池打乱然后取前面的n个元素就可以了
打乱的算法:
1, 定义一个数组池 a
2, 随机生成一个从0到a.length-1之前的一个数字j,并把当前的数组元素与a[j]进行交换
3, 循环这个数组中的每一个元素执行2
数组中常常我们还会使用到移动一个元素,这种对于数组来说效率不高但是也常用,每一个元素进行相应的下移并且使用一个中间变量来进行交换数据
double temp = myList[0];
for(int i = 1;i<myList.length.length;i++){
myList[i-1] = myList[i];
}
myList[myList.length-1] = temp;
Java中的for-each循环:
这是Java中的一种简单的for循环,也叫增强for循环
for(double u:myList){
System.out.println(u);
}
这种循环不需要记录数组的个数来进行一个一个循环而是每次循环会自动遍历下一个数组元素,这种循环只能是从下标0开始依次循环如果要以其它的顺来循环的话则要使用下标变量来循环
注意:Java中的下标范围:[0,数组长度-1]
下面的程序我们从一副牌中取到随机的四张牌
import java.util.Random;
publicclass RanomOfCards {
publicstaticvoid main(String[] args) {
// 声明一副牌的数组
int[] deck = newint[52];
// 声明一个数组存储牌的花色
String[] suits = { "♠", "♥", "♣", "♦" };
// 声明一个数组来存储牌面大小
String[] ranks = { "Α", "2", "3", "4", "5", "6", "7", "8", "9", "10",
"J", "Q", "K" };
// 初始化一副牌
for (int i = 0; i < deck.length; i++)
deck[i] = i;
// 打乱这整个牌
for (int i = 0; i < deck.length; i++) {
Random random = new Random();
int index = random.nextInt(deck.length); // 取到一个随机的下标
// 把取到的随机下标对应的牌值与当前循环的这个牌值进行交换
int temp = deck[i];
deck[i] = deck[index];
deck[index] = temp;
}
// 随机取出4张牌
for (int i = 0; i < 4; i++) {
String suit = suits[deck[i] / 13];
String rank = ranks[ranks.length - 1];
rank = ranks[deck[i] % 13 - 1];
System.out.println(" 你的第" + (i + 1) + "张牌是:" + suit + " " + rank);
}
}
}
这里用到了使用牌数/13à花色(0~3的下标)
牌数%13à牌面(0~12的下标)
在程序当中通常会要复制一个数组或是数组的一部分,这时可能会使用赋值语句=
比如:list2 = list1;
这条语句并不是把list1引用的数组的内容赋给list2,因为list1当中存储的是对数组的引用所以这个时候是把list1这个引用赋给了list2这时list1与list2指向的是同一个数组,list2原本指向的数组这时没有被引用这时JVM的垃圾回收器会把原来list2指向的数组看作垃圾而被回收
上面的这种做法是把一个数组的引用复制给另一个数组变量,使这两个变量都指向同一个数组
复制数组有三种方法:
1, 使用循环语句逐个地复制数组的元素
2, 使用System类中的静态方法arraycopy
3, 使用clone方法复制数组
如下使用循环把sourceArray中的元素复制到targetArray
int[] sourceArray = {1,2,3,4,5};
int[] targetArray = new int[sourceArray.length];
for(int i = 0;i<sourceArray.length){
targetArray[i] = sourceArray[i];
}
可以使用java.lang.System类中的arraycopy方法来复制数组中的元素
arraycopy(sourceArray,src_pos,targetArray,tar_pos,length);
其中参数说明:
src_pos:指定了源数组中的起始位置
tar_pos:指定了目标数组中的起始位置
length:指定了从sourceArray数组中复制到targetArray数组中元素的个数
所以对于上面的循环可以使用这个方法来代替:
System.arraycopy(sourceArray,0,targetArray,0,sourceArray.length);
进行如上两种方法进行复制数组时得先创建目标数组并且为其分配内存空间,复制完成后它们都有相同的数组元素内容,并且各自有自己的内存空间
为方法传递数组:
对于方法来说可以传递基本数据类型,其它也可以传递数组
public static void printArray(int[] arry){
for(int i = 0;i<array.length;i++)
System.out.print(arry[i]+” ”);
}
那么这个时候就可以通过传递一个数组调用上面的方法如:
printArray(new int[]{3,2,1,6,7,8});
这个调用中的实参我们可以看到我们直接new了一个数组这个数组没有显式地引用变量,这样的数组我们称之为匿名数组
Java使用的是值传递的方式,对于这里这种传递则与基本类型的传递参数有很大的区别
对于基本数据类型参数,传的是实传的值
对于数组类型实参,参数值是数组的引用,这里把引用传过去了则方法中的数组和传递的数组是同一个这个时候如果改变方法中的数组则方法外的话数组也改变了
可以看一下如下的不同就可以体会到:
publicclass DifferentPassParamater {
publicstaticvoid main(String[] args) {
int x = 1; // 基本数据类型
int[] y = newint[10]; // y是一个对数组的引用类型
// 执行相应的方法分别对传过去的参数进行修改
m(x, y);
// 显示执行上述方法后声明的变量的变化情况
System.out.println("x的最后结果是:" + x);
System.out.println("y[0]的最后结果是:" + y[0]);
}
privatestaticvoid m(int x, int[] y) {
x = 1000;
y[0] = 1000;
}
}
通过这里的结果可以看到在调用m后x依然是1而y[0]则变为了1000
这是因为在传递一个数组后形参变量就得到了数组的引用因而指向了同一个数组在其上进行相应的修改当然会使用原来的数组产生相应的变化。
JVM把数组存储在一个称为堆的内存区域当中,堆用于动态的内存分配,在堆当中内存块可以近按随意的顺序进行分配和释放
publicclass PassArray {
publicstaticvoid main(String[] args) {
int[] a = { 1, 2 }; // 定义一个数组
System.out.println("在执行交换之前数组的两个元素分别是:");
System.out.println("a[0] = " + a[0] + "," + "a[1]" + " = " + a[1]);
// 执行方法
swap(a[0], a[1]);
// 显示执行后的方法
System.out.println("执行swap方法后的结果:");
System.out.println("a[0] = " + a[0] + "," + "a[1]" + " = " + a[1]);
swap1(a);
// 执行传数组引用的方法后
System.out.println("执行swap1方法后的结果:");
System.out.println("a[0] = " + a[0] + "," + "a[1]" + " = " + a[1]);
}
privatestaticvoid swap1(int[] a) {
int temp = a[0];
a[0] = a[1];
a[1] = temp;
}
privatestaticvoid swap(int i, int j) {
int temp = j;
j = i;
i = temp;
}
}
这里第一个方法是swap它没有完成真正的交换两个数组的元素是因为它传过去的是基本数据类型,而swap1进行了两个数组元素的互换是因为它传过去的是一个数组的引用这时在方法内的变量与传过来的变量都是指向的同一个数组的引用因而对其进行相应的操作会引起方法外的变量的更改
从方法返回数组:
给方法可以传递数组而且可以在方法中返回数组
比如下面的方法,把传入的一个数组进行反转并返回一个新的数组,原来传入的数组并没有改变:
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] = list[i];
return result;
}
如下面的程序生成100个小写字母,并把它放入一个字符数组中,并对数组中每个字母出现的次数进行相应的统计
import java.util.Random;
publicclass CountLettersInArray {
publicstaticvoid main(String[] args) {
//生成一个字符数组数组中每一个为一个小写字母
char[] chars = createArray();
//显示产生的字符数组中的内容
System.out.println("生成的字符数组为:");
displayArray(chars);
//记录生成的小写字母中每个字母的个数
int[] counts = countLetters(chars);
//显示统计个数的结果
System.out.println("统计每一个字母在字符数组中出现的个数:");
displayCounts(counts);
}
privatestaticvoid displayCounts(int[] counts) {
for(int i = 0;i<counts.length;i++)
if((i+1)%10==0)
System.out.println(counts[i]+" - "+(char)(i+'a'));
else
System.out.print(counts[i]+" - "+(char)(i+'a')+" ");
}
privatestaticint[] countLetters(char[] chars) {
int[] counts = newint[26];
for(int i=0;i<chars.length;i++)
counts[chars[i]-'a']++;
return counts;
}
privatestaticvoid displayArray(char[] chars) {
for(int i = 0;i<chars.length;i++)
if((i+1)%20==0)
System.out.println(chars[i]);
else
System.out.print(chars[i]+" ");
}
privatestaticchar[] createArray() {
char[] chars = newchar[100];
for(int i = 0;i<chars.length;i++)
chars[i] = getRandomLowerCaseLetter();
return chars;
}
privatestaticchar getRandomLowerCaseLetter() {
//随机生成一个小写母
Random random = new Random();
char a = (char)(random.nextInt(26)+'a');
return a;
}
}
可变长参数列表:
可以把类型相同但是个数可变的参数传递给方法
typeName…parameterName
在方法的声明中指定类型后跟着省略号(…),注意只能给方法指定一个可变长的参数,而且必须是最后一个参数,任何一个常规的参数必须要它的前面。Java把这种可变长的参数当成一个数组来对待,可以把一个数组或是可变长的参数个数传给可变长参数,当使用可变长参数个数来调用方法的时候Java会创建一个数组并把参数传给它
publicclass VarArgsDemo {
publicstaticvoid main(String[] args) {
printMax(34, 3, 5, 6, 23, 56.9);
printMax(newdouble[]{1,7,23.9});
printMax(newdouble[]{});
}
privatestaticvoid printMax(double... numbers) {
if (numbers.length == 0) {
System.err.println("传过来的参数不正确!");
return;
}
double result = numbers[0];
for (int i = 1; i < numbers.length; i++)
if (numbers[i] > result)
result = numbers[i];
System.out.println("传过来的数据中最大的值是:" + result);
}
}
数组的查找:
查找,就是在数组中找到相应给定的数组的元素所在的位置的一个过程
线性查找法:
这种方法是把要查找的key与数组中的元素一个一个进行比较,这种查找则持续到在列表中长到相应的key或是查到最后并未找到数组中与key有匹配的为止
publicclass LineSearch {
publicstaticvoid main(String[] args) {
int[] list = { 1, 2, -5, 6, -9 };
int key = -5;
System.out.println("查找" + key + "在数组中的位置是:" + linerSearch(list, key));
}
privatestaticint linerSearch(int[] list, int key) {
for (int i = 0; i < list.length; i++)
if (key == list[i])
return i + 1;
return -1;
}
}
如上的过程就是一个简单的线形查找的例子
线形查找的执行时间会随着元素个数的增长而线性增长,所以对于大数组来说的话一般不会使用线形查找这样做效率不高
二分查找:
要进行二分查找的前提是这个数组是经过排好序的
现在假定数组已按升序来进行排列了,先找到中间的元素进行比较
1, 如果key小于中间元素则只需要在数组的前一半元素中查找
2, 如果key与中间元素相等则匹配成功不需再进行查找
3, 如果key大于中间元素则只需要在数组的后一半元素中查找
明显二分查找每次查找就会排除掉一半的数组元素
import java.util.Arrays;
publicclass BinarySearch {
publicstaticvoid main(String[] args) {
System.out.println(binarySearch(newint[]{2,8,10,16,17,90},2));
}
publicstaticint binarySearch(int[] list, int key) {
int low = 0;
int high = list.length - 1;
while (high >= low) {
int mid = (low + high) / 2;
if (key == list[mid])
return mid;
elseif (key < list[mid])
high = mid - 1;
else
low = mid + 1;
}
return -1;
}
}
做这个二分查找的时候可以分为两步:
先做第一步的迭代过程
int mid = (low+high)/2;
if(key== list[mid])
reutrn mid;
else if(key<list[mid])
high = mid-1;
else
low = mid+1;
接下来要做的第二步是我们可能在第一次得到最后的结果则要再次进入迭代,所以迭代的次数是不定的可以用一个while循环
while(high>=low){
int mid = (low+high)/2;
if(key== list[mid])
reutrn mid;
else if(key<list[mid])
high = mid-1;
else
low = mid+1;
}
return -1;//没有找到的情况
通过上面的两种查找来说,线性查找适用于小数组或没有排序的数组,而大数组适用于已排序的数组
数组的排序:
排序也是计算机中一个常用的任务
选择排序:(假设是升序)
找到数列中最小的数,然后把它放在数列的最前面,接下来在剩下的数中找到最小的数,把它放到第一个数的后面至到数列中只剩下一个数为止
publicclass SelectionSort {
publicstaticvoid main(String[] args) {
System.out.println(Arrays.toString(selectionSort(newdouble[] { 0, 5,4, 1, 8, 2, 3 })));
}
publicstaticdouble[] selectionSort(double[] list) {
for (int i = 0; i < list.length - 1; i++) {
double currentMin = list[i];
for (int j = i + 1; j < list.length; j++) {
if (currentMin > list[j]) {
currentMin = list[j];
double t = list[i];
list[i] = list[j];
list[j] = t;
}
}
}
return list;
}
}
插入排序:
publicclass InsertSort {
publicstaticvoid main(String[] args) {
System.out.println(Arrays.toString(selectionSort2(newdouble[] { 4.6,
1, 0, 3, 5, 1, 7 })));
}
privatestaticdouble[] selectionSort2(double[] ds) {
for (int i = 1; i < ds.length; i++) {
//待插入的元素从1开始到最后一个
double d = ds[i]; //先记录下当前待插入的元素
int j;
for (j = i - 1; j >= 0 && ds[j] > d; j--) {
ds[j + 1] = ds[j]; //如果已排序的元素组中有元素大于当前待插入的数则住后移
}
//把待插入的元素进插入到相应的位置
ds[j + 1] = d;
}
return ds;
}
}
Arrays类:
为了实现对数组的提排序、查找、数组的比较和对数组元素的填充java.util.Arrays提供了很多的静态方法,这些方法都有对所有基本类型的重载方法
sort方法可以对数组整体或是部分进行排序
比如:
Arrays.sort(numbers);//这个是对整个numbers数组进行排序
Arrays.sort(numbers,1,3);//这个是对数组中number[1]到number[3-1]的部分进行排序,注意在Java当中都是包前不包后的
binarySearch方法:使用二分查找查找关键字,这里的前提就是进行了数组的排序
如果查找的时候不存在相应的键值则会返回-(插入点下标+1)