数组的概述
数组可以看成多个相同类型数据的集合,对这些数据的统一管理。
数组变量属引用类型,数组也可以看成是对象,数组中的每个元素相当于对该对象的成员变量。
数组中的元素可以是任何数据类型,包括基本类型和引用类型。
(一)一维数组
(1)一维数组的声明
一维数组的声明方式:type var[ ];或type[ ] var;
package array;
import oop.Person;
/**
* 说明:数组声明举例
*
* @author huayu
* @date 2018/8/8 1:01 PM
*/
public class ArrayDemo {
//数组内装的是int类型的数据
int a[];
int[] b;
//数组内装的是double类型的数据
double c[];
//这里面装的是Person对象的引用
Person[] p;
//数组内装的是String对象的引用
String string[];
}
java语言中声明数组时不能指定其长度(数组中元素的个数),例如:int a[5]; //非法 因为java中的数组分布在堆上面。
(2)数组对象的创建及内存分析
1)java中使用关键字new 创建数组对象,格式为:数组名=new 数组元素的类型[数组元素的个数]
例如:
/**
* 说明:创建一个数组,并赋初值
*
* @author huayu
* @date 2018/8/8 1:15 PM
*/
public class CreateArrayDemo {
public static void main(String[] args) {
//声明数组array
1. int[] array;
//为数组array分配空间
2. array = new int[5];
//给数组赋值
3. for (int i = 0; i < array.length ; i++) {
array[i]=2*i+1;
System.out.println("数组下标index["+i+"]"+" array[i]= "+array[i]);
}
}
}
结果:
数组下标index[0] array[i]= 1
数组下标index[1] array[i]= 3
数组下标index[2] array[i]= 5
数组下标index[3] array[i]= 7
数组下标index[4] array[i]= 9
对于以上小demo进行内存分析:
执行第1句 int[] array; 这时是只声明了一个int类型的array数组,此时内存状态:
执行第2句array = new int[5]; 这时为声明了的array数组申请空间(new出来的东西放堆内存里面),并为数组初始化赋上了int的默认值0,而栈内存的array引用也有了值;此时内存状态:
执行到第3句for循环,为数组赋值,此时的内存状态:
2)元素是引用数据类型的数组:new出来的对象装的是引用。
注意:元素为引用数据类型的数组中的每一个元素都需要实例化。
package array;
/**
* 说明:自定义Date类
*
* @author huayu
* @date 2018/8/8 1:49 PM
*/
public class Date {
private int year;
private int month;
private int day;
public Date(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
}
import array.Date;
/**
* 说明:引用类型的数组
*
* @author huayu
* @date 2018/8/8 1:51 PM
*/
public class TestDate {
public static void main(String[] args) {
1. Date[] dates;
2. dates=new Date[3];
3. for (int i = 0; i <dates.length ; i++) {
dates[i]=new Date(1970,1,1);
System.out.println("数组下标index["+i+"]"+" dates[i]= "+dates[i].toString());
}
}
}
结果:
数组下标index[0] dates[i]= array.Date@511d50c0
数组下标index[1] dates[i]= array.Date@60e53b93
数组下标index[2] dates[i]= array.Date@5e2de80c
内存分析:
执行到第2句Date[] dates;内存状态(怎么来的我就不做过多解释了,看上面的栗子):
执行第3句for循环,每循环一次就创建一个新对象,执行完后的内存状态:
(3)数组初始化(时刻记得,数组要先分配空间再去赋值)
1)动态初始化:数组定义与为数组元素分配空间和赋值的操作分开进行,例如:
package array;
/**
* 说明:自定义Date类
*
* @author huayu
* @date 2018/8/8 1:49 PM
*/
public class Date {
private int year;
private int month;
private int day;
public Date(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
}
import array.Date;
/**
* 说明:动态初始化释例
*
* @author huayu
* @date 2018/8/8 1:51 PM
*/
public class TestDate {
public static void main(String[] args) {
int a[];
a = new int[3];
a[0] = 0;
a[1] = 1;
a[2] = 2;
for (int i = 0; i < a.length; i++) {
System.out.println("a["+i+"]= "+a[i]);
}
Date[] dates;
dates = new Date[3];
dates[0]=new Date(2018,1,1);
dates[1]=new Date(2018,2,2);
dates[2]=new Date(2018,3,3);
for (int i = 0; i < dates.length; i++) {
System.out.println("数组下标index[" + i + "]" + " dates[i]= " + dates[i].toString());
}
}
}
结果:
a[0]= 0
a[1]= 1
a[2]= 2
数组下标index[0] dates[i]= array.Date@511d50c0
数组下标index[1] dates[i]= array.Date@60e53b93
数组下标index[2] dates[i]= array.Date@5e2de80c
2)静态初始化:在定义数组的同时就为数组元素分配空间并赋值,例如
package array;
/**
* 说明:自定义Date类
*
* @author huayu
* @date 2018/8/8 1:49 PM
*/
public class Date {
private int year;
private int month;
private int day;
public Date(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
}
import array.Date;
/**
* 说明:静态初始化释例,它在执行过程其实还是向动态那样,只不过它现在给屏蔽掉,我们看不到而已
*
* @author huayu
* @date 2018/8/8 1:51 PM
*/
public class TestDate {
public static void main(String[] args) {
int a[] = {1, 2, 3};
for (int i = 0; i < a.length; i++) {
System.out.println("a[" + i + "]= " + a[i]);
}
Date[] dates = {
new Date(2018, 1, 1),
new Date(2018, 2, 2),
new Date(2018, 3, 3)
};
for (int i = 0; i < dates.length; i++) {
System.out.println("数组下标index[" + i + "]" + " dates[i]= " + dates[i].toString());
}
}
}
结果:
a[0]= 1
a[1]= 2
a[2]= 3
数组下标index[0] dates[i]= array.Date@511d50c0
数组下标index[1] dates[i]= array.Date@60e53b93
数组下标index[2] dates[i]= array.Date@5e2de80c
3)数组元素的默认初始化:数组是引用类型,它的元素相当于类的成员变量,因此数组分配空间后,每个元素也被按照成员变量的规则被隐式初始化,例如:
package array;
/**
* 说明:自定义Date类
*
* @author huayu
* @date 2018/8/8 1:49 PM
*/
public class Date {
private int year;
private int month;
private int day;
public Date(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
}
import array.Date;
/**
* 说明:数组元素默认初始化演示
* 1.int的类型的默认值是0(以前讲过各种类型变量的默认值,忘了的可以再去瞄一眼)
* 2.引用类型的默认值为null
* @author huayu
* @date 2018/8/8 1:51 PM
*/
public class TestDate {
public static void main(String[] args) {
int a[] = new int[3];
Date[] dates = new Date[3];
System.out.println(a[2]);
System.out.println(dates[2]);
}
}
结果:
0
null
(4)数组元素的引用(访问数组里面的内容)
1)定义并用运算符new为之分配空间后,才可以引用数组中的每个元素,数组元素的引用方式为:arrayName[index]
注:1.index为数组元素的下标,可以是整型常量或表达式。如a[1],a[i],a[2*i]
2.数组元素下标从0开始,长度为n的数组的合法下标取值范围为0~n-1
2)每个数组都有一个属性length指明它的长度,例如:array.length的值为数组array的长度(元素的个数)。上面例子已经用过了。
知识连接:数组没有 length()方法,有 length 的属性。String 有 length()方法。JavaScript 中,获得字符串的长度是通过 length 属性得到的,这一点容易和 Java 混淆。
(5)用数组写几个小程序
1)为乱序的int数组排序
以下这个程序自己对每一次的循环结果都在纸上画一下理解一下,后期我会整理一篇通过算法为数组排序的专题文章。
package array;
/**
* 说明:为a数组排序(简单选择)
* 先不考虑程序健壮性,只考虑功能的实现
* 这个方法后期完全可以被封装被优化,感兴趣的自己试一下
* @author huayu
* @date 2018/8/8 3:40 PM
*/
public class NumSort {
public static void main(String[] args) {
int[] a = {2, 1, 5, 3, 4, 9, 6, 8, 7};
for (int i = 0; i < a.length; i++) {
for (int j = i + 1; j < a.length; j++) {
if (a[j] < a[i]) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
System.out.print(a[i]+",");
}
}
}
结果:
1,2,3,4,5,6,7,8,9,
2)对引用(某个类的对象)类型的数据排序
package array;
/**
* 说明:自定义Date类
*
* @author huayu
* @date 2018/8/8 1:49 PM
*/
public class Date{
private int year;
private int month;
private int day;
public Date(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
/**
* 比较方法
* @param date
* @return
*/
public int compare(Date date) {
//想必大家看到这个三目运算符也懵逼了吧,三目运算符还可以这么玩,大家自己捋一下吧
//现实工作中别这么写,容易被祭天
return year > date.year ? 1
: year < date.year ? -1
: month > date.month ? 1
: month < date.month ? -1
: day > date.day ? 1
: day > date.day ? -1 : 0;
}
/**
* 重写toString方法是为了方便看输出来的结果
* @return
*/
@Override
public String toString() {
return "Date{" +
"year=" + year +
", month=" + month +
", day=" + day +
'}';
}
}
import array.Date;
/**
* 说明:日期输入1970年1月1日以后的
*
* @author huayu
* @date 2018/8/8 1:51 PM
*/
public class TestDateSort {
public static void main(String[] args) {
Date[] date = new Date[3];
date[0] = new Date(2001, 1, 1);
date[1] = new Date(1992, 4, 1);
date[2] = new Date(2096, 1, 5);
for (int i = 0; i < date.length; i++) {
System.out.println("排序前index[" + i + "]" + date[i]);
}
System.out.println();
Date[] dates = bubbleSort(date);
for (int i = 0; i < date.length; i++) {
System.out.println("排序后index[" + i + "]" + dates[i]);
}
}
public static Date[] bubbleSort(Date[] dates) {
int length = dates.length;
for (int i = length - 1; i >= 1; i--) {
for (int j = 0; j < i - 1; j++) {
if (dates[j].compare(dates[j + 1]) > 0) {
Date temp = dates[j];
dates[j] = dates[j + 1];
dates[j + 1] = temp;
}
}
}
return dates;
}
}
结果:
排序前index[0]Date{year=2001, month=1, day=1}
排序前index[1]Date{year=1992, month=4, day=1}
排序前index[2]Date{year=2096, month=1, day=5}
排序后index[0]Date{year=1992, month=4, day=1}
排序后index[1]Date{year=2001, month=1, day=1}
排序后index[2]Date{year=2096, month=1, day=5}
(5)数组的应用
释例一:从算法角度考虑,解决从围成一个圈的一群人中,1,2,3循环数数,数到3就退出圈,求最后还留在圈里的人的位置。
/**
* 说明:一群人,1,2,3轮着数数,数到三的自动离开
* 求最后剩下的人原来在什么位置
*
* @author huayu
* @date 2018/8/9 4:28 PM
*/
public class CountThreeQuit {
public static void main(String[] args) {
boolean[] arr = new boolean[500];
for (int i = 0; i < arr.length; i++) {
arr[i] = true;
}
//定义一个变量用来统计离开后,原队伍还剩下的人数
int leftCount = arr.length;
//用来记录数的数
int countNum = 0;
//用来记录下标位置
int index = 0;
//只要队伍中剩下的人多于1个就继续数数,数到3离开
while (leftCount > 1) {
if (arr[index] == true) {
countNum++;
if (countNum == 3) {
countNum = 0;
arr[index] = false;
leftCount--;
}
}
index++;
//如果数组下标到了数组的长度就将index置为0,数组从头开始再循环
if (index == arr.length) {
index = 0;
}
}
//将找到的位置输出出来
for (int i = 0; i < arr.length; i++) {
if (arr[i] == true) {
System.out.println(i);
}
}
}
}
结果:
435
释例二:从面向对象角度考虑,解决从围成一个圈的一群人中,1,2,3循环数数,数到3就退出圈,求最后还留在圈里的人的位置。
package array;
/**
* 说明:学生类
*
* @author huayu
* @date 2018/8/9 5:28 PM
*/
public class Student {
//学生的id
int sid;
//左边的学生
Student left;
//右边的学生
Student right;
}
package array;
/**
* 说明:500个学生围成一圈 学生圈类
*
* @author huayu
* @date 2018/8/9 7:01 PM
*/
public class StudentCircle {
int count = 0;
//开头的那个小孩
Student first;
//结束的那个小孩
Student last;
/**
* 构造方法
* @param n 构造多少人的圈
*/
StudentCircle(int n) {
//往圈里依次添加人
for (int i = 0; i < n; i++) {
add();
}
}
/**
* 添加方法:往圈里添加一个学生
*/
public void add() {
Student student = new Student();
//刚开始的id设置为0,为了跟数组下标结合
student.sid = count;
if (count <= 0) {//圈里一个学生都没有的时候
first = student;
last = student;
student.left = student;
student.right = student;
} else {//当圈里有学生的时候,从后面开始添加
last.right = student;
student.left = last;
student.right = first;
first.left = student;
last = student;
}
//添加到圈里后人数加1
count++;
}
/**
* 删除方法:圈添加完后,按照123数数,数到3的退出
* @param student
*/
public void delete(Student student) {
if (count <= 0) {
return;
} else if (count == 1) {
first = last = null;
} else {
/*想这儿的时候你用形象思维去考虑以下,想由学生围成圈是什么样的,该怎么离开圈
由一个学生为中心,分这个学生在什么位置的情况作退出圈的行为*/
//第一种情况,当要被删除的这个小孩不再头、尾的时候
student.left.right = student.right;
student.right.left = student.left;
//第二种情况,当要被删除的学生在第一个位置的时候
if (student == first) {
first = student.right;
} else if (student == last) {
//第三种情况,当这个要被删除的学生在最后一个位置的时候
last = student.left;
}
}
//退出圈后,人数减1
count--;
}
}
package array;
/**
* 说明:测试类
*
* @author huayu
* @date 2018/8/9 7:20 PM
*/
public class CountThreeQuit1 {
public static void main(String[] args) {
StudentCircle sc=new StudentCircle(500);
int countNum=0;
Student student=sc.first;
while (sc.count>1){
countNum++;
if(countNum==3){
countNum=0;
sc.delete(student);
}
student=student.right;
}
System.out.println(sc.first.sid);
}
}
结果:
435
解决这个问题的大致思路(如果是复杂的程序,分析过程要把面向对象设计原则考虑在内,这个问题先不去考虑那些):
对面向对象设计原则感兴趣的同学可以先看一下以下这一篇文章:
https://blog.csdn.net/Myuhua/article/details/81562759
释例三:
package array;
/**
* 说明:二分查找
* 谈到在数组中查找东西,脑海中先要想到先给数组排序
*
* @author huayu
* @date 2018/8/9 5:43 PM
*/
public class BinarySerach {
public static void main(String[] args) {
int[] arr = {2,4,5,1,9,7,6,8,3};
int num = 5;
System.out.println("排序前:");
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]);
}
System.out.println();
int[] arrSorted = arraySort(arr);
System.out.println("排序后:");
for (int i = 0; i < arrSorted.length; i++) {
System.out.print(arrSorted[i]);
}
System.out.println();
String index = binarySearch(arr, num);
System.out.println("被搜索数是:"+num+" 位置在:" + index);
}
/**
* int数组排序方法
*
* @param a int数组
* @return
*/
public static int[] arraySort(int[] a) {
for (int i = 0; i < a.length; i++) {
for (int j = i + 1; j < a.length; j++) {
if (a[i] > a[j]) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
}
return a;
}
/**
* 二分查找
*
* @param a 搜索数来自的数组
* @param num 要搜索的int值
* @return
*/
public static String binarySearch(int[] a, int num) {
if (a.length == 0) {
return "未找到,因为数组为空";
}
int startPosition = 0;
int endPosition = a.length;
int m = (startPosition + endPosition) / 2;
int count = 0;
while (startPosition <= endPosition) {
count++;
//如果正好m上的值就是被搜索的值直接返回下标m
if (num == a[m]) {
return "index[" + m +"]"+ " 执行了几次:" + count;
}
//如果被搜索的值大于m位置的数,则起始位置+1
if (num > a[m]) {
startPosition = m + 1;
}
//如果被搜索的值小于m位置的数,则结束位置+1
if (num < a[m]) {
endPosition = m - 1;
}
m = (startPosition + endPosition) / 2;
}
return "未找到";
}
}
结果:
排序前:
245197683
排序后:
123456789
被搜索数是:5 位置在:index[4] 执行了几次:1
(二)二维数组
1)二维数组可以看成以数组为元素的数组:例如:
int a[] [] = {{1,2},{3,4,5},{6.7,8,9}};
java中多维数组的声明和初始化按高维到低维(从左到右)的顺序进行,例如:
//合法声明
int a[][]=new int[3][];
a[0] = new int[2];
a[1] = new int[4];
a[2] = new int[3];
//非法声明
int a1[][]=new int[][3];
内存分布图:
2)二维数组初始化
1.静态初始化
//合法初始化
int intA[][]={{1,2},{2,3},{3,4,5}};
//非法初始化,前面的数java替你确定,它会自己给你做检查,不用写
int intB[3][2]={{1,2},{2,3},{4,5}};
2.动态初始化
//第一维是3个小格,第二维,每一个数组都是5个小格
int a[][]=new int[3][5];
int b[][]=new int[3][];
b[0]=new int[2];
b[1]=new int[3];
b[2]=new int[5];
package array;
/**
* 说明:打印二维数组
*
* @author huayu
* @date 2018/8/8 1:01 PM
*/
public class ArrayDemo {
public static void main(String[] args) {
int intA[][]={{1,2},{2,3},{3,4,5}};
for (int i = 0; i < intA.length; i++) {
for (int j = 0; j < intA[i].length; j++) {
System.out.println("a["+i+"]["+j+"]="+intA[i][j]+"");
}
}
}
}
(三)数组的拷贝
使用java.lang.System类静态方法
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
可以用于数组src从第srcPos项元素开始的length个元素拷贝到目标数组从destPos项开始的length个位置。
释例一:
/**
* 说明:数组拷贝释例
*
* @author huayu
* @date 2018/8/10 1:01 PM
*/
public class TestArrayCopy {
public static void main(String[] args) {
//拷贝一维int类型数组
int[] a={1,2,3,4,5};
int[] b=new int[a.length];
System.arraycopy(a,0,b,0,a.length);
for (int i = 0; i < b.length; i++) {
System.out.println("b["+i+"]="+b[i]);
}
//拷贝一维类型String数组
String[] str1={"yu","hua"};
String[] str2=new String[str1.length];
System.arraycopy(str1,0,str2,0,str1.length);
for (int i = 0; i < str2.length; i++) {
System.out.println("str2["+i+"]="+str2[i]);
}
//拷贝二维int类型数组
int[][] ints={{1},{2,3,4},{5}};
int[][] ints1=new int[ints.length][];
System.arraycopy(ints,0,ints1,0,ints.length);
for (int i = 0; i < ints1.length; i++) {
for (int j = 0; j < ints1[i].length; j++) {
System.out.println("ints1["+i+"]["+j+"]="+ints1[i][j]+" ");
}
}
//拷贝二维String类型数组
String[][] strings={{"yu"},{"hua"}};
String[][] strings1=new String[str2.length][];
System.arraycopy(strings,0,strings1,0,strings.length);
for (int i = 0; i < strings1.length; i++) {
for (int j = 0; j < strings1[i].length; j++) {
System.out.println("strings1["+i+"]["+j+"]="+strings1[i][j]+" ");
}
}
}
}
结果:
b[0]=1
b[1]=2
b[2]=3
b[3]=4
b[4]=5
str2[0]=yu
str2[1]=hua
ints1[0][0]=1
ints1[1][0]=2
ints1[1][1]=3
ints1[1][2]=4
ints1[2][0]=5
strings1[0][0]=yu
strings1[1][0]=hua
释例二:对以下释例进行内存分析
/**
* 说明:实验数组赋值是操作的内存中同一块位置
*
* @author huayu
* @date 2018/8/10 1:01 PM
*/
public class TestArrayCopy {
public static void main(String[] args) {
//拷贝二维int类型数组
int[][] ints={{1},{2,3,4},{5}};
for (int i = 0; i < ints.length; i++) {
for (int j = 0; j < ints[i].length; j++) {
System.out.println("未修改ints1的值时ints["+i+"]["+j+"]="+ints[i][j]+" ");
}
}
int[][] ints1=new int[ints.length][];
System.arraycopy(ints,0,ints1,0,ints.length);
ints1[1][2]=5;
for (int i = 0; i < ints1.length; i++) {
for (int j = 0; j < ints1[i].length; j++) {
System.out.println("ints1["+i+"]["+j+"]="+ints1[i][j]+" ");
}
}
for (int i = 0; i < ints.length; i++) {
for (int j = 0; j < ints[i].length; j++) {
System.out.println("修改ints1的值后ints["+i+"]["+j+"]="+ints[i][j]+" ");
}
}
}
}
结果:
未修改ints1的值时ints[0][0]=1
未修改ints1的值时ints[1][0]=2
未修改ints1的值时ints[1][1]=3
未修改ints1的值时ints[1][2]=4
未修改ints1的值时ints[2][0]=5
ints1[0][0]=1
ints1[1][0]=2
ints1[1][1]=3
ints1[1][2]=5
ints1[2][0]=5
修改ints1的值后ints[0][0]=1
修改ints1的值后ints[1][0]=2
修改ints1的值后ints[1][1]=3
修改ints1的值后ints[1][2]=5
修改ints1的值后ints[2][0]=5
通过输出结果我们清楚的看出在对数组ints复制成ints1后又修改了ints1[1][2]=5;的值,
对原来的ints数组作输出发现原来被复制数组ints的值也发生了变化,
说明复制数组ints1操作的是跟ints数组一样都是同一块内存。
内存分析图:
注意:如果源数据目标超过目标数组边界会抛出IndexOutOfBoundsException异常。
若有问题欢迎大家与我互动交流,可评论,可留言,以后每周我会坚持至少更新一篇博客文章,喜欢的朋友可以加一下关注。