数组(Array)是
相同类型的变量 的集合,若将该集合命名,那么这个名称为
数组名 。组成数组的各个变量称为数组的元素。
(本文画的图里的地址均为杜撰,了解其含义即可)
一、创建数组
??基本语法形式:
数据类型[] 数组名称 = { 初始化数据 };
数据类型[] 数组名称 = new int[元素个数];
数据类型[] 数组名称 = new 数据类型 [] { 初始化数据 };
📑代码示例:
public class TestDemo {
public static void main(String[] args) {
int[] array1 = {1,2,3,4,5,6};
int[] array2 = new int[10];
int[] array3 = new int[]{1,2,3,4,5,6,7};
}
}
💬代码解释:
- array1、array2、array3 都是局部变量,需要在
Java虚拟机栈 上开辟空间进行存储。 - 同时它们也都是引用变量,该变量中存放着其
指向的对象的地址 。
二. 引用变量是什么?
- 引用变量又叫引用,可以理解成一个指针。
- 创建一个引用就相当于创建了一个
保存了地址的变量 ,这个地址就是这个变量的值。 - 如果进行数组参数传参,其实就相当于将
数组的地址 传入到所对应函数的形参中,避免整个数组的拷贝(万一数组比较长,空间会浪费很多)。
三. 对象是什么?
- 对象的一个实例,人,狗,书本等都可以叫做对象。
- array1指向的对象就是1到6这六个数字。
- 对象需要在
堆 上开辟空间进行存储。
注意:
- 在初始化数组的时候,数据类型后面的 [ ] 中
不可以添加任何数字 。初始化数组后,编译器会自动知晓数组元素的个数。 - 习惯将 [ ] 放在数组名称的
前面 ,因为 [ ] 也是数据类型的一部分。尽管也可以写成类似 int array[] = {1,2,3};这样的代码形式。 - 在静态初始化的时候,数组元素个数和初始化数据的
格式应当保持一致 。 - 日常的数组使用以方法一为主
二、打印数组内容
方法一:利用循环结构打印
📑代码示例:
public class TestDemo {
public static void main(String[] args) {
int[] array1 = {1,2,3,4,5,6};
System.out.println("数组元素个数为:" + array1.length);
for (int i = 0; i < array1.length; i++) {
System.out.print(array1[i] + " ");
}
}
}
🏸 代码结果:
💬代码解释:
array.length 能够获取到数组的长度- 使用 [ ] 来访问到各个下标的数组元素,下标有效范围 [0 , array.length - 1]
- 不能超出有效范围,否则会出现
数组越界异常 (ArrayIndexOutOfBoundsException)
方法二:增强for循环(for-each 循环)
📑代码示例:
public class TestDemo {
public static void print(int[] array) {
for(int x : array) {
System.out.print(x + " ");
}
}
public static void main(String[] args) {
int[] array1 = {1,2,3,4,5,6};
print(array1);
}
}
🏸 代码结果
💬代码解释:
- for( : ) 括号里冒号的右侧是
需要遍历的数组名称 ,左侧是数组里面存放的元素的类型定义的变量 。向变量x中存放一个,接着打印一个,从而实现遍历数组内容的功能。 - 和方法一相比,方法二没有办法利用下标拿到各个元素的内容,从而进行更多的操作,但是能够更方便的完成对数组的遍历. 可以避免循环条件和更新语句写错。
方法三:利用toString()方法
📑代码示例:
public class TestDemo {
public static void main(String[] args) {
int[] array1 = {1,2,3,4,5,6};
System.out.println(Arrays.toString(array1));
}
}
🏸 代码结果
💬代码解释:
一. 这里调用 Arrays 这个操作数组的工具类 里面的 toString() 方法,该方法可以将当前的数组转换为字符串的形式进行输出
二. 想要使用这个类,首要做的就需要导入包 (就相当于C语言当中的#include < >),这里需要导入Java的实用工具类库java.util 包,具体导入代码为 import java.util.Arrays; 。
三. 什么是包? 程序员进行开发并不是从最底层一步一步的完完全全的由自己实现,为了减少代码量,提高效率,我们需要站在巨人的肩膀上进行开发,包里面有着大量的编译好生成字节码文件,拿来就可以在jdk中运行。导包实际上就是导入这个包底下的类。
import java.util.* 表示把 util 包下的所有类都导入到程序当中,但是实际上并不会导入所有东西到文件里,因为在Java中用到了哪个类才会加载哪个类。import java.util.Arrays 表示将util包下特定的类Arrays 导入到程序当中。
自我实现toString()方法
public class TestDemo {
public static String myToString(int[] array) {
if(array == null) {
return null;
}
if(array.length == 0) {
return "";
}
String ret = "[";
for (int i = 0; i < array.length ; i++) {
ret += array[i];
if(i < array.length - 1) {
ret += ",";
}
}
ret += "]";
return ret;
}
public static void main(String[] args) {
int[] array = {1,2,3,4,5,6};
String ret = myToString(array);
System.out.println(ret);
}
}
💬代码解释:
拓展:交换整形变量
写一个方法使其具有交换两个整形变量的功能
思路一:
📑代码示例:
public class TestDemo {
public static void swap(int a,int b) {
int tmp = a;
a = b;
b = tmp;
}
public static void main(String[] args) {
int x = 10;
int y = 20;
swap(x,y);
System.out.println("x = " + x + " y = " + y);
}
}
🏸 代码结果:
💬代码解释:
结果显而易见,并没有实现数据的交换,思路一的思路是有问题的。
因为对于 int 这样的基础类型,形参 a 和 b 就是实参 x 和 y 的临时拷贝,即传值调用。
就是说,你a b交换了和我x y有啥关系!
思路二:
利用数组进行交换
📑代码示例:
public class TestDemo {
public static void swap(int[] array) {
int tmp = array[0];
array[0] = array[1];
array[1] = tmp;
}
public static void main(String[] args) {
int a = 10;
int b = 20;
int[] array1 = {a,b};
System.out.println("交换前: " + array1[0] + " " + array1[1]);
swap(array1);
System.out.println("交换后: " + array1[0] + " " + array1[1]);
}
}
🏸 代码结果:
💬代码解释:
三、了解null
📑代码示例:
public class TestDemo {
public static void main(String[] args) {
int[] array1 = {1,2,3,4,5,6};
int[] array2 = array1;
int[] array3 = null;
}
}
💬代码解释:
- 在此处,
array2 这个引用指向 array1 这个引用所指向的对象 - array3 这个引用赋值一个null,代表着
不指向任何对象 - 一个引用类型如果不知道将来指向哪个对象,就可以将其赋值为
null - 当引用类型赋值为 null 时,如果通过 null 尝试访问属性的时候,例如想要求数组的长度时(array3.length),将会报错,显示
空指针异常 (NullPointerException)
四、牛刀小试
练习一:元素 * 2
题目:写一个方法, 将数组中的每个元素都 * 2(要求不改变原来的数组)
📑代码示例:
public class TestDemo {
public static int[] change(int[] array) {
int[] ret = new int[array.length];
for (int i = 0; i < array.length; i++) {
ret[i] = array[i] * 2;
}
return ret;
}
public static void main(String[] args) {
int[] array1 = {1,2,3,4,5};
System.out.println("改变前array1数组:" + Arrays.toString(array1));
int[] ans = change(array1);
System.out.println("改变后array1数组:" + Arrays.toString(array1));
System.out.println("改变后返回的数组:" + Arrays.toString(ans));
}
}
🏸 代码结果:
💬代码解释:
- 将 array1作为参数传给change()函数,由于数组是引用类型,返回的时候只是将 array1指向的对象的地址递给了实参 array ,并没有拷贝数组的内容,从而比较高效。
- 在 change() 函数中重新创建了一个数组 ret 来接收array数组中每个数乘以2后的结果,返回数组 ret,就不会破坏原有的数组。
- change() 函数结束后,数组 array 和 数组 ret 随之被销毁,但是其指向的对象在堆上并不会销毁。
练习二:数组拷贝
方法一:循环拷贝
📑代码示例:
public class TestDemo {
public static int[] arraysCopy(int[] array) {
int[] newArray = new int[array.length];
for (int i = 0; i < array.length; i++) {
newArray[i] = array[i];
}
return newArray;
}
public static void main(String[] args) {
int[] array = {1,2,3,4,5,6};
int[] ret = arraysCopy(array);
System.out.println(Arrays.toString(ret));
}
}
🏸 代码结果:
💬代码解释:
在 arraysCopy 这个拷贝的方法中定义一个新的数组 newArray 通过 for 循环的方式来进行拷贝,最后将拷贝的数组返回
方法二:Arrays.copyOf 和 Arrays.copyOfRange
📑代码示例:
public class TestDemo {
public static void main(String[] args) {
int[] array = {1,2,3,4,5,6};
int[] newArray = Arrays.copyOf(array,2 * array.length);
System.out.println(Arrays.toString(newArray));
int[] newArray2 = Arrays.copyOfRange(array,1,4);
System.out.println(Arrays.toString(newArray2));
}
}
🏸 代码结果:
💬代码解释:
- 如果新的数组(newArray)的长度大于需要拷贝的数组(array)的长度,用
零 进行补充。 - Java中一般看到 from to 这样的一个范围,一般情况下都会是
左闭右开 ,在这里从打印下标为 1 的元素进行打印,打印到下标为 4 的元素为止(不包括下标为 4 的元素),范围为 [1,4)。
方法三:System.arraycopy
📑代码示例:
public class TestDemo {
public static void main(String[] args) {
int[] array = {1,2,3,4,5,6};
int[] newArray = new int[array.length];
System.arraycopy(array,1,newArray,2,3);
System.out.println(Arrays.toString(newArray));
}
}
🏸 代码结果:
💬代码解释:
在该代码中从原数组(array)下标为 1 的位置开始拷贝,拷贝到目标数组(newArray)下标为 2 的位置,拷贝的长度为 3,剩余的位置补 0 。
方法四:array.clone
📑代码示例:
public class TestDemo {
public static void main(String[] args) {
int[] array = {1,2,3,4,5,6};
int[] newArray = array.clone();
System.out.println(Arrays.toString(newArray));
}
}
🏸 代码结果:
💬代码解释:
深拷贝与浅拷贝
以上举例的四种拷贝的实现方式都属于深拷贝,那么什么是浅拷贝?两者又有什么区别?
提问:如果想要将上述的浅拷贝变成深拷贝,应当怎么做?
唯一的办法就是将 array1 数组中每个元素指向的对象在堆中全部复制一份,拷贝之后的数组中的元素指向新复制的对象。
当面试的时候,面试官问,这几种拷贝方式是深拷贝还是浅拷贝?
应当这么回答:
首先思考拷贝的对象是什么?
- 对象是基本数据类型,是深拷贝
- 对象是引用类型
- 只是单纯的使用这几个方法,是浅拷贝
- 使用这几个方法,并且代码本身也处理了深拷贝,是深拷贝
练习三:查找数组中指定元素
顺序查找
📑代码示例:
ublic class TestDemo {
public static int findNum(int[] array,int key) {
for (int i = 0; i < array.length; i++) {
if (array[i] == key) {
return i;
}
}
return -1;
}
public static void main(String[] args) {
int[] array = {23,35,2,31,5,67};
int ret = findNum(array,31);
if (ret == -1) {
System.out.println("查无此数");
}else {
System.out.println("该数下标为: " + ret);
}
}
}
🏸 代码结果:
💬代码解释:
顺序查找采用的是最暴力的通过循环一个一个对比进行查找的方法,思想简单 。最大的缺点是效率低下 ,如果该数组中有1000个元素,刚好查找的元素位于该数组的最后,就意味着循环需要进行1000次
二分查找
二分查找针对的是有序数组 。
📑代码示例:
public class TestDemo {
public static int binarySearch(int[] array,int key) {
int left = 0;
int right = array.length-1;
while (left <= right) {
int mid = (left + right) / 2;
if (array[mid] > key) {
right = mid - 1;
}else if (array[mid] < key) {
left = mid + 1;
}else {
return mid;
}
}
return -1;
}
public static void main(String[] args) {
int[] array = {23,35,2,31,5,67};
Arrays.sort(array);
System.out.println("排序后的数组:" + Arrays.toString(array));
int ret = binarySearch(array,5);
if (ret == -1) {
System.out.println("查无此数");
}else {
System.out.println("下标为: " + ret);
}
}
}
🏸 代码结果:
💬代码解释:
首先要用 Array 这个类中的 sort 这个方法对数组进行排序,使之成为有序数组。
练习四:冒泡排序
📑代码示例:
public class TestDemo {
public static void bubbleSort(int[] array) {
for (int i = 0; i < array.length - 1; i++) {
boolean flag = true;
for (int j = 0; j < array.length - 1 - i; j++) {
if (array[j] > array[j + 1]) {
int tmp = array[j];
array[j] = array[j + 1];
array[j + 1] = tmp;
flag = false;
}
}
if (flag) {
break;
}
}
}
public static void main(String[] args) {
int[] array = {8,3,5,7,2};
System.out.println("排序之前" + Arrays.toString(array));
bubbleSort(array);
System.out.println("排序之后" + Arrays.toString(array));
}
}
🏸 代码结果:
💬代码解释:
冒泡排序是一种简单基础的排序算法,实现原理是重复排序数组序列,并比较每一对相邻的元素。
-
有 n 个元素时,一共将进行 n - 1 趟冒泡排序。 -
第i (1 ~ n - 1) 趟排序需要进行 n - 1 - i 次比较。每当元素顺序不正确时(在这里实现的是升序),就进行交换。 -
为了提高效率,在每次进入一趟新的排序时,定义一个 flag 变量,初始化为 true 。如果某一趟冒泡排序,有进行交换这一操作,就将 flag 赋值为 false ;如若一整趟冒泡排序下来没有进行交换元素,flag 始终为true ,说明该数组已经排序完成,就可以提前跳出循环,完成一整个排序过程。
完!
|