数组
🌈一,初识数组
🌟1.1,数组定义
数组:可以看成是相同类型元素的一个集合。在内存中是一段连续的空间。
🌟1.2,数组创建及初始化
1.2.1,创建
类型名[] 数组名 = new 类型名[元素个数]
可以看到,Java里面的数组与C里面的数组还是有很大的区别的,一是创建的写法,二是创建数组的位置(在堆区)。另外数组是一个引用类型,它指向的是一个数组对象。
1.2.2,初始化
动态初始化:
int[] arr1 = new int[10]
在初始化时直接指定元素的个数,但是没有赋值,需要后期手动的去赋值。
静态初始化:
int[] arr2 = {1,2,3,4,5};
int[] arr3 = new int[]{1,2,3,4,5};//这种赋值的时候,后面的[] 里面不要写元素个数,编译器会自动判断
上面这两种静态初始化就是在创建的时候同时直接赋值,两种方法的本质是一样的,第一种是一种简写(编译的时候编译器会自动还原)。
🚩注意:
🌖1,在Java里面要注意一点,你一旦把长度写定了,就不能改了,如果在运行的时候需要扩展数组的大小,那么就得用到另一种数据结构—数组列表(array list)。
🌖2,数组也可以按照C语言的方法创建,不推荐。(知道它不是错的就行,不要这样用,选择性忘记,而且本身Java的写法才是标准的形式)
int arr[] = {1,2,3};//不推荐,int[] 这样写可以更好的明晰arr是一个数组类型变量
🌖3,静态和动态初始化也可以分为两步,但是省略格式不可以。
int[] arr = new int[10];
//可以分段写为
int[] arr;
arr = new int[10];
------------------------
int[] arr = new int[]{1,2,3,4};
//可以分段写为
int[] arr;
arr = new int[]{1,2,3,4};
------------------------
//但是对于本身已经是简写模式下的初始化的 int[] arr = {1,2,3,4} 是不能分段写的
int[] arr;
arr = {1,2,3,4};//注意!!!这是错的!
🌖4,在Java里面,支持数组的大小是一个定义初始化好的变量。
int n = 10;
int[] arr4 = new int[n];//这是没有任何问题的
🌖5,采用动态初始化的方式,还没有给数组赋值时,数组元素是有默认值的。
这是基本的数据类型,如果数组元素也是引用类型,也就是说里面待存放的是地址的话,那么默认值就是null。
🌖6,在Java里面,允许有长度为0的数组。
int[] arr = new int[0] 或者 int[] arr = new int[]
//但是注意,长度为0与null是不一样的
🌈二,数组元素的访问输出
🌟2.1,for循环
public class TestDemo220429 {
public static void main(String[] args) {
int[] arr = {1,2,3,4,5};
for(int i = 0;i < arr.length;i++){
System.out.print(arr[i] + " ");
}
}
}
这是老方法了,只是需要注意下Java里面求取数组长度的方法即可。
🌟2.2,foreach
public class TestDemo220429 {
public static void main(String[] args) {
int[] arr = {1,2,3,4,5};
for(int x:arr){
System.out.print(x + " ");
}
}
}
🌟2.3,以字符串的形式打印
System.out.println(Arrays.toString(arr));//为了更加方便的去操作数组,有很多的方法,在工具类Arrays中
//将传进来的字符串以字符串的形式打印
🌟2.4,三种形式的输出结果
🌈三,数组是引用类型的变量
🌟3.1,初步认识JVM内存布局
我们现阶段可能接触的最多的还是虚拟机栈以及堆区这两个内存分区,那么我们用代码来具体分析一下二者的关系!
🌟3.2,引用变量使用示例
public class TestDemo220429 {
public static void main(String[] args) {
int[] array1 = {1,2,3};
int[] array2 = {4,5,6};
array2 = array1;
for (int x:array2) {
System.out.print(x + " ");
}
}
}
其实对于引用就当成是一个普通变量就好,只不过就是这个变量里面存放的是地址,其它没有什么差别,上面的这段代码的输出结果是 1 2 3,原理如下:
这里补充一个点,就是当某一个对象不在被指向(引用)的时候,就会被JVM的垃圾回收器给回收掉,并不需要我们手动释放。
🌟3.3,认识null
int[] arr = null;
可以将一个引用类型的变量赋值为null,表示它不指向任何的对象,也就是空对象。所以你不能对这个引用变量arr做任何的操作,因为它没有实体的对象,而这也就是我们可能常会看到的空指针异常的原因。
🌟3.4,一个题加固对引用的认识
public class TestDemo220429 {
public static void func1(int[] arr1){
arr1 = new int[]{1,2,3};
}
public static void func2(int[] arr2){
arr2[0] = 9;
}
public static void main(String[] args) {
int[] arr = {8,8,8};
func1(arr);
for (int x:arr) {
System.out.print(x + " ");
}
System.out.println();
func2(arr);
for (int x:arr) {
System.out.print(x + " ");
}
}
}
程序运行截图:
总结:传引用调用的时候,形参可以改变实参指向的对象的内部元素的值,但是不能改变实参的指向,最多只能改变形参其自身的指向。
🌈四,数组的应用
🌟4.1,传参传数组类型
🌟4.2,作为返回值
//写一段代码将array数组中的元素变为2倍
public class TestDemo220429 {
public static int[] getDouble(int[] array){
for(int i = 0;i < array.length;i++){
array[i] *= 2;
}
return array;
}
public static void main(String[] args) {
int[] array = {1,2,3};
int[] ret = getDouble(array);
System.out.println(Arrays.toString(ret));
}
}
程序运行截图:
🌈五,数组练习
数组里面会用到的很多方法都是放在我们的工具类Arrays里面的。
🌟5.1,数组转字符串
数组转字符串利用 Arrays类里面的toString方法是可以直接完成的,但是下面我们来模拟实现一下这个简单的功能,如下:
public class TestDemo220429 {
public static String myToString(int[] array){
String tmp = "[";
for(int i = 0;i < array.length;i++){
tmp += array[i];
if(i != array.length - 1){
tmp += ",";
}
if(i == array.length - 1){
tmp += "]";
}
}
return tmp;
}
public static void main(String[] args) {
int[] array = {1,2,3};
String ret = myToString(array);
System.out.println(ret);
}
}
原理其实很简单,就是利用了Java里面的 “ +” 号的对于字符串的拼接特性。
🌟5.2,数组拷贝
5.2.1,for循环拷贝
public class TestDemo220429 {
public static int[] copyArray(int[] array){
int[] tmp = new int[array.length];
for(int i = 0;i < array.length;i++){
tmp[i] = array[i];
}
return tmp;
}
public static void main(String[] args) {
int[] array = {1,2,3,4,5};
int[] ret = copyArray(array);
System.out.println(Arrays.toString(ret));
}
}
基本原理就是创建一个中间数组,然后一个元素一个元素的进行拷贝。
5.2.2,利用Arrays类方法
public class TestDemo220429 {
public static void main(String[] args) {
int[] array = {1,2,3,4,5};
int[] ret1 = Arrays.copyOf(array,array.length);
System.out.println(Arrays.toString(ret));
}
}
在工具类Arrays中,有专门的用于数组拷贝的方法供我们使用。方法具体的解析我们可以看一下源码,如下:
这里是以int类型的数组作为展示,其实在Arrays类里面,每种类型的数组都会有一个自己对应的拷贝的方法,是可以直接使用的。
5.2.3,利用System类的方法
import java.lang.System;
import java.util.Arrays;
public class TestDemo220430 {
public static void main(String[] args) {
int[] array = {1,2,3,4,5};
int[] ret = new int[array.length];
System.arraycopy(array,0,ret,0,array.length);
System.out.println(Arrays.toString(ret));
}
}
源码解析:
arraycopy是一个System类里面的本地方法,因为是用C/C++代码实现的,所以运行起来效率速度肯定是更快的,另外,如果仔细观察会发现,在上面的Arrays.copyof()方法里面的实际拷贝过程调用的就是arraycopy这个native方法。
5.2.3,克隆方法
public class TestDemo220430 {
public static void main(String[] args) {
int[] array = {1,2,3,4,5};
int[] ret = new int[array.length];
ret = array.clone();
System.out.println(Arrays.toString(ret));
}
}
clone()产生的是这个数组对象的一个副本,此方法属于Object类(是所有类的父类)。
5.2.3,深浅拷贝问题
上面介绍完了四种拷贝数组的方法,但是说到拷贝,我们就不得不谈到深浅拷贝的问题,那下面我们就来浅浅的分析一下…
首先,认识认识深浅拷贝的定义:
那么对于上面的四种拷贝方法,我们的结论是,从本质上都是浅拷贝的。
🚩注意:
🌖1,对于基本数据类型的数组:
可以看到对于基本数据类型的数据,拷贝过去的对象与原来的是毫不相干,互不影响的,那又为什么说本质上是浅拷贝呢?因为还有引用类型的变量没有考虑…
🌖2,对于引用类型变量的数组
🌟5.3,找出数组元素的最大值
public class TestDemo220430 {
public static int getMaxnum(int[] array){
int max = Integer.MIN_VALUE;
int min = Integer.MAX_VALUE;
for(int i = 0;i < array.length;i++){
if(array[i] > max){
max = array[i];
}
if(array[i] < min){
min = array[i];
}
}
return max;//最大值
//return min // 最小值
}
public static void main(String[] args) {
int[] array = {12,13,4,5,6,88};
int ret = getMaxnum(array);
System.out.println("最大值是:" + ret);
}
}
🌟5.4,查找数组中的指定元素
5.4.1,顺序查找
import java.util.Scanner;
public class TestDemo220430 {
public static int find(int[] arr,int key){
for(int i = 0;i < arr.length;i++){
if(arr[i] == key){
return i;
}
}
return -1;//找不到就返回-1
}
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
System.out.println("请输入你要查找的数:");
int key = scan.nextInt();
int[] arr = {1,2,3,4,5};
int ret = find(arr,key);
if(ret != -1){
System.out.println("找到了,下标为:" + ret);
}else{
System.out.println("未找到!");
}
}
}
顺序查找,最朴素的查找方法,原理就是遍历数组一个个对比即可。
5.4.2,二分查找
import java.util.Scanner;
public class TestDemo220430 {
public static int find1(int[] arr,int key){
int left = 0;
int right = arr.length - 1;
while(left <= right){
int mid = (left + right)/2;
if(arr[mid] > key){
right = mid - 1;
}else if(arr[mid] < key){
left = mid + 1;
}else{
return mid;
}
}
return -1;
}
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
System.out.println("请输入你要查找的数:");
int key = scan.nextInt();
int[] arr = {1,2,3,4,5};
int ret = find1(arr,key);
if(ret != -1){
System.out.println("找到了,下标为:" + ret);
}else{
System.out.println("未找到!");
}
}
}
二分查找相信大家也听过,它的原理就是不断地拆分成一半然后去查找直至最后找到那个数,但是需要注意的是他只能针对于有序的数组,因为我们是每次和中间下标的元素比值,然后决定去哪半边找,如果是无序的,这将没有任何的意义。
当然,上面这段代码还不是最优的,还有一点点小毛病需要优化。
int mid = (left + right)/2;
//这里最好的写法是 int mid = left + (right - left)>>>1 ,无符号右移就是相当于除2(正数负数都一样),另外 (left + right)如果在数组比较长的情况下,有可能会超过整形的表示范围,所以最好改成 left + (right - left)>>>1
上面是我们自己的模拟实现,但是Java这么强大方便的语言怎么会让你自己去写一个二分查找的方法呢,直接就给你安排好了类方法,直接用就完事了!
import java.util.Scanner;
public class TestDemo220430 {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
System.out.println("请输入你要查找的数:");
int key = scan.nextInt();
int[] arr = {1,2,3,4,5};
int ret1 = Arrays.binarySearch(arr,key);//工具类Arrays里面有二分查找的方法
System.out.println("下标为:" + ret1);
}
}
对于这个方法,比较有意思的就是它的返回值,如果找到了就还是返回相应的下标值,如果是没找到,就是最后的 -(left + 1)。
既然说到了类方法的二分查找,那我们来具体的看看定义(这里只是以整形的数组的二分查找法为例,每个类型的数组都有相应的二分查找法):
我们上面的查找是在整个数组范围下进行的,其实也是可以在范围内查找的,也就是[fromIndex , toIndex) 特别要注意一下这里是左闭右开的范围!
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
System.out.println("请输入你要查找的数:");
int key = scan.nextInt();
int[] arr = {1,2,3,4,5};
int ret1 = Arrays.binarySearch(arr,1,3,key);
System.out.println("下标为:" + ret1);
}
🌟5.5,判断一个数组是否是升序的
public class TestDemo220430 {
public static boolean isUp(int[] array){
for(int i = 0;i < array.length;i++){
if(array[i] > array[i + 1]){
return false;
}
}
return true;
}
public static void main(String[] args) {
int[] array = {2,3,1,4,6,8,7};
boolean ret = isUp(array);
System.out.println(ret);
}
}
🌟5.6,冒泡排序优化版
public class TestDemo220430 {
public static void bubbleSort(int[] array){
for(int i = 0;i < array.length - 1;i++){
int flg = 1;//定义一个标签,默认每一轮是有序的
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;
flg = 0;//如果是if满足了,就说明是无序的,将flg = 0
}
}
if(flg == 1){
break;//如果比了一轮flg没变,就说明是有序的,不需要再比了
}
}
}
public static void main(String[] args) {
int[] array = {1,2,3,5,6,4};
bubbleSort(array);
System.out.println(Arrays.toString(array));
}
}
当然,Java里面是有Arrays.sort()的排序方法的,比冒泡排序的效率会高很多。
🌟5.7,数组逆序
public class TestDemo220430 {
public static void reverse(int[] array){
int left = 0;
int right = array.length - 1;
while(left < right){
int tmp = array[left];
array[left] = array[right];
array[right] = tmp;
left++;
right--;
}
}
public static void main(String[] args) {
// 数组的逆置
int[] array = {1,2,3,4,5};
reverse(array);
System.out.println(Arrays.toString(array));
}
}
🌟5.8,数组元素的排列(偶数在前,奇数在后)
public class TestDemo220430 {
public static void swap(int[] arr){
int left = 0;
int right = arr.length - 1;
while(left < right){
while(left < right && arr[left]%2 == 0){
left++;//偶数放前面,所以只要前面有偶数,left就往后走
}//跳出就说明遇到奇数了
while(left < right && arr[right]%2 != 0){
right--;//奇数放后面,所以只要后面有奇数,right就往前走
}//跳出就说明遇到偶数了
if(left < right){//当right与left相遇了就不用交换了
int tmp = arr[left];
arr[left] = arr[right];
arr[right] = tmp;
}
}
}
public static void main(String[] args) {
int[] arr = {1,2,3,4,5,6,7,8,9};
swap(arr);
System.out.println(Arrays.toString(arr));
}
}
🌈六,二维数组(重点理解内存结构)
🌟6.1,二维数组的定义
二维数组的定义方式:
int[][] array1 = new int[2][3];
int[][] array2 = {{1,2,3},{4,5,6}};
int[][] array = new int[][]{{1,2,3},{4,5,6}};
🌟6.2,二维数组的存储方式
那二维数组在内存中是怎么存储的呢?
🌟6.3,遍历输出二维数组
一,规则的二维数组
for循环打印:
public class TestDemo220430 {
public static void main(String[] args) {
int[][] array = new int[][]{{1,2,3},{4,5,6}};
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();
}
}
}
注意是怎么求二维数组的行数与列数的。
利用Arrays类方法:
System.out.println(Arrays.toString(array));//一维数组可以这么将数组转字符串打印,但是这是二维数组,array这个引用指向的也是一个一维数组,里面存的是地址,所以肯定是行不通的
既然如此,那我们二维数组肯定也是有特定的方法的,如下:
System.out.println(Arrays.deepToString(array));
foreach打印输出:
for (int[] tmp:array) {
for (int x:tmp) {
System.out.print(x + " ");
}
System.out.println();
}
foreach这里用的话就必须要嵌套使用才可以。
二,不规则的二维数组
public class TestDemo220430 {
public static void main(String[] args) {
int[][] array = new int[2][];
array[0] = new int[]{1,2,3};
array[1] = new int[]{4,5};
System.out.println(Arrays.deepToString(array));
}
}
C语言里面。二维数组的列数必须写,行数可以自动推导。但是在Java里面,你的行是必须指定的,并且列不可以自动推导,因为你如果不指定行数,那么你通过数组名这个引用变量访问到的一维数组都不确定,并且因为列数不能自动推导,所以就相当于现在这个一维数组里面的引用变量值是null。
可以看到,Java里面的二维数组是很神奇的,因为它的二维数组的每一行的列数竟然是可以不一样的,也就是说对于每一行,也就是那个一维数组里面的引用变量所指向的对象我们是可以单独各自赋值的。
今天的博客就到这了,字数有一点点小多,但是它够详细呀。如果看完觉得不错的话还请帮忙点点赞咯,十分感谢呢!🥰🥰🥰