1. 数组概述
Java语言中的数组是一种引用数据类型。不属于基本数据类型。数组的父类是Object
- 数组实际上是一个容器,可以容纳多个元素(数组是相同类型数据的有序集合)
- 数组当中可以存储“基本数据类型”的数据,也可以存储“引用数据类型”的数据
- 因为数组是引用数据类型,所以数组对象是在堆内存当中
- 数组描述的是相同类型的若干个数据,按照一定的先后次序排列组合而成其中,每一个数据称作一个数组元素,每个数组元素可以通过一个下标来访问它们。
1.1 数组的四个基本特点
-
其长度是确定的。数组一旦被创建,它的大小就是不可以改变的
-
其元素必须是相同类型,不允许出现混合类型
例如:int类型数组只能存储int类型,Person类型数组只能存储Person类型
-
数组中的元素可以是任何数据类型,包括基本类型和引用类型
-
数组变量属于引用类型,数组也可以看成是对象,数组中的每个元素相对于该对象的成员变量。
数组本身就是对象,Java中对象是在堆中的,因此数组无论保存原始类型还是其他对象类型,数组对象本身是在堆中的
1.2 数组的分类
- 所有的数组对象都有length属性(java自带的)用来获取数组中元素的个数
1.3 数组内存图
- 数组当中如果存储的是“Java对象”的话,实际上存储的是对象的“引用(内存地址)”,数组不能直接存储java对象
- 数组一旦创建,在java中规定,长度不可变
数组存储元素的特点:
- 数组在内存方面存储的时候,数组中的元素内存地址(存储的每一个元素都是有规则的挨着排列的)是连续的。内存地址连续
- 数组实际上是一种简单的数据结构
所有的数组都是拿“第一个小方框的内存地址”作为整个数组对象的内存地址。(数组中首元素的内存地址作为整个数组对象的内存地址)
原因:数组存储元素的时候,内存地址是连续的,知道了第一个元素的内存地址,
可以算出第二个,以此类推,算出所有的元素内存地址
- array01数组名存储的是数组对象的地址,放在栈中
- 而数组对象是连续存储在堆内存中
- 原因:定义数组时为:数据类型[] 数组名 = new 数据类型[]{1,2,3} 也就是把对象的首地址赋值给数组名
1.4 数组在计算机中的执行原理
1.2.1 Java程序的执行原理
程序是在内存中执行的。实际上Java程序是把编译后的字节码加载到Java虚拟机中执行的
- Java为了便于虚拟机执行Java程序,将虚拟机的内存划分为 方法区、栈、堆、本地方法栈、寄存器 这5块区域。
需要重点关注的是 方法区、栈、堆。
- 方法区:字节码文件先加载到这里
- 栈:方法运行时所进入的内存区域,由于变量在方法中,所以变量也在这一块区域中
- 堆:存储new出来的东西,并分配地址。由于数组是new 出来的,所以数组也在这块区域。
public class ArrayDemo1 {
public static void main(String[] args) {
int a = 10;
System.out.println(a);
int[] arr = new int[]{11, 22, 33};
System.out.println(arr);
System.out.println(arr[1]);
arr[0] = 44;
arr[1] = 55;
arr[2] = 66;
System.out.println(arr[0]);
System.out.println(arr[1]);
System.out.println(arr[2]);
}
}
// 这里的int a是一个基本类型变量,在栈内存中,a变量存储的是一个数值
int a = 10 ;
//这里的int[] arr是一个引用类型的变量,在栈中,存储的是一个地址值
int[] arr = new int[]{44,55,66};
1.2.2 多个变量指向同一个数组的问题
public class ArrayDemo2 {
public static void main(String[] args) {
// 目标:认识多个变量指向同一个数组对象的形式,并掌握其注意事项。
int[] arr1 = {11, 22, 33};
// 把int类型的数组变量arr1赋值给int类型的数组变量arr2
int[] arr2 = arr1;
System.out.println(arr1);
System.out.println(arr2);
arr2[1] = 99;
System.out.println(arr1[1]);
arr2 = null; // 拿到的数组变量中存储的值是null
System.out.println(arr2);
//System.out.println(arr2[0]);
//System.out.println(arr2.length);
}
}
- 两个变量指向同一个数组时,两个变量记录的是同一个地址值。
- 当一个变量修改数组中的元素时,另一个变量去访问数组中的元素,元素已经被修改过了
1.5 数组边界
- 数组中每一个元素都是有下标的,下标从0开始,以1递增,最后一个元素的下标是:length-1
- 下标的合法区间:[0,length-1],如果越界就会报错;
- 我们对数组中的元素进行存取的时候,都需要通过下标来进行
public class ArrayDemo03 {
public static void main(String[] args) {
int[] a = new int[2];
System.out.println(a[2]);
}
}
java.lang.ArrayIndexOutOfBoundsException:数组下标越界异常
小结
- 数组是相同数据类型(数据类型可以为任意类型)的有序集合
- 数组也是对象。数组元素相对于对象的成员变量
- 数组长度是确定的,不可变的。如果越界,则报错ArrayIndexOutOfBounds
1.6 数组这种数据结构的优缺点?
1.5.1 优点
查询 / 查找 / 检索某个下标上的元素时效率极高。可以说是查询效率最高的一个数据结构
-
原因一:每一个元素的内存地址在空间存储上是连续的
-
原因二:每一个元素类型相同,所以占用空间大小一样
-
原因三:知道第一个元素内存地址,知道每一个元素占用空间的大小,又知道下标,所以通过一个数学表达式就可以计算出某个下标上元素的内存地址。直接通过内存地址定位元素,所以数组的检索效率最高的
数组中存储100个元素,或者存储100万个元素,在元素查询/检索方面,效率是相同的, 因为数组中元素查找的时候不会一个一个找,是通过数学表达式计算出来的 (算出内存地址,直接定位)
1.5.2 缺点
- 第一:由于为了保证数组中每个元素的内存地址连续,所以在数组上随机删除或者增加元素的时候,效率较低,因为随机增删元素会涉及到后面元素统一向前或者向后位移的操作
- 第二:数组不能存储大数据量,因为很难在内存空间上找到一块特别大的连续的内存空间
注意:对于数组中最后一个元素的增删,是没有效率影响的
2. 一维数组
2.1 声明一维数组
首先必须声明数组变量,才能在程序中使用数组。
下面是声明数组变量的语法:
dataType[] arrayRefVar; //首选的方法
int[] array1
double[] array2
boolean[] array3
String[] array3
Object[] array5
或
dataType arrayRefVar[]; //效果相同,但不是首选方法
2.2 创建一维数组
声明完数组之后对数据进行创建,分配内存空间
arrayRefVar = new dataType[空间大小];
Java语言也可以使用new操作符将声明和创建二合一创建数组,语法如下:
dataType[] arrayRedVar = new dataType[arraySize];
数组的元素是通过索引访问的,数组索引从0开始
获取素组的长度:
arrays.length
代码里面声明创建数组
package com.pudding.array;
public class ArrayDemo01 {
//变量的类型 变量的名字 = 变量的值;
//数组类型
public static void main(String[] args) {
int[] nums; //1. 声明一个数组
// int nums[];
//分配空间:这里面可以存放10个int类型的数字
nums = new int[10]; //2. 创建数组
//声明和创建合二为一
// int[] nums = new int[10];
//3. 给数组元素中赋值
nums[0] = 1;
nums[1] = 2;
nums[2] = 3;
nums[3] = 4;
nums[4] = 5;
nums[5] = 6;
nums[6] = 7;
nums[7] = 8;
nums[8] = 9;
nums[9] = 10;
//计算所有元素的和
int sum = 0;
//获取数组长度:arrays.length
for (int i = 0; i < nums.length; i++) {
sum = sum + nums[i];
}
System.out.println("总和为:"+sum);
}
}
2.3 初始化一维数组
1. Java内存分析:
2. 三种初始化状态:
1. 静态初始化:
int[] a = {1,2,3};
Man[] mans = {new Man(1,1), new Man(2,2)};
//完整格式:
数据类型[] 数组名 = new 数据类型[]{元素1,元素2,元素3… };
int[] ages = new int[]{12, 24, 36}
//简化风格
数据类型[] 变量名 = {元素1,元素2,元素3};
//定义数组,用来存储多个年龄
int[] ages = {12, 24, 36}
C++风格和Java风格:
C++风格不建议使用:int a[] = {1,100,10,20,55};
Java风格: int[] a = {1,100,10,20,55};
public class ArrayTest01 {
public static void main(String[] args) {
//声明一个int类型的数组,使用静态初始化的方式
int[] a = {1,100,10,20,55,689};
//所有的数组对象都有length属性
System.out.println("数组中元素的个数"+a.length);
//数组中每一个元素都有下标
//通过下标对数组中的元素进行存和取
//取(读)
System.out.println("第一个元素 = "+a[0]);
System.out.println("最后一个元素 = "+a[a.length - 1]);
//存(改)
//把第一个元素修改为111
a[0] = 111;
//把最后一个元素修改为0
a[a.length-1] = 0;
System.out.println("第一个元素 = "+a[0]);
System.out.println("最后一个元素 = "+a[5]);
//一维数组怎么遍历?
for (int i = 0; i < a.length; i++) {
System.out.println(a[i]); //i是0到5,是下标
}
//下标为6表示第7个元素,下标越界了,会出现什么异常?
// System.out.println(a[6]); //ArrayIndexOutOfBoundsException 数组下标越界异常
//从最后一个元素遍历到第一个元素
for (int i = a.length - 1; i > 0; i--) {
System.out.println("颠倒顺序输出-->" + a[i]);
}
}
}
2. 动态初始化:
动态初始化不需要我们写出具体的元素,而是指定元素类型和长度就行。格式如下
//数据类型[] 数组名 = new 数据类型[长度];
int[] arr = new int[3];
int[] a = new int[2]; //这个的2表示数组的元素个数
//初始化一个5个长度的int类型数组,每个元素默认值0
a[0] = 1;
a[1] = 2;
String[] names = new String[6]; //初始化6个长度的String类型数组,每个元素默认值null
public class ArrayTest02 {
public static void main(String[] args) {
//声明/定义一个数组,采用动态初始化的方式创建
int[] a = new int[4]; //创建长度为4的int数组,数组中每一个元素的默认值是0
//遍历数组
for (int i = 0; i < a.length; i++) {
System.out.println("数组中下标为:"+i+"的元素为"+a[i]);
}
//后期赋值,注意下标别越界
a[0] = 1;
a[1] = 2;
a[2] = 3;
a[3] = 4;
//初始化一个Object类型的数组,采用动态初始化方式
Object[] obj = new Object[3]; //3个长度,动态初始化,所有每个默认值是null
for (int i = 0; i < obj.length; i++) {
System.out.println(obj[i]);
}
//采用静态初始化数组的方式
Object o1 = new Object();
Object o2 = new Object();
Object o3 = new Object();
Object[] objects = {o1,o2,o3};
for (int i = 0; i < objects.length; i++) {
System.out.println(objects[i]); //println默认调用toString方法,
// 输出java.lang.Object@3f99bd52
//java.lang.Object@4f023edb
//java.lang.Object@3a71f4dd
}
//等价于
//Object[] object = {new Object(),new Object(), new Object()};
System.out.println("=========================");
String[] strs = new String[3]; //动态初始化数组
for (int i = 0; i < strs.length; i++) {
System.out.println(strs[i]);
}
//采用静态初始化的方式
String[] strs2 = {"abc","def","xyz"};
for (int i = 0; i < strs2.length; i++) {
System.out.println(strs2[i]);
}
}
}
3. 数组的默认初始化
数组是引用类型,它的元素相对于类的实例变量,因此数组一经分配空间,其中的每个元素也被按照实例变量同样的方式被隐式初始化。
关于每个类型的默认值还有印象?
数据类型 默认值
--------------------
byte 0
short 0
int 0
long 0L
float 0.0F
double 0.0
boolean false
char \u0000
引用数据类型 null
tip:使用动态初始化定义数组时,根据元素类型不同,默认值也有所不同
public class ArrayDemo02 {
public static void main(String[] args) {
//静态初始化:创建+赋值 (这个是基本类型)
int[] a = {1,2,3,4,5,6,7,8};
System.out.println(a[0]);
//动态初始化: 包含默认初始化
int[] b = new int[10];
b[0] = 10;
System.out.println(b[0]);
System.out.println(b[1]); //默认初始化中的值都是0
System.out.println(b[2]);
System.out.println(b[3]);
}
}
什么时候采用静态初始化,什么时候采用动态初始化?
- 当你创建数组的时候,确定数组中存储哪些具体的元素的时候,采用静态初始化方式
- 当你创建数组的时候,不确定将来数组中存储哪些数据,你可以采用动态初始化的方式,预先分配内存空间,之后再赋值
2.4 使用一维数组
1. 数组元素的访问和修改
1.1 访问数组
//数组名可以找到数组对象的地址,再通过索引就可以定位到具体的元素了
数组名[索引]
int[] arr = {12, 24, 36};
// 1、访问数组的全部数据
System.out.println(arr[0]); //12
System.out.println(arr[1]); //24
System.out.println(arr[2]); //36
1.2 修改数组
//数组名可以找到数组对象的地址,再通过索引就可以定位到具体的元素了
数组名[索引] = 值
int[] arr = {12, 24, 36};
// 2、修改数组中的数据
arr[0] = 66;
arr[2] = 100;
System.out.println(arr[0]); //66
System.out.println(arr[1]); 0
System.out.println(arr[2]); //100
1.3 获取数组元素个数
// 3、访问数组的元素个数:数组名.length
System.out.println(arr.length);
// 技巧:获取数组的最大索引: arr.length - 1(前提是数组中存在数据)
System.out.println(arr.length - 1);
int[] arr2 = {};
System.out.println(arr2.length - 1); //-1
2. 一维数组的遍历
public class ArreyDemo04 {
public static void main(String[] args) {
int[] arrays = {1,2,3,4,5};
//打印全部的数组元素
for (int i=0; i<arrays.length; i++){
System.out.println(arrays[i]);
}
System.out.println("===============");
//计算所有元素的和
int sum = 0;
for (int i = 0; i < arrays.length; i++) {
sum += arrays[i];
}
System.out.println("sum = "+sum);
System.out.println("===============");
//查找最大元素
int max = arrays[0];
for (int i = 1; i < arrays.length; i++) {
if (arrays[i] > max){
max = arrays[i];
}
}
System.out.println("max = "+max);
}
}
For-Each循环
public class ArrayDemo05 {
public static void main(String[] args) {
int[] arrays = {1,2,3,4,5};
for (int array : arrays) { //增强型的for循环,JDK1.5之后,没有下标
System.out.println(array);
}
}
}
3. 数组作为方法的参数类型进行传参
数组作为方法的参数举例理解
package com.pudding.array;
public class ArrayDemo05 {
public static void main(String[] args) {
int[] arrays = {1,2,3,4,5};
int[] reverse = reverse(arrays);
printArray(reverse);
}
//打印数组元素
public static void printArray(int[] arrays){
for (int i = 0; i < arrays.length; i++) {
System.out.print(arrays[i]+" ");
}
}
//反转数组
public static int[] reverse(int[] arrays){
int[] result = new int[arrays.length];
//反转的操作
for (int i = 0,j=result.length-1; i < arrays.length; i++,j--) {
result[i] = arrays[j];
}
return result;
}
}
当一个方法上,参数类型是一个数组的时候,我们可以采用以下基本传参方式。
public class ArrayTest03 {
//main方法的编写方式,还可以采用C++的语法格式
public static void main(String args[]) {
//调用方法时传一个数组
int[] x = {1,2,3,4};
printArray(x);
//创建String数组
String[] strs= {"abc","def","hehe","haha"};
printArray(strs);
//动态创建数组
String[] strArray = new String[10];
printArray(strArray); //10个null
System.out.println("===============");
printArray(new String[3]);
System.out.println("****************");
printArray(new int[4]);
}
public static void printArray(int[] array){
for (int i = 0; i < array.length; i++) {
System.out.println(array[i]);
}
}
public static void printArray(String[] args){
for (int i = 0; i < args.length; i++) {
System.out.println("String数组中的元素:"+args[i]);
}
}
}
当一个方法的参数是一个数组的时候,我们还可以采用这种方式传参,采用静态初始化直接传参。
public class ArrayTest04 {
public static void main(String[] args) {
//静态初始化一维数组
int[] a = {1,2,3};
printArray(a);
System.out.println("============");
//没有这种语法
//printArray({1,2,3});
//如果直接传递一个静态数组的话,语法必须这样写
printArray(new int[]{1,2,3});
//动态初始化一维数组
int[] a2 = new int[4];
printArray(a2);
System.out.println("=========");
printArray(new int[2]);
}
//使用静态方法方便啊,不需要new对象了
public static void printArray(int[] array){
for (int i = 0; i < array.length; i++) {
System.out.println(array[i]);
}
}
}
4. main方法上面的"String[ ] args"有什么用?
分析 一下:谁负责调用main方法(JVM)
- JVM调用main方法的时候,会自动传一个String数组过来
public class ArrayTest05 {
//这个方法程序员负责写出来,JVM负责调用的时候一定会传入一个String数组过来
public static void main(String[] args) {
//JVM默认传递过来的这个数组对象长度:默认0
//通过测试得出:args不是null
System.out.println("JVM给传递过来的String数组参数,他这个数组长度是:"+args.length);
/*
//类似:以下代码表示的含义:数组对象创建了,但还是数组中没有任何数据
//String[] strs = new String[0];
String[] strs = {}; //静态初始化,里面没有东西
printlength(strs);
String[] strs2 = new String[1];
System.out.println(strs2[0]); //默认初始化里面的一个数据为null
*/
//遍历数组
for (int i = 0; i < args.length; i++) {
System.out.println(args[i]);
}
}
public static void printlength(String[] args){
System.out.println(args.length); //长度为0
}
}
这个数组什么时候里面会有值?
- 其实这个数组是留给用户的,用户可以在控制台上输入参数,这个参数自动会转换为"String[] args"
- 例如这样的程序:java ArrayTest05 abc def xyz
- 那么这个时候JVM会自动将"abc def xyz"通过空格的方式进行分离,分离完成之后,自动放到"String[] args"数组当中
- 所以main方法上面的String[] args数组主要是用来接收用户输入参数的
- 把abc def xyz 转换为字符串数组:{“abc”,“def”,“xyz”}
CMD中
IDEA中
模拟一个系统,假设这个系统要使用,必须输入用户名和密码
public class ArrayTest06 {
//用户名和密码输入到String[] args数组当中
public static void main(String[] args) {
if (args.length != 2){
System.out.println("使用该系统时请输入程序参数,参数中包括用户名和密码信息,例如:zhangsan 123");
return;
}
//程序执行到此处说明用户确实提供了用户名和密码
//接下来你应该判断用户名和密码是否正确
//取出用户名
String username = args[0];
//取出密码
String password = args[1];
//假设用户名是admin,密码是123的时候表示登录成功,其他一律失败
//判断两个字符串是否相等,需要使用equals方法
//if (username.equals("admin") && password.equals("123")){
//下面编写可以避免空指针异常,即使username和password都是null也不会出现空指针异常
if ("admin".equals(username) && "123".equals(password)){
System.out.println("登录成功,欢迎["+username+"]回来");
} else {
System.out.println("验证失败,用户名不存在或者密码错误");
}
}
}
非重点:以后一般都是有界面的,用户可以在界面上输入用户名和密码等参数信息。
5. 数组中存储引用数据类型
一维数组的深入,数组中存储的类型为:引用数据类型
- 对于数组来说,实际上只能存储java对象的"内存地址",数组中存储的每个元素是“引用”。
package com.array;
public class ArrayTest07 {
public static void main(String[] args) {
//array是一个数组
//array[0] 是数组中的一个元素
int[] array = {1,2,3};
for (int i = 0; i < array.length; i++) {
int temp = array[i];
System.out.println(temp);
}
//创建一个Animal类型的数组
Animal a1 = new Animal();
Animal a2 = new Animal();
Animal[] animals = {a1,a2};
//对Animal数组进行遍历
for (int i = 0; i < animals.length; i++) {
/*
Animal a = animals[i];
a.move();
*/
//代码合并
animals[i].move(); //这个move()方法不是数组的,是数组当中Animal对象的move()方法
}
//动态初始化一个长度为2的Animal类型数组
Animal[] ans = new Animal[2];
//创建一个Animal对象,放到数组里的第一个盒子中
ans[0] = new Animal();
//Animal数组中只能存放Animal类型,不能存放Product类型
//ans[1] = new Product()
//Animal数组中可以存放Cat类型的数据,因为Cat是一个Animal
//Cat是Animal子类
ans[1] = new Cat();
//创建一个Animal类型的数组,数组中存储Cat和Brid
Animal[] anis = {new Cat(), new Bird()}; //该数组中存储了两个对象的内存地址
for (int i = 0; i < anis.length; i++) {
/*
//这个取出来的可能是Cat,也可能是Bird,不过肯定是一个Animal
//如果调用的方法是父类中存在的方法不需要向下转型,直接使用父类型引用调用即可
Animal an1 = anis[i];
an1.move();
*/
//Animal中没有sing()方法
//anis[i].sing();
//调用子对象中特有的方法的话,需要向下转型
if (anis[i] instanceof Cat){
Cat cat = (Cat)anis[i];
cat.catchMouse();
} else if (anis[i] instanceof Bird){
Bird bird = (Bird)anis[i];
bird.sing();
}
}
}
}
class Animal{
public void move(){
System.out.println("Animal move...");
}
}
//商品类
class Product{
}
//子类猫
class Cat extends Animal{
public void move(){
System.out.println("Cat move...");
}
//特有的方法
public void catchMouse(){
System.out.println("猫抓老鼠。。");
}
}
//Bird子类
class Bird extends Animal{
public void move(){
System.out.println("Bird fly....");
}
//特有的方法
public void sing(){
System.out.println("鸟儿在歌唱");
}
}
6. 数组扩容
在java开发中,数组长度一旦确定不可变,那么数组满了的怎么办?
- java中数组的扩容是:先新建一个大容量的数组,然后将小容量的数组中的数据一个一个拷贝到大数组当中,小数组对象被垃圾回收。
- 结论:数组扩容效率比较低,因为涉及到拷贝的问题。所以在以后的开发中请注意:尽可能少的进行数组的拷贝。
- 可以在创建数组对象的时候预估以下多长合适,最好预估准确,这样可以减少数组的扩容次数。提高效率
7. 数组拷贝
package com.array;
public class ArrayTest08 {
public static void main(String[] args) {
//java中的数组是怎么进行拷贝的?
//System.arraycopy(5个参数);
//拷贝源(从数组中拷贝)
int[] src = {1,11,22,3,4};
//拷贝目标(拷贝到这个目标数组上)
int[] dest = new int[20]; //动态初始化一个长度为20的数组,每一个元素的默认值为0
/*
//调用JDK,System类中的arraycopy方法,来完成数组的拷贝
System.arraycopy(src, 1, dest, 3, 2);
//遍历目标数组
for (int i = 0; i < dest.length; i++) {
System.out.println(dest[i]); //0 0 0 11 22 0 ...0
}
*/
System.arraycopy(src, 0, dest, 0, src.length);
for (int i = 0; i < dest.length; i++) {
System.out.println(dest[i]);
}
//数组中存储的元素是引用类型的话,也可以拷贝
String[] strs = {"hello","world","study","java","oracle","mysql","jdbc"};//字符串数组
String[] newStrs = new String[20];
System.arraycopy(strs,0,newStrs,0,strs.length);
for (int i = 0; i < newStrs.length; i++) {
System.out.println(newStrs[i]); //hello world study java oracle mysql jdbc null...
}
Object[] objs = {new Object(), new Object(), new Object()};
Object[] newObjs = new Object[5];
//这里拷贝的时候,是拷贝对象的地址
System.arraycopy(objs,0,newObjs,0,objs.length);
for (int i = 0; i < newObjs.length; i++) {
System.out.println(newObjs[i]); //默认调用toString方法
/*
输出结果:
java.lang.Object@3f99bd52
java.lang.Object@4f023edb
java.lang.Object@3a71f4dd
null
null
*/
}
}
}
3. 二维数组
多维数组可以看成是数组的数组,比如二维数组就是一个特殊的一维数组,特殊在,这个一维数组当中的每一个元素都是一个一维数组
- 三位数组是一个特殊的二维数组,特殊在这个二维数组中每一个元素都是一个一维数组
- 实际开发中使用最多的就是一维数组,二维数组很少使用,三位数组几乎不用
(1). 创建二维数组
int a[][] = new int[2][5]
以上二维数组a可以看成一个两行五列的数组
package com.pudding.array;
public class ArrayDemo06 {
public static void main(String[] args) {
//[4][2]
/*
1,2 array[0]
2,3 array[1]
3,4 array[2]
4,5 array[3]
*/
int[][] array = {{1,2},{2,3},{3,4},{4,5}};
printArray(array[0]); //打印的是二维数组中的第一行的一维数组
System.out.println();
System.out.println(array[0][0]); //二维数组的第一行第一列的值
System.out.println(array[0][1]);
for (int i = 0; i < array.length; i++) { //外面空间数组长度
for (int j = 0; j < array[i].length; j++) { //每一个一维数组的长度
System.out.println(array[i][j]);
}
}
}
//打印数组元素
public static void printArray(int[] arrays){
for (int i = 0; i < arrays.length; i++) {
System.out.print(arrays[i]+" ");
}
}
}
(2). 初始化二维数组
1. 二维数组的静态初始化和动态初始化
静态初始化
int[][] arr = {
{112,2.3},
{3,43},
{34,23,3,5}
};
Object[][] arr = {
{new Object(),new Object()},
{new Object(),new Object(),new Object()},
{new Object(),new Object()}
};
动态初始化
int[][] arr = new int[3][4]; //三行四列
Object[][] arr = new Object[4][4]; //四行四列
Animal[][] arr = new Animal[2][2];
//animal类型数组,里面可以存储animal类型对象,以及animal类型子类型
2. 二维数组length属性
package com.array;
public class ArrayTest09 {
public static void main(String[] args) {
//一维数组
int[] array = {100,200,300};
System.out.println(array.length); //3个
//二维数组
//以下代码当中:里面是4个一维数组
int[][] a = {
{100,200,300},
{30,20,40,50,60},
{6,7,9,1},
{0}
};
System.out.println(a.length); //4个元素
System.out.println(a[0].length); //3个元素
System.out.println(a[1].length); //5个元素
System.out.println(a[3].length); //1个元素
}
}
(3). 使用二维数组
1. 二维数组的元素访问
package com.array;
/*
关于二维数组中元素的:读和该
a[二维数组中的一维数组的下标][一维数组的下标]
a[0][0]:表示第一个一维数组中的第一个元素
注意对于a[3][100]来说,其中a[3]是一个整体,[100]是前面a[3]执行结束的结果然后再下标100.
*/
public class ArrayTest10 {
public static void main(String[] args) {
//二维数组
int[][] a = {
{11,23,54},
{100,23,432,234},
{0}
};
//请取出以上二维数组中的第一个一维数组
int[] 我是第1个一维数组 = a[0];
int 我是第1个一维数组中的第1个元素 = 我是第1个一维数组[0];
System.out.println(我是第1个一维数组中的第1个元素); //11
//合并以上代码
System.out.println(a[0][0]); //11
//注意别越界
// System.out.println(a[2][1]); //ArrayIndexOutOfBoundsException
//取出第二个一维数组当中第三个元素
System.out.println("第二个数组中第三个元素:"+a[1][2]); //432
//改
a[2][0] = 11111;
System.out.println(a[2][0]); //11111
}
}
2. 二维数组的遍历
package com.array;
public class ArrayTest11 {
public static void main(String[] args) {
//二维数组
String[][] array = {
{"java","oracle","c++","c#"},
{"张三","李四","王五"},
{"lucy","jack","rose"}
};
//遍历二维数组
for (int i = 0; i < array.length; i++) { //外层循环3次(负责纵向,也就是外面的一维数组)
/*
String[] 一维数组 = array[i];
//负责遍历一维数组
for (int j = 0; j < 一维数组.length; j++) {
System.out.println(一维数组[j]+"");
}
*/
//合并代码
//内存循环负责输出一行,负责遍历二维数组里面的一维数组
for (int j = 0; j < array[i].length; j++) {
System.out.print(array[i][j]+" ");
}
//换行
System.out.println();
}
}
}
3. 方法的参数是一个二维数组
package com.array;
/*
* 动态初始化二维数组
* */
public class ArrayTest12 {
public static void main(String[] args) {
//3行4列:3个一维数组,每一个一维数组当中4个元素
int[][] array = new int[3][4];
//二维数组遍历
/*
for (int i = 0; i < array.length; i++) { //循环3次
for (int j = 0; j < array[i].length; j++) { //循环4次
System.out.print(array[i][j]+" ");
}
System.out.println();
}
*/
//静态初始化
int[][] a = {
{1,2,3,4},
{4,5,6,76},
{1,23,4}
};
printArray(a);
//没有这种语法
//printArray({{1,2,3,4}, {4,5,6,76}, {1,23,4}});
//可以这样写
printArray(new int[][]{{1,2,3,4}, {4,5,6,76}, {1,23,4}});
}
public static void printArray(int[][] array){
//二维数组遍历
for (int i = 0; i < array.length; i++) { //循环3次
for (int j = 0; j < array[i].length; j++) { //循环4次
System.out.print(array[i][j]+" ");
}
System.out.println();
}
}
}
4. 数组模拟题目练习
(1). 数组模拟栈数据结构
编写程序,使用一维数组,模拟数据结构
要求:
1. 这个栈可以存储java中的任何引用类型的数据
2. 在栈中提供push方法模拟压栈(栈满了,要有提示信息)、
3. 在栈中提供pop方法模拟弹栈(栈空了,也要有提示信息)
4. 编写测试程序,new栈对象,调用push、pop方法来模拟压栈弹栈的动作
- Object[]这是一个万能的口袋,这个口袋中可以装任何引用数据类型的数据
package com.array.homework;
/*
编写程序,使用一维数组,模拟数据结构
要求:
1. 这个栈可以存储java中的任何引用类型的数据
2. 在栈中提供push方法模拟压栈(栈满了,要有提示信息)、
3. 在栈中提供pop方法模拟弹栈(栈空了,也要有提示信息)
4. 编写测试程序,new栈对象,调用push、pop方法来模拟压栈弹栈的动作
5. 假设栈的默认初始化容量是10。(请注意无参数构造方法的编写方式)
*/
public class MyStack {
//向栈当中存储元素,使用一维数组,存到栈中,表示存储到数组中,数组是一个容器
//选择Object类型数组,可以存储java中任何引用类型的数据
private Object[] elements;
private int index; //采用-1表示栈帧指向了顶部元素
/*压栈,压入元素obj*/
public void push(Object obj){
if (index >= elements.length - 1){
System.out.println("压栈失败,栈已满");
return;
}
//程序执行到这来说明栈没满
index++;
elements[index] = obj;
//所有sout()方法执行时,如果输出引用的话,自动调用引用的toString()方法
System.out.println("压栈"+obj+"元素成功,栈帧指向"+index);
}
/*弹栈,每次弹一个元素*/
public Object pop(){
if (index < 0){
System.out.println("弹栈失败,栈已空");
return null;
}
System.out.println("弹栈"+elements[index]+"元素成功,栈帧指向"+index);
index--;
return elements[index+1];
}
public MyStack() {
//动态初始化
this.elements = new Object[10];
this.index = -1;
}
//封装:第一步:属性私有化,第二步:提供set和get方法
public Object[] getElements() {
return elements;
}
public void setElements(Object[] elements) {
this.elements = elements;
}
}
package com.array.homework;
public class MyStackTest {
public static void main(String[] args) {
MyStack stack = new MyStack();
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
}
}
压栈java.lang.Object@154617c元素成功,栈帧指向0
压栈java.lang.Object@a14482元素成功,栈帧指向1
压栈java.lang.Object@140e19d元素成功,栈帧指向2
压栈java.lang.Object@17327b6元素成功,栈帧指向3
压栈java.lang.Object@14ae5a5元素成功,栈帧指向4
压栈java.lang.Object@131245a元素成功,栈帧指向5
压栈java.lang.Object@16f6e28元素成功,栈帧指向6
压栈java.lang.Object@15fbaa4元素成功,栈帧指向7
压栈java.lang.Object@1ee12a7元素成功,栈帧指向8
压栈java.lang.Object@10bedb4元素成功,栈帧指向9
压栈失败,栈已满
弹栈java.lang.Object@10bedb4元素成功,栈帧指向9
弹栈java.lang.Object@1ee12a7元素成功,栈帧指向8
弹栈java.lang.Object@15fbaa4元素成功,栈帧指向7
弹栈java.lang.Object@16f6e28元素成功,栈帧指向6
弹栈java.lang.Object@131245a元素成功,栈帧指向5
弹栈java.lang.Object@14ae5a5元素成功,栈帧指向4
弹栈java.lang.Object@17327b6元素成功,栈帧指向3
弹栈java.lang.Object@140e19d元素成功,栈帧指向2
弹栈java.lang.Object@a14482元素成功,栈帧指向1
弹栈java.lang.Object@154617c元素成功,栈帧指向0
弹栈失败,栈已空
(2). 二维数组模拟酒店管理系统
package com.array.homework;
import java.util.Scanner;
/*为某个酒店编写程序:酒店管理系统,模拟订房、退房、打印所有房间状态等功能
* 1. 该系统的用户是酒店前台
* 2. 酒店使用一个二维数组来模拟,"Room[][] rooms"
* 3. 酒店中的每一个房间应该是一个java对象:room
* 4. 每一个房间Room应该有:房间编号、房间类型、房间是否空闲
* 5. 系统应该对外提供的功能:
* 可以预定房间:用户输入房间编号,订房
* 可以退房:用户输入房间编号、退房
* 可以查看所有房间的状态:用户输入某个指令应该可以查看所有房间状态*/
public class HotelMgtSystem {
public static void main(String[] args) {
//创建酒店对象
Hotel hotel = new Hotel();
System.out.println("欢迎使用酒店管理系统,请认真阅读以下使用说明");
System.out.println("请输入对应的功能编号:[1]表示查看房间列表;[2]表示订房;[3]表示退房;[0]表示退出系统");
Scanner scanner = new Scanner(System.in);
while (true){
System.out.print("请输入功能编号:");
int i = scanner.nextInt();
if (i == 1){
hotel.print();
}else if (i == 2){
System.out.print("请输入订房编号:");
int roomNo = scanner.nextInt();
hotel.order(roomNo);
}else if (i == 3){
System.out.print("请输入退房编号:");
int roomNo = scanner.nextInt();
hotel.exit(roomNo);
}else if (i == 0){
System.out.println("再见,欢迎下次再来!");
return;
}else {
System.out.println("输入编号有误,请重新输入!");
}
}
}
}
package com.array.homework;
/*
* 酒店对象,酒店中有二维数组,二维数组模拟大厦所有房间*/
public class Hotel {
private Room[][] rooms;
//通过构造方法盖楼
public Hotel() {
//一共有几层,每层的房间类型是什么,每个房间的编号是什么
//动态初始化
rooms = new Room[3][10]; //3行10列,3层楼,每层10个房间
for (int i = 0; i < rooms.length; i++) { //i是下标,i+1是楼层
for (int j = 0; j < rooms[i].length; j++) {
if (i == 0){
rooms[i][j] = new Room((i+1)*100+j+1,"单人间",true);
}else if (i == 1){
rooms[i][j] = new Room((i+1)*100+j+1,"标准间",true);
}else if (i == 2){
rooms[i][j] = new Room((i+1)*100+j+1,"总统套房",true);
}
}
}
}
//酒店对象上提供一个打印房间列表的方法
public void print(){
//打印所有房间状态,就是遍历二维数组
for (int i = 0; i < rooms.length; i++) {
for (int j = 0; j < rooms[i].length; j++) {
Room room = rooms[i][j];
System.out.print(room.toString());
}
System.out.println();
}
}
/**
* 订房方法
* @param roomNo 传递一个房间编号
*/
public void order(int roomNo){
//最主要就是将房间对象的status修改为false
//通过房间编号演算出下标,获取房间对象
Room room = rooms[roomNo/100 - 1][roomNo%100 - 1];
room.setStatus(false);
System.out.println(roomNo + "已定房!");
}
/*
* 退房
* */
public void exit(int roomNo){
Room room = rooms[roomNo/100 - 1][roomNo%100 - 1];
room.setStatus(true);
System.out.println(roomNo + "已退房!");
}
}
package com.array.homework;
public class Room extends Object{
/* 房间编号
一楼:101 102 103...
二楼:201....
* */
private int no;
/*
* 房间类型:标准间、单人间、总统套房*/
private String type;
/*
* 房间状态
* true表示空闲,房间可以被预定
* false表示占用,房间不能被预定
* */
private boolean status;
public Room() {
}
public Room(int no, String type, boolean status) {
this.no = no;
this.type = type;
this.status = status;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public boolean getStatus() {
return status;
}
public void setStatus(boolean status) {
this.status = status;
}
//equals方法重写
//比较两个对象是否相同,这个是程序员自己定
public boolean equals(Object obj) {
if (null == obj || !(obj instanceof Room)) return false;
if (this == obj) return true;
Room room = (Room)obj;
//当前房间编号 等于 传过来的房间对象的房间编号。认为是同一个房间
return this.getNo() == room.getNo();
}
//toString方法重写(可以不要看对象的内存地址)
//将Java对象转换为字符串形式,程序员自己定
public String toString() {
//把一个变量塞到一个字符串当中,口诀:加一个双引号,双引号中间加两个加号,两个加号中间加变量名
return "["+no+","+type+","+(status ? "空闲" : "占用")+"]";
}
/*public static void main(String[] args) {
Room room= new Room(101,"单人间",false);
//new一个room对象,是一个引用
//println(引用),会自动调用引用的toString()方法
System.out.println(room);
}*/
}
5. Arrays类
5.1 概述
数组工具类java.util.Arrays
由于数组对象本身并没有什么方法可以供我们调用,但API中提供了一个工具类Arrays供我们使用,从而可以对数据对象进行一些基本的操作
Arrays类中的方法都是static修饰的静态方法,在使用的时候可以直接使用类名进行调用,而不用使用对象来调用(注意:是不用,而不是不能)
- Java为什么好学,因为Java有一套强大的类库,牛人都写好了,可以直接拿过来调用
5.2 Arrays具有以下常用的功能
- 给数组赋值:通过fill方法
- 对数组排序:通过sort方法,按升序
- 比较数组:通过equals方法比较数组中元素值是否相等
- 查找数组元素:通过binarySearch方法能对排序好的数组进行二分查找法操作
package com.pudding.array;
import java.util.Arrays;
public class ArrayDemo07 {
public static void main(String[] args) {
int[] a = {1,23,43,6,12390,1323,23};
System.out.println(a); //对象的哈希code[I@154617c
//打印数组元素Arrays.toString
System.out.println(Arrays.toString(a));
printArray(a);
Arrays.sort(a); //数组进行排序: 顺序
System.out.println(Arrays.toString(a));
Arrays.fill(a,2,4,0); //数组填充(将下标2-4填充为0)
System.out.println(Arrays.toString(a));
}
//重复造轮子
public static void printArray(int[] a){
for (int i = 0; i < a.length; i++) {
if (i==0){
System.out.print("[");
}
if (i==a.length-1){
System.out.print(a[i]+"]");
}else {
System.out.print(a[i]+", ");
}
}
}
}
6. 对数组的基本操作
(1). 遍历数组
(2). 对数组进行排序
6.2.1 冒泡排序
冒泡排序无疑是最为出名的排序算法之一,总共有八大排序
冒泡的代码还是相当简单的,两层循环,外层冒泡轮数,里层依次比较,江湖中人人尽皆知。
我们看到嵌套循环,应该立马就可以得出这个算法的时间复杂度0(n2)
冒泡排序
package com.pudding.array;
import java.util.Arrays;
public class ArrayDemo08 {
public static void main(String[] args) {
int[] a = {1,2,6,23,12,33,-6};
int[] sort = sort(a); //调用完我们自己写的排序方法以后,返回一个排序后的数组
System.out.println(Arrays.toString(sort));
}
//冒泡排序
//1. 比较数组中,两个相邻的元素,如果第一个数比第二个数大,我们就交换他们的位置
//2. 每一次比较,都会产生出一个最大,或者最小的数字;
//3. 下一轮则可以少一次排序
//4. 依次循环,直到结束!
public static int[] sort(int[] array){
//临时变量
int temp = 0;
//外层循环,判断我们这个要走多少次;
for (int i = 0; i < array.length-1; i++) {
//内存循环,比较判断两个数,如果第一个数比第二个数大,则交换位置
for (int j = 0; j < array.length-1-i; j++) {
if (array[j+1] < array[j]){
temp = array[j+1];
array[j+1] = array[j];
array[j] = temp;
}
}
}
return array;
}
}
或者这样也行
public class ArraysTest01 {
public static void main(String[] args) {
int[] arr = {2,4,5,67,76};
for (int i = arr.length-1; i > 0; i--) {
for (int j = 0; j < i; j++) {
if (arr[j] > arr[j+1]){
int temp;
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}
(3). 填充替换数组元素
(4). 复制数组
(5). 对数组进行查找
(6). 稀疏数组
需求:编写五子棋游戏中,有存盘退出和续上盘的功能
其中黑子代表1,白子代表2,因为该二维数组的很多默认值0,因此记录了很多没有意义的数据。
解决方式:通过稀疏数组(压缩)
6.6.1 稀疏数组的介绍
当一个数组中大部分元素为0,或者为同一个值的数组时,可以使用稀疏数组来保存该数组。
稀疏数组的处理方式:
- 记录数组一共有几行几列,有多少个不同值
- 把具有不同值的元素和行列及值记录在一个小规模的数组中,从而缩小程序的规模
如下图:左边是原始数组,右边是稀疏数组
6.6.2 稀疏数组的应用
package com.pudding.array;
public class ArrayDemo09 {
public static void main(String[] args) {
//1. 创建一个二维数组11*11, 0:没有棋子,1:黑棋,2:白棋
int[][] array1 = new int[11][11];
array1[1][2] = 1;
array1[2][3] = 2;
//输出原始的数组
System.out.println("输出原始的数组");
for (int[] ints : array1) {
for (int anInt : ints) {
System.out.print(anInt+"\t");
}
System.out.println();
}
System.out.println("===================");
//转换为稀疏数组保存
//获取有效值的个数
int sum = 0;
for (int i = 0; i < 11; i++) {
for (int j = 0; j < 11; j++) {
if (array1[i][j]!=0){
sum++;
}
}
}
System.out.println("有效值的个数:"+sum);
//1. 创建一个稀疏数组的数组
int[][] array2 = new int[sum+1][3];
array2[0][0] = 11; //代表数组有多少行
array2[0][1] = 11; //代表数组有多少列
array2[0][2] = sum; //代表有效值的个数
//遍历原来的二维数组,将非零的值存放到稀疏数组中
int count = 0;
for (int i = 0; i < array1.length; i++) {
for (int j = 0; j < array1[i].length; j++) {
if (array1[i][j]!=0){
count++;
array2[count][0] = i; //获取原数组位置赋值给稀疏数组的第count行第1列
array2[count][1] = j;
array2[count][2] = array1[i][j];//或者原数组的值赋值给稀疏数组的第count行第3列
}
}
}
//输出稀疏数组
System.out.println("稀疏数组");
for (int i = 0; i < array2.length; i++) {
System.out.println(array2[i][0]+"\t"
+array2[i][1]+"\t"
+array2[i][2]+"\t");
}
System.out.println("===================");
System.out.println("还原数组");
//1. 读取稀疏数组的值
int[][] array3 = new int[array2[0][0]][array2[0][1]];
//2. 给其中的元素还原它的值
for (int i = 1; i < array2.length; i++) {
array3[array2[i][0]][array2[i][1]] = array2[i][2];
}
//打印
System.out.println("输出还原的数组");
for (int[] ints : array3) {
for (int anInt : ints) {
System.out.print(anInt+"\t");
}
System.out.println();
}
}
}
结果如下:
输出原始的数组
0 0 0 0 0 0 0 0 0 0 0
0 0 1 0 0 0 0 0 0 0 0
0 0 0 2 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
===================
有效值的个数:2
稀疏数组
11 11 2
1 2 1
2 3 2
===================
还原数组
输出还原的数组
0 0 0 0 0 0 0 0 0 0 0
0 0 1 0 0 0 0 0 0 0 0
0 0 0 2 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0