目录
一、数组的概念
1.1什么是数组
数组:可以看成是
相同类型元素的一个集合
。在内存中是一段连续的空间。
1.2数组的创建及初始化
数组的创建:格式如下
T[] 数组名 = new T[N];
T
:表示数组中存放元素的类型
T[]
:表示数组的类型
N
:表示数组的长度
int[] array1 = new int[10]; // 创建一个可以容纳10个int类型元素的数组
double[] array2 = new double[5]; // 创建一个可以容纳5个double类型元素的数组
String[] array3 = new double[3]; // 创建一个可以容纳3个字符串元素的数组
数组的初始化:
数组的初始化主要分为
动态初始化以及静态初始化。
1.
动态初始化:在创建数组时,直接指定数组中元素的个数
int[] array = new int[10];
2.
静态初始化:在创建数组时不直接指定数据元素个数,而直接将具体的数据内容进行指定
语法格式:
T[]
数组名称
= {data1, data2, data3, ..., datan};
int[] array1 = new int[]{0,1,2,3,4,5,6,7,8,9};
double[] array2 = new double[]{1.0, 2.0, 3.0, 4.0, 5.0};
String[] array3 = new String[]{"hell", "Java", "!!!"};
【注意事项】
静态初始化虽然没有指定数组的长度,编译器在编译时会根据
{}
中元素个数来确定数组的长度。
静态初始化时
, {}
中数据类型必须与
[]
前数据类型一致。
静态初始化可以简写,省去后面的
new T[]
。
// 注意:虽然省去了new T[], 但是编译器编译代码时还是会还原
int[] array1 = {0,1,2,3,4,5,6,7,8,9};
double[] array2 = {1.0, 2.0, 3.0, 4.0, 5.0};
String[] array3 = {"hell", "Java", "!!!"};
如果没有对数组初始化,那么默认值就为0。
1.3数组的使用
如何遍历数组元素,在这里有三种方法
第一种方法采用for循环来遍历数组
int[]array = new int[]{10, 20, 30, 40, 50};
for(int i = 0; i < 5; i++){
System.out.println(array[i]);
}
注意:
在数组中可以通过
数组对象
.length
来获取数组的长度
所以可以对我们的代码进行优化
int[]array = new int[]{10, 20, 30, 40, 50};
for(int i = 0; i < array.length; i++){
System.out.println(array[i]);
}
第二种方法是用for-each遍历数组
for-each 是 for 循环的另外一种使用方式. 能够更方便的完成对数组的遍历. 可以避免循环条件和更新语句写错. 但是这种方法是获取不到下标的,
int[] array = {1, 2, 3};
for (int x : array) {//x的类型要与数组的类型一致,如果数组是double那么这里就是double x
System.out.println(x);
}
第三种方法我们可以运用Java本身提供的一个类(工具)
这种方法我们先了解一下,等到后面博主会详细说明
String ret=Arrays.toString(array);
System.out.println(ret);
二、数组是引用类型
2.1初识jvm的内存分布
内存是一段连续的存储空间,主要用来存储程序运行时数据的。比如:
1.
程序运行时代码需要加载到内存
2.
程序运行产生的中间数据要存放在内存
3.
程序中的常量也要保存
4.
有些数据可能需要长时间存储,而有些数据当方法运行结束后就要被销毁
如果对内存中存储的数据不加区分的随意存储,那对内存管理起来将会非常麻烦
因此
JVM
也对所使用的内存按照功能的不同进行了划分:
程序计数器
(PC Register):
只是一个很小的空间
,
保存下一条执行的指令的地址
虚拟机栈
(JVM Stack):
与方法调用相关的一些信息,
每个方法在执行时,都会先创建一个栈帧
,栈帧中包含有:局部变量表
、
操作数栈
、
动态链接
、
返回地址
以及其他的一些信息,保存的都是与方法执行时相关的一些信息。比如:局部变量。当方法运行结束后,栈帧就被销毁了,即栈帧中保存
的数据也被销毁了。
本地方法栈
(Native Method Stack):
本地方法栈与虚拟机栈的作用类似
.
只不过
保存的内容是
Native
方法的局
部变量
.
在有些版本的
JVM
实现中
(
例如
HotSpot),
本地方法栈和虚拟机栈是一起的
堆
(Heap)
: JVM
所管理的最大内存区域
.
使用
new
创建的对象都是在堆上保存
(
例如前面的
new int[]{1, 2, 3} )
,
堆是随着程序开始运行时而创建,随着程序的退出而销毁,堆中的数据只要还有在使用,就不会被销
毁
。
方法区
(Method Area)
:
用于
存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数
据
.
方法编译出的的字节码就是保存在这个区域
现在我们只简单关心堆 和 虚拟机栈这两块空间,后序
JVM
中还会更详细介绍。
2.2基本类型变量和引用类型变量的区别
基本数据类型创建的变量,称为基本变量,该变量空间中直接存放的是其所对应的值;
而引用数据类型创建的变量,一般称为对象的引用,其空间中存储的是对象所在空间的地址。
public static void func() {
int a = 10;
int b = 20;
int[] arr = new int[]{1,2,3};
}
在上述代码中,
a
、
b
、
arr
,都是函数内部的变量,因此其空间都在
main
方法对应的栈帧中分配。
a
、
b
是内置类型的变量,因此其空间中保存的就是给该变量初始化的值。
array
是数组类型的引用变量,其内部保存的内容可以简单理解成是数组在堆空间中的首地址。
![](https://i-blog.csdnimg.cn/blog_migrate/c4879c6a1d4944ca3e56723d2a9e9b76.png)
从上图可以看到,
引用变量并不直接存储对象本身,可以简单理解成存储的是对象在堆中空间的起始地址。通过该
地址,引用变量便可以去操作对象
。有点类似
C
语言中的指针,但是
Java
中引用要比指针的操作更简单。
三、数组的应用场景
3.1保存数据
public static void main(String[] args) {
int[] array = {1, 2, 3};
for(int i = 0; i < array.length; ++i){
System.out.println(array[i] + " ");
}
}
3.2作为函数的参数
参数传数组类型(引用数据类型)
public static void main(String[] args) {
int[] arr = {1, 2, 3};
func(arr);
System.out.println("arr[0] = " + arr[0]);
}
public static void func(int[] a) {
a[0] = 10;
System.out.println("a[0] = " + a[0]);
}
// 执行结果
a[0] = 10
arr[0] = 10
发现在
func
方法内部修改数组的内容
,
方法外部的数组内容也发生改变
.
因为数组是引用类型,按照引用类型来进行传递,是可以修改其中存放的内容的
总结:
所谓的
"
引用
"
本质上只是存了一个地址
. Java
将数组设定成引用类型
,
这样的话后续进行数组参数传参
,
其实只是将数组的地址传入到函数形参中.
这样可以避免对整个数组的拷贝
(
数组可能比较长
,
那么拷贝开销就会很大
)
四、数组练习
4.1数组转字符串
import java.util.Arrays
int[] arr = {1,2,3,4,5,6};
String newArr = Arrays.toString(arr);
System.out.println(newArr);
// 执行结果
[1, 2, 3, 4, 5, 6]
使用这个方法后续打印数组就更方便一些
.
Java
中提供了
java.util.Arrays
包
,
其中包含了一些操作数组的常用方法
.
4.2数组拷贝
import java.util.Arrays;
public static void func(){
// newArr和arr引用的是同一个数组
// 因此newArr修改空间中内容之后,arr也可以看到修改的结果
int[] arr = {1,2,3,4,5,6};
int[] newArr = arr;
newArr[0] = 10;
System.out.println("newArr: " + Arrays.toString(arr));
// 使用Arrays中copyOf方法完成数组的拷贝:
// copyOf方法在进行数组拷贝时,创建了一个新的数组
// arr和newArr引用的不是同一个数组
arr[0] = 1;
newArr = Arrays.copyOf(arr, arr.length);
System.out.println("newArr: " + Arrays.toString(newArr));
// 因为arr修改其引用数组中内容时,对newArr没有任何影响
arr[0] = 10;
System.out.println("arr: " + Arrays.toString(arr));
System.out.println("newArr: " + Arrays.toString(newArr));
// 拷贝某个范围.
int[] newArr2 = Arrays.copyOfRange(arr, 2, 4);
System.out.println("newArr2: " + Arrays.toString(newArr2));
}
4.3数组排序(冒泡排序)
public static void main(String[] args) {
int[] arr = {9, 5, 2, 7};
bubbleSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void bubbleSort(int[] arr) {
for (int i = 0; i < arr.length; i++) {
for (int j = 1; j < arr.length-i; j++) {
if (arr[j-1] > arr[j]) {
int tmp = arr[j - 1];
arr[j - 1] = arr[j];
arr[j] = tmp;
}
}
} // end for
} // end bubbleSort
// 执行结果
[2, 5, 7, 9]
冒泡排序性能较低
. Java
中内置了更高效的排序算法
public static void main(String[] args) {
int[] arr = {9, 5, 2, 7};
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));
}
4.4查找数组中指定元素(二分查找)
针对
有序数组
,
可以使用更高效的二分查找
以升序数组为例
,
二分查找的思路是先取中间位置的元素
,
然后使用待查找元素与数组中间元素进行比较:
如果相等,即找到了返回该元素在数组中的下标
如果小于,以类似方式到数组左半侧查找
如果大于,以类似方式到数组右半侧查找
public static void main(String[] args) {
int[] arr = {1,2,3,4,5,6};
System.out.println(binarySearch(arr, 6));
}
public static int binarySearch(int[] arr, int toFind) {
int left = 0;
int right = arr.length - 1;
while (left <= right) {
int mid = (left + right) / 2;
if (toFind < arr[mid]) {
// 去左侧区间找
right = mid - 1;
} else if (toFind > arr[mid]) {
// 去右侧区间找
left = mid + 1;
} else {
// 相等, 说明找到了
return mid;
}
}
// 循环结束, 说明没找到
return -1;
}
// 执行结果
5
可以看到
,
针对一个长度为
10000
个元素的数组查找
,
二分查找只需要循环
14
次就能完成查找
.
随着数组元素个数越多,
二分的优势就越大
.
五、二维数组
二维数组本质上也就是一维数组
,
只不过每个元素又是一个一维数组
.
但是Java中的二维数组是一个不规则数组
每一行中的元素可以与其他行元素不相等,下图是一个二维数组,有两行,第一行数组有5个元素,第二行元素有3个元素。
![](https://i-blog.csdnimg.cn/blog_migrate/a6f06e5b8a1e2accec2b736a6d463e55.png)
如果我们要遍历二维数组
可以采用下图的方法
![](https://i-blog.csdnimg.cn/blog_migrate/2f2f6e9ab5adcbfeb6da681debaeab28.png)
图中圈出的部分要特别注意,在我们得到每一行的数组之后,如果要遍历各行数组长度,那么我们就只需要j<array3[i].length即可,length是可以帮助我们得到数组的长度的