0
点赞
收藏
分享

微信扫一扫

Java基础语法|数组

小桥流水2016 2022-04-13 阅读 77
java

文章目录

1.什么是数组

什么是数据结构

2.数组的创建与初始化

数组的动态初始化

写法一:数据类型[ ] 数组名称 = new 数组类型[ ] {初始化数据} ; - 大括号可选

int [] arr = new int[] {1,3,5,7,9};

写法二:数据类型[ ] 数组名称 = new 数组类型[num]; - num表示当前数组的最大元素

int [] arr = new int[5];

两种写法的区别只不过是有没有初始化

在创建数组时,若没有使用{}来初始化每个元素的值,每个元素都是该数据类型的默认值

数组的静态初始化

数据类型[] 数据名称 = {初始化数据}

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

实际上,静态初始化就是Java的语法糖,也就是说,javac编译过后,就是动态初始化:

在这里插入图片描述

在IDEA中可以通过javac命令后,观察class文件可以看到:

在这里插入图片描述

int[] data = new int[n];

Java在定义数组的时候,可以是变量,因为变量在使用的时候是确定值,即使后面改变了他的值,但空间已经开辟好了,不会因为变量值的改变而改变

3.数组的使用

获取数组的长度

语法:数组名称.length

int[] arr1 = new int[] {1,3,5,7,9};
int[] arr2 = new int[5];
System.out.println(arr1.length);//执行结果 5
System.out.println(arr2.length);//执行结果 5

如何访问数组元素

使用:数组名称[要访问的元素相较于第一个元素的偏移量]

等同于:数组名称[元素的索引]

元素的索引从0开始计算,最后一个元素的索引arr.length - 1

int[] arr1 = new int[] {1,3,5,7,9};
int[] arr2 = new int[5];
System.out.println(arr1[0]);//执行结果 1
System.out.println(arr1[4]);//执行结果 9
System.out.println(arr1[6]);//报错:数组异常访问,即越界

什么是索引,为什么从0开始?

索引起始就是“偏移量”,相较于数组的第一个元素的单位长度

存储数据13579
索引/偏移量[0][1][2][3][4]

数组在内存中存储时,每个元素之间都是顺序存储的

保存的就是数组的首元素的地址,要找到其他元素,只要知道其他元素相较于第一个元素的距离就行

遍历数组

int[] arr1 = new int[] {1,3,5,7,9};
//访问数组arr1的每个元素
for (int i = 0; i < arr1.length; i++) {
		System.out.print(arr1[i] + " ");
}
System.out.println();

//JDK1.5引入的for-each循环,增强型for循环
for(int i : arr1){
		System.out.print(i + " ");
}
//两者的执行结果完全一致

但是两种循环里的i代表着不同的意思:

第一种循环里的i表示数组中每个元素的索引下标

第二种循环里的i指的是从数组的第一个元素开始取取值,第一次把第一个元素的值拷贝一份给i,第二次循环把第二个元素的值拷贝一份给i,依次类推,直到整个数组都遍历结束

第二种循环只能读取数组的元素值,无法修改,i是原数组每个元素的值拷贝,并不是实实在在的数组元素
而第一种里的arr[i]确实拿到了每个数组的元素

for循环:

for (int i = 0; i < arr1.length; i++) {
		if( i == 2){
				arr1[i] = 55;
		}
}
System.out.println("第三个元素的值为:" + arr1[2]);
//执行结果为 55

增强for-each循环:

for(int i : arr1){
		if(i == 5){
				i = 55;//尝试把数组中值为5的元素改为55
		}
}
System.out.println("第三个元素的值为:" + arr1[2]);
//执行结果为 5
//也就是说arr1中的内容并没有别修改

4.数组和方法之间的关系

数组是引用数据类型,比如int[]是对整型数组的引用

数组作为方法的参数

创建一个方法,接收任意的整型数组并打印:

public static void printArr(int[] num){
  for(int i : num){
    System.out.print(i + " ");
  }
}
//int[] num 是形参

交换两个整型:

   public static void main(String[] args) {
        int x = 10;
        int y = 20;
        swap(x, y);
        System.out.println("x = " + x + ", y = " + y);
    }

    public static void swap(int a , int b){
        int tmp = a;
        a = b;
        b = tmp;
    }
//执行结果 a = 10, b = 20

如果创建两个变量,然后利用方法交换变量时,实际上是把位于主方法中的临时变量,即实参传递给交换方法的,方法接收的不过是实参的临时拷贝,即形参

理解引用类型

JVM把内存划分为6个区域,今天只重点讲“栈区”和“堆区”

方法的调用就是在栈区进行的,每个方法的调用过程,就是一个栈帧的入栈和出栈的过程

“栈” - 先进后出的结构,LIFO

堆 - 看待new关键字,new出来的对象都在堆中保存

方法中的局部变量和形参都在栈中存储,当方法调用结束出栈时,临时变量都会销毁

在这里插入图片描述

利用数组交换

    public static void main(String[] args) {
        int[] arr = {10, 20};
        swapArr(arr);
        System.out.println("arr[0] = " + arr[0] + ", arr[1] = " + arr[1]);
    }

    public static void swapArr(int[] arr){
        int temp = arr[0];
        arr[0] = arr[1];
        arr[1] = temp;
    }
//执行结果 arr[0] = 20, arr[1] = 10
//可见确实是被修改了

JVM的另一块内存区域称为堆区,堆区存放了所有对象,这个区域是所有线程共享的

包括:数组对象,类的实例化对象,接口的对象

什么是引用:

int[] arr = new int[] {10, 20};

后半部分,用new出来的是对象,这里就是数组对象,在堆上存储

前半部分int[] arr就是数组引用

类比生活:

回到这个代码:

int[] arr = new int[] {10, 20};

程序的执行都是从右向左,执行的时候程序先看到new,会在堆中创建这个数组对象

在这里插入图片描述

数组对象在堆中也是实实在在存在的,通过交换方法中的int[] arr里存的地址,修改了堆中数组对象的值,这个修改对于主方法中的arr是可见的

本质上,两个引用都指向了堆中同一块区域

我们修改一下swap方法:

public static void main(String[] args) {
		int[] arr = {10, 20};
		swapArr(arr);
		System.out.println("arr[0] = " + arr[0] + ", arr[1] = " + arr[1]);
}

public static void swapArr(int[] arr){
		arr = new int[] {10, 20};//在java中,看见new关键字,就一定在堆中开辟了新的空间
		int temp = arr[0];
		arr[0] = arr[1];
		arr[1] = temp;
}
//执行结果 arr[0] = 10, arr[1] = 20
//可见arr中的值并没有被修改

刚进入swap时的内存图:

在这里插入图片描述

执行了swap中第一条语句之后的内存图:

在这里插入图片描述

调用完swap之后的内存图:

在这里插入图片描述

关于会否会内存泄露的问题:

什么时候会释放?

由于swap出栈之后,没有任何变量指向0x200,如果想要调出swap中的arr,只能在main中创建新的数组变量来接收swap中的arr中存储的首元素地址

public static void main(String[] args) {
		int[] arr = {10, 20};
		int[] ret = swapArr(arr);
		System.out.println("arr[0] = " + arr[0] + ", arr[1] = " + arr[1]);
		System.out.println("ret[0] = " + ret[0] + ", ret[1] = " + ret[1]);
}

public static int[] swapArr(int[] arr){
    arr = new int[] {10, 20};
    int temp = arr[0];
    arr[0] = arr[1];
    arr[1] = temp;
    return arr;
}
//执行结果
//arr[0] = 10, arr[1] = 20
//ret[0] = 20, ret[1] = 10

这里在swap在结束之前,把arr的值返回给main,这样,swap中的arr指向堆中的空间的地址就会被保存下来,在main中,就可以继续通过swap的返回值来访问堆中的空间

5.数组练习

在JDK中的某些类后面加s的,这种类都是工具类,提供了大量有用的方法,可以直接调用

Arrays - 数组的工具类,包含数组转字符串的方法,数组排序的方法,等等操作数组的各种方法都在这个类中,我们直接通过类名称来调用

Collections - 集合的工具类

public static void main(String[] args) {
    int[] arr = {1,3,5};
    String str = Arrays.toString(arr);
    System.out.println(str);
}
//执行结果 [1, 3, 5]

数组对象转为字符串对象

模拟实现Arrays.toString

public static void main(String[] args) {
    int[] arr = {1,3,5};
    String str = arrToString(arr);
    System.out.println(str);
}

public static String arrToString(int[] arr){
    String ret = "[";
    for (int i = 0; i < arr.length; i++) {
        ret += arr[i];
        //如果是数组中的最后一个元素,不需要再加分隔符
        if(i != arr.length - 1) {
            ret += ", ";
        }
    }
    ret += "]";
    return ret;
}
//执行结果 [1, 3, 5]

拷贝数组

Arrays.copyOf(data, length)

data - 是原数组,length - 是需要拷贝的新数组的长度

几种情况:

模拟实现 Arrays.copyOf

public static void main(String[] args) {
    int[] arr = {1,2,3,4,5,6,7,8};
    int[] ret = arrayCopyOf(arr, 10);
    System.out.println(Arrays.toString(ret));
}

public static int[] arrayCopyOf(int[] arr, int length) {
    int[] ret = new int[length];
    if(length <= arr.length){
        for (int i = 0; i < length; i++) {
            ret[i] = arr[i];
        }
    }else{
        for (int i = 0; i < arr.length; i++) {
            ret[i] = arr[i];
        }
        for (int i = arr.length; i < length; i++) {
            ret[i] = 0;
        }
    }
    return ret;
}

Arrays.copyOfRange(data, start, over) - 数组区间拷贝

从下标开始位置拷贝,直到结束为止,区间是左闭右开的,即 [ strat, over )

[就是从下标为start的地方一直到下标over,但不拷贝下标为over的值

public static void main(String[] args) {
    int[] arr = {1,2,3,4,5,6,7,8};
    int[] ret = Arrays.copyOfRange(arr, 1, 4);
    System.out.println(Arrays.toString(ret));
}
//执行结果 [2, 3, 4]

找整数数组最大值

给定一个整数数组,找出这个数组的最大值 - “打擂”思想

找到一个集合的最大/最小值,默认取第一个数组元素,并假定其为最大/最小值,但不能取一个随机的数组

比如int max = 0;这是不可取的,如果数组全是负数,那么0就是最大值,但0不是数组元素

public static void main(String[] args) {
    int[] arr = {1,5,3,5,7,8,9,2,4,6};
    int max = arrMax(arr);
    System.out.println("max = arr[" + max + "] = " + arr[max]);
}

public static int arrMax(int[] arr){
    int max = 0;
    for (int i = 0; i < arr.length; i++) {
        //如果有元素大于max,则赋值给max,否则什么都不做
        if(arr[max] < arr[i]){
            max = i;
        }
    }
    return max;
}
//执行结果:
//max = arr[6] = 9

求数组的平均值

public static void main(String[] args) {
    int[] arr = {1,3,5,7,9};
    System.out.println("avg = " + arrAvg(arr));
}

public static double arrAvg(int[] arr){
    double sum = 0;
    for (int i : arr) {
        sum += i;
    }
    return sum / arr.length;
}
//执行结果 5

查找指定元素

查找一个数组中是否包含指定元素,若存在,返回该元素的索引,若不存在,返回-1

如果数组中存在多个相同的元素,默认返回第一个

public static void main(String[] args) {
    int[] arr = {1,5,3,5,7,8,9,2,4,6};
    System.out.println(arrFind(arr, 7));
}

public static int arrFind(int[] arr, int key){
    for (int i = 0; i < arr.length; i++) {
        //判断key是否和数组元素相等,相等则返回下标
        if(key == arr[i]){
            System.out.print("找到了,下标是");
            return i;
        }
    }
    //没有找到对应的元素,返回 -1
    return -1;
}
//执行结果:
//找到了,下标是4

二分查找

前提:在有序(升序或降序)的集合上,才能使用二分查找

在有序区间中查找一个元素key,我们不断比较待查找元素和区间中间位置元素的大小关系

若key < arr[mid],则说明这个元素一定左区间,一定小于arr[mid, right]的所有元素

right = mid - 1开始继续判断

若key == arr[mid],说明中间位置元素恰好就是待查找元素,直接返回mid即可

若key > arr[mid],则待查找元素大于左区间所有值,left = mid + 1继续在右区间中查找元素

public static void main(String[] args) {
    int[] arr = {1,2,3,4,5,6,7,8,9};
    System.out.println(binarySearch(arr, 8));
}

public static int binarySearch(int[] arr, int key){
    int left = 0;
    int right = arr.length - 1;
    while(left <= right){
        int mid = (left + right) / 2;//mid每次循环都会变
        if(key < arr[mid]){
            right = mid - 1;
        }else if(key > arr[mid]){
            left = mid + 1;
        }else{
            System.out.print("找到了,下标是");
            return mid;
        }
    }
    return -1;
}
//找到了,下标是7

当一个有序集合的长度为n时,顺序查找法,比较的次数为n

二分查找则是经过 n/2/2/2/2/2… == 1后找到,即log2 n

递归实现二分查找:

public static void main(String[] args) {
    int[] arr = {1,2,3,4,5,6,7,8,9};
    System.out.println(binarySearch(arr, 1, 0, arr.length - 1));
}

public static int binarySearch(int[] arr, int key, int left, int right){
    if(left > right){
        //找不到,直接返回
        return -1;
    }
    int mid = (left + right) / 2;
    if(key < arr[mid]){
        //key在左区间
        return binarySearch(arr, key, left, mid - 1);
    }
    if(key > arr[mid]){
        //key在右区间
        return binarySearch(arr, key, mid + 1, right);
    }
    //如果上面都不满足,说明找到了
    System.out.print("找到了,下标是");
    return mid;
}
//执行结果:找到了,下标是0

判断数组是否有序

以升序为例,如果有序,那么前一个元素 <= 后一个元素,否则就不是有序数组

public static void main(String[] args) {
    int[] arr = {1,3,4,5,6,2,9};
    System.out.println(isSorted(arr));
}
public static boolean isSorted(int[] arr){
    for (int i = 0; i < arr.length - 1; i++) {
        if(arr[i] > arr[i + 1]){
            System.out.println("不是有序数组");
            return false;
        }
    }
    System.out.println("是有序数组");
  	return true;
}
//执行结果:
//不是有序数组
//false

写循环的边界条件时,最大的取值不能越界,尤其是类似于 i + 1 这样的下标,必须保证 i + 1不能越界

冒泡排序

一般排序都排升序

核心思想:假设现在数组有n个元素,每进行一次遍历过程,就将当前数组中最大值放在数组末尾

public static void main(String[] args) {
    int[] arr = {9,8,7,6,5,4,3,2,1};
    bubbleSort(arr);
    System.out.println(Arrays.toString(arr));
}

public static void bubbleSort(int[] arr){
    for (int i = 0; i < arr.length - 1; i++) {
        for (int j = 0; j < arr.length - i - 1; j++) {
            if(arr[j] > arr[j+1]){
                int tmp = arr[j];
                arr[j] = arr[j + 1];
                arr[j+1] = tmp;
            }
        }
    }
}
// 执行结果
// [1, 2, 3, 4, 5, 6, 7, 8, 9]

冒泡优化:

如果内存没有进行交换,则说明,该数组已经有序,无需再进行排序

public static void main(String[] args) {
    int[] arr = {9,8,7,6,5,4,3,2,1};
    bubbleSort(arr);
    System.out.println(Arrays.toString(arr));
}

public static void bubbleSort(int[] arr){
    for (int i = 0; i < arr.length - 1; i++) {
        boolean isSorted = true;
        for (int j = 0; j < arr.length - i - 1; j++) {
            if(arr[j] > arr[j+1]){
                isSorted = false;
                int tmp = arr[j];
                arr[j] = arr[j + 1];
                arr[j+1] = tmp;
            }
        }
      //如果遍历一遍,没有发生交换,数组必然有序,因此没必要再排序
        if(isSorted){
            return;
        }
    }
}

数组逆序

public static void main(String[] args) {
    int[] arr = {1,2,3,4};
    int[] arr1 = {1,2,3,4,5};
    reverse(arr);
    reverse(arr1);
    System.out.println(Arrays.toString(arr));
    System.out.println(Arrays.toString(arr1));
}

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--;
    }
}
//[4, 3, 2, 1]
//[5, 4, 3, 2, 1]

while循环和for循环的区别:

数组的数字排列问题

给定一个整型数组,将所有的偶数放在前半部分,将所有的奇数放在数组后半部分

思考方法:

从前向后找到第一个奇数停止,从后向前找到第一个偶数停止,交换
然后继续,知道left >= right

不过我们要考虑到全是奇数或者偶数的可能

public static void main(String[] args) {
    int[] arr = {1,2,3,4,5,6};
    swap(arr);
    System.out.println(Arrays.toString(arr));
}

public static void swap(int[] arr){
    int left = 0;
    int right = arr.length - 1;
    while(left < right){
        //如果全是奇数或者偶数,内层循环坑会越界,因此也要判断left < right
        while(left < right && arr[left] % 2 == 0){
            left++;
        }
        //如果right左边有奇数,left一定停留在从左到右的第一个奇数,否则一直执行到left==right

        while(left < right && arr[right] % 2 == 1){
            right--;
        }
        //如果left右边有偶数,right一定停留在从右到左的第一个偶数,否则一直执行到left==right

        int tmp = arr[left];
        arr[left] = arr[right];
        arr[right] = tmp;
        left++;
        right--;
    }
}
//执行结果 [6, 2, 4, 3, 5, 1]

6.二维数组

所谓的二维数组就是多了列的概念

语法:

public static void main(String[] args) {
    int[][] arr = new int[][] {
            {1,2,3},
            {4,5,6},
            {7,8,9}
    };//内层的大括号表示一行
    for(int row = 0;row < arr.length;row++){
        for(int col = 0;col < arr[row].length;col++){
            System.out.print(arr[row][col] + " ");
        }
        System.out.println();
    }
}
//执行结果
1 2 3 
4 5 6 
7 8 9 

实际上,二维数组里的每一行就是个一维数组,二维数组里存储的是一维数组的地址

举报

相关推荐

0 条评论