目录
数组的定义和使用
为什么要有数组?
在这里举一个例子,假如我们要编写一个程序,输出班级中50个人的最高成绩,平均成绩及平均值,这时我们首先得知道这50个同学的成绩吧,那我们怎么知道呢?难道定义50个变量么?显然这样太麻烦了,那我们可以输入一个成绩比较一次,但是这样我们并没有存储这50个人的成绩,这时候编程语言中就为我们定义数组这个类型,他就可以存储各种类型的数据并且通过下标来访问。这就为在实现某些功能的时候就会很方便。
数组的定义和创建
数组的定义
数组是这样定义的,数组是相同类型元素的集合。这里我们要注意,使用数组,数组里的元素类型都是相同的,比如int类型的数组,元素的类型只能是int不能是其他的数据类型。
数组有三个要求: 1):数组中的元素必须是同一类型的。 2):数组中的元素可以通过下标来访问。 3)数组在空间中是连续存放的。
数组的创建
讲了数组的定义及一些属于数组的规则,我们来说一下如何在程序中创建一个数组呢?与c语言又有何区别呢?
public static void main(String[] args) {
int[] array = new int[10];//创建一个int类型的数组,数组里有10个元素,系统为我们初始化成默认值
}
}
int[] array1 = new int[10]; // 创建一个可以容纳10个int类型元素的数组
double[] array2 = new double[5]; // 创建一个可以容纳5个double类型元素的数组
String[] array3 = new double[3]; // 创建一个可以容纳3个字符串元素的数组
数组的创建:数据类型 变量名 = 初始化部分。
数组的初始化
数组的静态初始化:在创建数组时不直接指定数据元素个数,而直接将具体的数据内容进行指定
eg:
int[] array ={1,2,3,4,5};
这种静态初始化,需要我们指定数组的内容,当然不需要我们在【】里指定元素个数,在Java中可以利用数组名.length就可以求得数组的长度,不需要像c语言那样利用sizeof操作符。
public static void main(String[] args) {
int[] array ={1,2,3,4,5};
System.out.println(array.length);
}
public static void main(String[] args) {
int[] array = new int[]{1,2,3,4,5};
System.out.println(array.length);
}
也可以这样初始化,也可以省略new ,但是当编译器编译代码的时候都会将这个new还原。
数组的动态初始化:在创建数组时,直接指定数组中元素的个数。
int[] array = new int[10];//利用new关键字创建一个10个元素的数组
这里的动态初始化只需要我们指定数组中元素的个数,数组中的内容我们可进行修改,如果不修改会自动初始化成默认值。
public static void main(String[] args) {
int[] array = new int[10];
System.out.println(Arrays.toString(array));
}
需要注意的点有:
1):静态初始化我们需要指定数组中的内容,至于数组的长度,编译器会根据数组的个数来计算求得,并且用数组名.length直接可以得到数组的大小。
2):静态初始化的时候,元素的数据类型需要与【】前的数据类型保持一致并且必须是同一数据类型。
3):数组定义的时候可以省略new
初始化可以 int[] array={0}; 也可以 int[] array =new[]{0};
4) : 我们既然有两种定义数组的方式我们到底用哪个呢?当不确定数组中的内容我们就可以利用 int[] array =new int[10] ,当我们确定数组中的内容并且要通过下标来访问某一个元素的时候我们就要使用静态初始化
5):当我们动态初始化的时候,我们只需指定数组的元素个数,编译器会给我们进行初始化默认值。
数组的访问
数组在空间中是连续存放的,他的元素可以随机访问,我们利用【】来进行访问数组下标的元素。
public static void main(String[] args) {
int[] array = {1,2,3,4,5,6,7,8,9};
int ret = array[0];//把下标为0的元素赋值给ret
}
但是有一点要注意:不可以越界访问,如果越界访问编译器就会报异常;
三种方式打印数组中的元素
1):普通for循环遍历打印数组中的元素
public static void main(String[] args) {
int[] array ={5,8,9,7,4,6,3,2,1};
for (int i = 0; i < array.length; i++) {
System.out.print(array[i]+" ");
}
}
2):利用foreach(增强for循环)打印数组中的元素
public static void main(String[] args) {
int[] array ={5,8,9,7,4,6,3,2,1};
for(int x:array) { //for(定义的数组元素类型的变量:数组)
System.out.print(x+" ");
}
}
这两种循环用哪个?
当我们要通过下标访问某个元素实现一些算法,功能时候我们选择使用普通for循环。
当我们只需要遍历数组中的元素,使用这两个for循环都可以。
3):Arrays类里的toString方法
toString方法就是将数组转化成字符串的形式输出出来。
模拟实现Arrays.toString方法
public static String toString1(int[] array) {
String str ="[";
for (int i = 0; i < array.length; i++) {
str+=array[i];
if(i!=array.length-1) {
str+=",";
}
}
str+="]";
return str;
}
public static void main(String[] args) {
int[] array ={1,2,3,4,5,6,7,8,9,4,1,0};
System.out.println(toString1(array));
}
数组的底层是个引用?
我们第一篇Java的文章就讲到一个jvm是Java虚拟机,我们的代码都要加载到jvm上。
好我们在来回忆一下,我们写的代码会经过Javac命令生成字节码文件也就是.class文件,在经过Java命令运行然后将代码加载到jvm中。既然我们写的Java程序都要经过jvm这个虚拟机,我们这里就来聊一聊jvm的内存分布。为什么要将内存划分成区域呢?答案是方便内存管理起来。
我们这节课主要探讨Java虚拟机栈和堆
一些官方概念:
程序计数器 (PC Register): 只是一个很小的空间, 保存下一条执行的指令的地址
虚拟机栈(JVM Stack): 与方法调用相关的一些信息,每个方法在执行时,都会先创建一个栈帧,栈帧中包含有:局部变量表、操作数栈、动态链接、返回地址以及其他的一些信息,保存的都是与方法执行时相关的一些信息。比如:局部变量。当方法运行结束后,栈帧就被销毁了,即栈帧中保存的数据也被销毁了。
本地方法栈(Native Method Stack): 本地方法栈与虚拟机栈的作用类似. 只不过保存的内容是Native方法的局部变量. 在有些版本的 JVM 实现中(例如HotSpot), 本地方法栈和虚拟机栈是一起的
堆(Heap): JVM所管理的最大内存区域. 使用 new 创建的对象都是在堆上保存 (例如前面的 new int[]{1, 2,3} ),堆是随着程序开始运行时而创建,随着程序的退出而销毁,堆中的数据只要还有在使用,就不会被销毁。
方法区(Method Area): 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据. 方法编译出的的字节码就是保存在这个区域
数组的内存分配
我们都知道数组与数据类型不一样,他是引用类型(这个引用与c/c++的指针类似,但也有很多的区别)。
在c语言中数组名是地址,那在Java中会是什么呢?我们来打印一下数组名
不出我们的意料,数组名是地址,但是与c语言不一样,在Java中数组名(也就是我们定义的一个变量名array他就是一个引用)或者引用变量,存放的它指向的或者它引用的对象的地址。
我们来画图理解一下
array是数组类型的引用变量,其内部保存的内容可以简单理解成是数组在堆空间中的首地址。
或许这个图更加形象一点:
引用变量并不直接存储对象本身,可以简单理解成存储的是对象在堆中空间的起始地址。通过该地址,引用变量便可以去操作对象。有点类似C语言中的指针,但是Java中引用要比指针的操作更简单。
几个疑难问题
既然数组是一个引用类型,那它的默认值就是null.
int[] array1=null;
这一行代码代表 array这个引用不指向任何的对象。
如果访问了会怎么样呢?
这里有个问题array2 =array1是什么意思?是引用指向引用么?
这个代码是什么意思呢?我们来画图理解一下
此时就代表,这两个引用引用了一个对象,并且array2=array1的意思就是array2引用了array1引用的对象。两个引用指向相同一块内存的时候 当改变一个引用的时候另一个引用也会发生改变。
还有一个问题,引用能同时指向多个对象么?
public static void main(String[] args) {
int[] array =new int[]{1,2,3,4,5};
array = new int[]{2,3,4,5,6};
array = new int[]{3,4,5,6,7};
}
我们利用这个代码来解释这个问题(画图理解)
所以引用只能同时指向一个对象,当指向新的对象的时候,原来的也被垃圾回收器(GC)回收掉。
最后一个问题,引用一定在栈上么?
答案是不一定,当前数组这个引用是在栈上,但到后面类和对象就是在堆上。
基本类型变量与引用类型变量的区别
基本数据类型创建的变量,称为基本变量,该变量空间中直接存放的是其所对应的值;
引用数据类型创建的变量,一般称为对象的引用,其空间中存储的是对象所在空间的地址。
public static void func() {
int a = 10;
int b = 20;
int[] arr = new int[]{1,2,3};
}
在上述代码中,a、b、arr,都是函数内部的变量,因此其空间都在main方法对应的栈帧中分配。
a、b是内置类型的变量,因此其空间中保存的就是给该变量初始化的值。
array是数组类型的引用变量,其内部保存的内容可以简单理解成是数组在堆空间中的首地址
数组作为函数参数
我们来看两个例子
public static void func1(int[] array) {
array = new int[]{3,4,5,6,7};
}
public static void func2(int[] array) {
array[0] =1;
}
public static void main(String[] args) {
int[] array ={2,3,4,5,6};
func1(array);
System.out.println(Arrays.toString(array));
func2(array);
System.out.println(Arrays.toString(array));
}
上述代码两个方法分别会输出什么呢?
答案是:
第一个为啥输出原来的么?不是说引用类型可以改变原来的值么?为啥啊?
我们还是来画图解释一下:
数组的应用:交换两个变量
public static void swap(int[] array) {
int tmp = array[0];
array[0] = array[1];
array[1] = tmp;
}
public static void main(String[] args) {
int[] array = new int[]{10,20};
swap(array);
System.out.println(Arrays.toString(array));
}
在Java中是可以返回数组的而在c/c++中是不可以的;
Arrays类的常用方法介绍
binarySearch
copyOf
copyRange
equals
fill
sort
toString
二维数组
二维数组的定义:二维数组的每个元素就是一个一维数组。
二维数组的创建方式:
数据类型[ ] [ ] 数组名称 = new 数据类型 [行数][列数] { 初始化数据 };
二维数组不规则初始化
int[][] array1 = new int[3][];//在Java中可以省略列数但不能省略行数
public static void main(String[] args) {
int[][] array1 = new int[2][];//在Java中可以省略列数但不能省略行数
array1[0]= new int[2];
array1[1] = new int[3];
System.out.println(Arrays.deepToString(array1));
}
可以手动控制几行几列。
二维数组的几种打印方式
1):Arrays类的deepToStirng方法
public static void main(String[] args) {
int[][] array ={{1,2,3,4,5},{6,7,8},{5,6,5,2}};
System.out.println(Arrays.deepToString(array));
}
2):普通for循环打印二维数组
public static void main(String[] args) {
int[][] array ={{1,2,3,4,5},{6,7,8},{5,6,5,2}};
for (int i = 0; i < array.length; i++) {
for (int j = 0; j < array[i].length; j++) {
System.out.print(array[i][j]+" ");
}
System.out.println();
}
}
3):foreach循环打印二维数组
public static void main(String[] args) {
int[][] array ={{1,2,3,4,5},{6,7,8},{5,6,5,2}};
for (int[] tmp:array) {
for (int x:tmp) {
System.out.print(x+" ");
}
System.out.println();
}
}