1.数组的基本用法

1.1 什么是数组

数组本质上就是让我们能"批量"创建相同类型的变量
例如:
如果需要表示两个数据,那么直接创建两个变量即可 int a,int b
如果需要表示五个数据,那么可以创建五个变量 int a1,int a2,int a3,int a4,int a5
但是如果需要表示一万个数据,那么就不能创建一万个变量了,这时候就需要使用数组,帮我们批量创建
注意:在Java中,数组中包含的变量必须是相同类型

1.2 创建数组

基本语法

// 动态初始化 
数据类型[] 数组名称 = new 数据类型 [] { 初始化数据 }; 
// 静态初始化 
数据类型[] 数组名称 = { 初始化数据 };

代码示例

int[] arr = new int[]{1, 2, 3}; 
int[] arr = {1, 2, 3};

注意: 静态初始化的时候,数组元素个数和初始化数据的格式是一致的

其实数组也可以写成int arr[ ] = {1, 2, 3}
这样就和 C 语言更相似了,但是我们还是更推荐写成 int[ ] arr 的形式,int和 [ ] 是一个整体

1.3 数组的使用

代码示例: 获取长度&访问元素

int[] arr = {1, 2, 3}; 

// 获取数组长度 
System.out.println("length: " + arr.length); // 执行结果: 3 

// 访问数组中的元素 
System.out.println(arr[1]); // 执行结果: 2 
System.out.println(arr[0]); // 执行结果: 1 
arr[2] = 100; 
System.out.println(arr[2]);// 执行结果: 100

注意

  1. 使用 arr.length 能够获取到数组的长度, “.” 这个操作为成员访问操作符,后面在面向对象中会经常用到
  2. 使用 [ ] 按下标取数组元素,需要注意,下标从 0 开始计数
  3. 使用 [ ] 操作既能读取数据,也能修改数据
  4. 下标访问操作不能超出有效范围 [0, length - 1],如果超出有效范围,会出现下标越界异常

代码示例: 下标越界

int[] arr = {1, 2, 3}; 
System.out.println(arr[100]); 
// 执行结果 
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100
		at Test.main(Test.java:4)

抛出了 java.lang.ArrayIndexOutOfBoundsException 异常,使用数组一定要下标谨防越界

代码示例: 遍历数组
所谓 “遍历” 是指将数组中的所有元素都访问一遍,不重不漏,通常需要搭配循环语句

int[] arr = {1, 2, 3}; 
for (int i = 0; i < arr.length; i++) { 
	System.out.println(arr[i]); 
}
// 执行结果 
1
2
3

代码示例: 使用for-each遍历数组

int[] arr = {1, 2, 3}; 
for (int x : arr) { 
	System.out.println(x); 
}
// 执行结果 
1
2
3

for-each 是 for 循环的另外一种使用方式,能够更方便的完成对数组的遍历,可以避免循环条件和更新语句写错

2.数组作为方法的参数

2.1 基本用法

代码示例: 打印数组内容

public static void main(String[] args) { 
	int[] arr = {1, 2, 3}; 
	printArray(arr); 
}
public static void printArray(int[] a) { 
	for (int x : a) { 
		System.out.println(x); 
	} 
}
// 执行结果 
1
2
3

在这个代码中
int[] a 是函数的形参,int[] arr 是函数实参
如果需要获取到数组长度,同样可以使用 a.length

2.2 理解引用类型(重点)

我们尝试以下代码

代码示例1: 参数传内置类型

public static void main(String[] args) { 
	int num = 0; func(num); 
	System.out.println("num = " + num); 
}
public static void func(int x) { 
	x = 10; 
	System.out.println("x = " + x); 
}
// 执行结果 
x = 10 
num = 0

我们发现 修改形参x的值,不影响实参num的值

代码示例2: 参数传数组类型

public static void main(String[] args) { 
		int[] arr = {1, 2, 3, 4, 5}; 
		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

我们发现, 在函数内部修改数组内容,函数外部也发生改变
此时数组名 arr 是一个 “引用” ,当传参的时候,是按照引用传参

这里我们要先从内存说起

如何理解内存?

内存就是指我们熟悉的 “内存”,内存可以直观的理解成一个宿舍楼,有一个长长的大走廊,上面有很多房间

每个房间的大小是 1 Byte (如果计算机有 8G 内存,则相当于有 80亿 个这样的房间)

每个房间上面又有一个门牌号,这个门牌号就称为地址

Java定义一个Long数组 如何定义一个数组java_System

那什么又是引用?

引用相当于一个 “别名”,也可以理解成一个指针

创建一个引用只是相当于创建了一个很小的变量,这个变量保存了一个整数, 这个整数表示内存中的一个地址

假设我们分别创建上述代码中三个元素一样的数组,如图

Java定义一个Long数组 如何定义一个数组java_System_02

  1. 当我们创建数组array1的时候,就相当于在堆内存中创建了一块内存空间保存了5个int
  2. 接下来在执行又依次创建了array2,array3,此时的array1,2,3是三个数组的变量名,他们的类型就是引用类型,存放的分别是自己对应数组的内存地址
  3. 因此当数组里面的元素被修改之后,我们的三个引用类型变量还是保存的对应数组的内存地址,此时通过引用访问修改后的元素,实质上就是获取对应地址上的数据

总结: 所谓的 “引用” 本质上只是存了一个地址,Java 将数组设定成引用类型,这样的话后续进行数组参数传参,其实只是将数组的地址传入到函数形参中,这样可以避免对整个数组的拷贝(数组可能比较长,那么拷贝开销就会很大)

2.3 认识null

null在Java中表示"空引用",也就是一个无效的引用

int[] arr = null; 
System.out.println(arr[0]); 

// 执行结果 
Exception in thread "main" java.lang.NullPointerException at 
		Test.main(Test.java:6)

null 的作用类似于 C 语言中的 NULL (空指针),都是表示一个无效的内存位置,因此不能对这个内存进行任何读写操作,一旦尝试读写,就会抛出 NullPointerException(空指针异常)

注意: Java 中并没有约定 null 和 0 号地址的内存有任何关联

3.数组作为方法返回值

代码示例: 写一个方法将数组中每个元素*2

// 直接修改原数组 
class Test { 
	public static void main(String[] args) { 
		int[] arr = {1, 2, 3}; 
		transform(arr); 
		printArray(arr); 
		}
	public static void printArray(int[] arr) { 
		for (int i = 0; i < arr.length; i++) { 
			System.out.println(arr[i]); 
		} 
	}
	public static void transform(int[] arr) { 
		for (int i = 0; i < arr.length; i++) { 
			arr[i] = arr[i] * 2; 
		} 
	} 
}

这个代码固然可行,但是破坏了原有数组,有时候我们不希望破坏原数组,就需要在方法内部创建一个新的数组,并由方法返回出来

// 返回一个新的数组 
class Test { 
	public static void main(String[] args) { 
		int[] arr = {1, 2, 3}; 
		int[] output = transform(arr); 
		printArray(output); 
	}
	public static void printArray(int[] arr) { 
		for (int i = 0; i < arr.length; i++) { 
			System.out.println(arr[i]); 
		} 
	}
	public static int[] transform(int[] arr) { 
		int[] ret = new int[arr.length]; 
		for (int i = 0; i < arr.length; i++) { 
			ret[i] = arr[i] * 2; 
		}
		return ret; 
	} 
}

这样的话就不会破坏原有数组了

另外由于数组是引用类型,返回的时候只是将这个数组的首地址返回给函数调用者,没有拷贝数组内容,从而比较高效

4.数组联系

4.1 数组转字符串

代码示例 我们实现一个自己的方法将数组转成字符串

public static void main(String[] args) {
    int[] array = {1,2,3,4,5};
    toString(array);
}
//把数组转换成字符串
public static void toString(int[] array) {
    if (array == null) {
        return;
    }
    if (array.length == 0) {
        System.out.println("[ ]");
        return;
    }
    System.out.print("[");
    for (int i = 0; i < array.length; i++) {
        System.out.print(array[i] + ",");
        if (i == array.length-1) {
            System.out.println("]");
        }
    }
}

当然这是我们自己写的方法,使用起来也很不方便,因此我们直接使用Java自带的方法

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 包,其中包含了一些操作数组的常用方法

什么是包?
例如做一碗油泼面,需要先和面,擀面,扯出面条,再烧水,下锅煮熟,放调料,泼油,但是其中的 “和面,擀面, 扯出面条” 环节难度比较大,不是所有人都能很容易做好,于是超市就提供了一些直接已经扯好的面条,可以直接买回来下锅煮,从而降低了做油泼面的难度,也提高了制作效率.
程序开发也不是从零开始,而是要站在巨人的肩膀上
像我们很多程序写的过程中不必把所有的细节都自己实现,已经有大量的标准库(JDK提供好的代码)和海量的第三方库(其他机构组织提供的代码)供我们直接使用,这些代码就放在一个一个的 “包” 之中,所谓的包就相当于卖面条的超市,只不过, 超市的面条只有寥寥几种,而我们可以使用的 “包” 有成千上万

注意: 这里只是简单了解一下,后续学习会专门去学习

4.2 数组拷贝

代码示例

import java.util.Arrays 
int[] arr = {1,2,3,4,5,6}; 
int[] newArr = Arrays.copyOf(arr, arr.length); System.out.println("newArr: " + Arrays.toString(newArr)); 

arr[0] = 10; 
System.out.println("arr: " + Arrays.toString(arr)); System.out.println("newArr: " + Arrays.toString(newArr)); 

// 拷贝某个范围.
int[] newArr = Arrays.copyOfRange(arr, 2, 4); System.out.println("newArr2: " + Arrays.toString(newArr2));

注意事项: 相比于 newArr = arr 这样的赋值,copyOf 是将数组进行了深拷贝,即又创建了一个数组对象,拷贝原有数组中的所有元素到新数组中,因此,修改原数组,不会影响到新数组

Java定义一个Long数组 如何定义一个数组java_代码示例_03


Java定义一个Long数组 如何定义一个数组java_System_04

实现自己版本的拷贝数组

public class HomeWork {
    public static void main(String[] args) {
        int[] arrary = {1,2,3,4,5};
        System.out.println(Arrays.toString(copy(arrary)));
    }
    //拷贝数组,得到新的数组
    public static int[] copy(int[] array) {
        int[] copy = new int[array.length];
        for (int i = 0; i < array.length; i++) {
            copy[i] = array[i];
        }
        return copy;
    }

4.3 找数组中的最大元素

给定一个整型数组,找到其中的最大元素(找最小同理)
代码示例

public static void main(String[] args) { 
	int[] arr = {1,2,3,4,5,6}; 
	System.out.println(max(arr)); 
}
public static int max(int[] arr) { 
	int max = arr[0]; 
	for (int i = 1; i < arr.length; i++) { 
		if (arr[i] > max) { 
			max = arr[i]; 
		} 
	}
	return max; 
}
// 执行结果 
6

类似于 “打擂台” 这样的过程,其中 max 变量作为 擂台,比擂台上的元素大,就替换上去,否则就下一个对手

4.4 求数组中元素的平均值

给定一个整型数组求平均值
代码示例

public class HomeWork {
    public static void main(String[] args) {
        int[] arrary = {1,2,3,4,5,6};
        System.out.println(average(arrary));
    }
    //求数组中所有元素的平均值
    public static double average (int[] arrary) {
        int ret = 0;
        for (int i = 0; i < arrary.length; i++) {
            ret = ret + arrary[i];
        }
        return (double) ret /(double) arrary.length;
    }
}

注意: 结果用double类型来表示

4.5 查找数组中指定元素(顺序查找)

给定一个数组,再给定一个元素,找出该元素在数组中的位置
代码示例

public static void main(String[] args) { 
	int[] arr = {1,2,3,10,5,6}; 
	System.out.println(find(arr, 10)); 
}
public static int find(int[] arr, int toFind) { 
	for (int i = 0; i < arr.length; i++) { 
		if (arr[i] == toFind) { 
			return i; 
		} 
	}
	return -1; // 表示没有找到 
}
// 执行结果 
3

4.6 查找数组中指定元素(二分查找)

针对有序数组,可以采用效率更高的二分查找

以升序数组为例,二分查找的思路是先取中间位置的元素,看要找的值比中间元素大还是小,如果小,就去左边找,否则就去右边找

代码示例

public static void main(String[] args) {
    int[] array = {1,2,3,4,5};
    System.out.println(binArraySearch(array,3));
}
//二分查找
public static int binArraySearch(int[] array,int n) {
    int left = 0;
    int right = array.length-1;
    while (left <= right) {
        int mid = (left+right) / 2;
        if (array[mid] < n) {
        	//去右侧区域找
            left = mid + 1;
        }else if (array[mid] > n) {
        	//去左侧区域找
            right = mid - 1;
        }else {
        	//找到了
            return mid;
        }
    }
    //没找到
    return -1;
}

感受二分查找的效率

class Test { 
	static int count = 0; // 创建一个成员变量, 记录二分查找循环次数 
	public static void main(String[] args) { 
		int[] arr = makeBigArray(); 
		int ret = binarySearch(arr, 9999); 
		System.out.println("ret = " + ret + " count = " + count); 
	}
	public static int[] makeBigArray() { 
		int[] arr = new int[10000]; 
		for (int i = 0; i < 10000; i++) { 
			arr[i] = i; 
		}
		return arr; 
	}
	public static int binarySearch(int[] arr, int toFind) { 
		int left = 0; 
		int right = arr.length - 1; 
		while (left <= right) { 
			count++; // 使用一个变量记录循环执行次数 
			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; 
	} 
}
// 执行结果 
ret = 9999 count = 14

可以看到,针对一个长度为 10000 个元素的数组查找,二分查找只需要循环 14 次就能完成查找,随着数组元素个数越多, 二分的优势就越大

4.7 检查数组有序性

给定一个整型数组, 判断是否该数组是有序的(升序)

代码示例

public static void main(String[] args) {
    int[] array = {1,2,3,5,4};
    System.out.println(judgeSort(array));
}
//判断是否有序
public static boolean judgeSort(int[] array) {
    for (int i = 0; i < array.length-1; i++) {
        if (array[i] > array[i+1]) {
            return false;
        }
    }
    return true;
}

4.8 数组数字排列

给定一个整型数组,将所有的偶数放在前半部分,将所有的奇数放在数组后半部分
例如
{1, 2, 3, 4}调整后得到{4, 2, 3, 1}
基本思路
设定两个下标分别指向第一个元素和最后一个元素
用前一个下标从左往右找到第一个奇数,用后一个下标从右往左找到第一个偶数,然后交换两个位置的元素,依次循环即可

代码示例

public static void main(String[] args) { 
	int[] arr = {1, 2, 3, 4, 5, 6}; 
	transform(arr); 
	System.out.println(Arrays.toString(arr)); 
}
public static void transform(int[] arr) { 
	int left = 0; 
	int right = arr.length - 1; 
	while (left < right) { 
		// 该循环结束, left 就指向了一个奇数 
		while (left < right && arr[left] % 2 == 0) { 
			left++; 
		}
		// 该循环结束, right 就指向了一个偶数 
		while (left < right && arr[right] % 2 != 0) { 
			right--; 
		}
		// 交换两个位置的元素
		int tmp = arr[left]; 
		arr[left] = arr[right]; 
		arr[right] = tmp; 
	} 
}

4.9 数组排序(冒泡排序)

给定一个数组,让数组升序 (降序) 排序

算法思路

每次尝试找到当前待排序区间中最小(或最大)的元素,放到数组最前面(或最后面)

代码示例

public static void main(String[] args) {
    int[] array = {1,3,5,4,2};
    bubbleSort(array);
    System.out.println(Arrays.toString(array));
}
//冒泡排序
public static void bubbleSort(int[] array) {
    boolean flg = false;//默认不交换
    for (int i = array.length-1; i >= 0 ; i--) {
        flg = false;//每一次都有可能不交换
        for (int j = 0; j < i; j++) {
            if (array[j+1] < array[j]) {
                int tmp = array[j];
                array[j] = array[j+1];
                array[j+1] = tmp;
                flg = true;//交换
            }
        }
        if (flg = false) {//不交换了
            break;
        }
    }
}

冒泡排序性能较低,Java 中内置了更高效的排序算法

public static void main(String[] args) { 
	int[] arr = {9, 5, 2, 7}; 
	Arrays.sort(arr); 
	System.out.println(Arrays.toString(arr)); 
}

关于 Arrays.sort 的具体实现算法,我们在后面的排序算法上再详细介绍,到时候会介绍很多种常见排序算法

4.10 数组逆序

给定一个数组,将里面的元素逆序排列

思路

设定两个下标,分别指向第一个元素和最后一个元素,交换两个位置的元素,然后让前一个下标自增,后一个下标自减,循环继续即可

代码示例

public static void main(String[] args) { 
	int[] arr = {1, 2, 3, 4}; 
	reverse(arr); 
	System.out.println(Arrays.toString(arr)); 
}
public static void reverse(int[] arr) { 
	int left = 0;
	int right = arr.length - 1; 
	while (left < right) { 
		int tmp = arr[left]; 
		arr[left] = arr[right]; 
		arr[right] = tmp; 
		left++; right--; 
	} 
}

5.二维数组

二维数组本质上也就是一维数组,只不过每个元素又是一个一维数组

Java定义一个Long数组 如何定义一个数组java_Java定义一个Long数组_05


一个二维数组的每一行第一个元素又相当于是一个数组

基本语法

数据类型[][] 数组名称 = new 数据类型 [行数][列数] { 初始化数据 };

代码示例

int[][] arr = { 
	{1, 2, 3, 4}, 
	{5, 6, 7, 8}, 
	{9, 10, 11, 12} 
}
for (int row = 0; row < arr.length; row++) { 
	for (int col = 0; col < arr[row].length; col++) { 		
		System.out.printf("%d\t", arr[row][col]); 
	}
	System.out.println(""); 
}
// 执行结果 
1 2 3 4 
5 6 7 8 
9 10 11 12

二维数组的用法和一维数组并没有明显差别,因此我们不再赘述
同理,还存在 “三维数组”,“四维数组” 等更复杂的数组,只不过出现频率都很低