目录
5.二分查找(若待查找数组是一个无序数组,无法使用二分查找的。)
一、数组的定义
在 java 中,包含 6 个整形类型元素的数组,就相当于上图中连在一起的 6 个车位,从上图中可以看到:1. 数组中存放的元素其类型相同2. 数组的空间是连在一起的3. 每个空间有自己的编号,其实位置的编号为 0 ,即数组的下标那在程序中如何创建数组呢?
二、 数组的创建及初始化
数组的创建
int[] array1 = new int[10]; // 创建一个可以容纳10个int类型元素的数组
double[] array2 = new double[5]; // 创建一个可以容纳5个double类型元素的数组
String[] array3 = new double[3]; // 创建一个可以容纳3个字符串元素的数组
动态初始化:在创建数组时,直接指定数组中元素的个数(数组中每个元素都是其数据类型的默认值 )
int[ score = new int[10]; // 创建了一个保存10个int的int数组,其中每个元素都是int的默认值0
doublell sal = new double[20]; // 创建了一个保存20个double的double数组,其中每个元素都是double的默认值0.0
静态初始化:在创建数组时不直接指定数据元素个数,而直接将具体的数据内容进行指定语法格式: 数据类型 [] 数组名称 = {data1, data2, data3, ..., datan};
// score长度为5
int[] score ={10,40,70,90); // 此时数组的长度根据初始化数值的个数来定
。。。。
静态初始化也可以保留new 数据类型[不需要写长度]0
// score的长度就是3
intll score = new intll[10,30,50);
三、数组的使用
1. 数组中元素访问
数组在内存中是一块连续的空间,空间的编号从0开始依次递增,这个编号就称为数组的下标。最终访问数组元素就是根据数组名称[下标]来取得对应元素。数组的下标是针对于首元素的偏移量。
数组的下标取值范围[0...长度N)
// score数组的下标范围就是[0..4)
intll score = [10,30,50,70);
// 取得第一个元素的内容
score[0]
// 取得第三个元素的内容
score[2]
第一个元素相较于首元素的偏移量(直白点就是距离,单位距离就是1),因此下标就是0
第三个元素相较于第一个元素,有两个元素的偏移量,因此下标就是2
表示/存储数组元素时,为何要采用偏移量?
数组在内存中存储时,空间是连续的,为了方便起见
程序只需要记录数组首元素的地址即可,其他元素的地址通过偏移量可以立即求得
(帮助程序减少存储其他元素地址的开销)
数组下标的合法取值就是从[0...N)
取得某个数组的长度,使用数组名称.length属性来取得数组的长度若访问了不合法的数组下标,抛出数组下标越界异常,如下图:
2. 遍历数组
循环访问数组的每个元素
普通的for循环: 可以进行增删改查。
在遍历数组时还可以使用增强的for-each循环来遍历数组: 只能用于数组的元素读取,不能修改数组元素!
语法 : for(数据类型 临时变量名称 : 数组名称)for-each循环默认从数组的第一个元素开始访问,每访问一个元素就将
该元素的值复制给temp变量,直到整个数组访问完毕
若只是进行数组元素的读取,使用for-each循环
若还需要在数组遍历时修改数组元素的内容,只能使用普通for循环
四、数组是引用类型
初始 JVM 的内存分布内存是一段连续的存储空间,主要用来存储程序运行时数据的。比如1. 程序运行时代码需要加载到内存2. 程序运行产生的中间数据要存放在内存3. 程序中的常量也要保存4. 有些数据可能需要长时间存储,而有些数据当方法运行结束后就要被销毁如果对内存中存储的数据不加区分的随意存储,那对内存管理起来将会非常麻烦。
内存就是一个大仓库,程序的所有数据都需要放在这个仓库中,当数据很多时,就需要对仓库进行划分。 新数据区域 旧数据区域 垃圾数据区域
程序计数器 (PC Register): 只是一个很小的空间 , 保存下一条执行的指令的地址虚拟机栈 (JVM Stack): 与方法调用相关的一些信息, 每个方法在执行时,都会先创建一个栈帧 ,栈帧中包含有:局部变量表 、 操作数栈 、 动态链接 、 返回地址 以及其他的一些信息,保存的都是与方法执行时相关的一 些信息。比如:局部变量。当方法运行结束后,栈帧就被销毁了,即栈帧中保存的数据也被销毁了 。本地方法栈 (Native Method Stack): 本地方法栈与虚拟机栈的作用类似 . 只不过 保存的内容是 Native 方法的局 部变量 . 在有些版本的 JVM 实现中 ( 例如 HotSpot), 本地方法栈和虚拟机栈是一起的堆 (Heap) : JVM 所管理的最大内存区域 . 使用 new 创建的对象都是在堆上保存 ( 例如前面的 new int[]{1, 2, 3} ) , 堆是随着程序开始运行时而创建,随着程序的退出而销毁,堆中的数据只要还有在使用,就不会被销 毁 。方法区 (Method Area) : 用于 存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数 据 . 方法编译出的的字节码就是保存在这个区域、现在我们只简单关心堆和虚拟机栈这两块空间,后序 JVM 中还会更详细介绍。
栈区存放方法调用时产生的所有临时变量:形参,局部变量都随着方法调用时的栈帧入栈和出栈(方法调用结束之后,所有的临时变量都会被销毁)
只要是关键字new 产生的内容,都在堆中存储。只要程序不退出,堆中的数据还在被使用,这个数据就不会被销毁。比如:
intl data = new int[5);//数组对象,在堆中存储
1. 基本类型变量与引用类型变量的区别
基本数据类型创建的变量,称为基本变量,该变量空间中直接存放的是其所对应的值;而引用数据类型创建的变量,一般称为对象的引用,其空间中存储的是对象所在空间的地址
int a = 10; // a是整型变量,保存的就是数值10!
有new就在堆上开辟新的内存空间
不要把引用想复杂了,引用也是变量,也要保存值,只不过这个值是堆内存的地址而已!
2. 认识 null
当一个引用数据类型的值为null时,表示该引用不指向任何的内存空间,因此无法使用该引用获取任何数值
一旦使用值为null的引用访问任何数值时,都会抛出NPE异常(NullPointerException)
3. 作为函数的参数
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
//func方法中修改形参 x 的值, 不影响实参的 num 值。
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 将数组设定成引用类型 , 这样的话后续进行数组参数传参 , 其实只是将数组的地址传入到函数形参中. 这样可以避免对整个数组的拷贝 ( 数组可能比较长 , 那么拷贝开销就会很大 )
五、数组练习
1.实现数组转字符串
import java.util.Arrays
public static void main(String[] args) {
int[] data = {1,3,5,7,9};
// data是数组引用,保存的是地址,直接打印data没意义
System.out.println(data);//[I@14ae5a5
// 使用JDK提供的关于数组的工具类,将数组对象转换为字符串处理
String str = Arrays.toString(data);
System.out.println(str);
int[] array = {10,20,30,40,50,60};
System.out.println(arr2Str(array));
}
//2.自己定义
public static String arr2Str(int[] arr){
String ret = "[";
for (int i = 0;i < arr.length;i++) {
ret += arr[i];
if(i != arr.length-1){
// arr.length - 1是最后一个数组元素的下标
ret += ",";
}
}
ret +="]";
return ret;
}
2.实现数组完全拷贝
public static void main(String[] args) {
int[] arr = {1,3,5,7,9};
// 引用
int[] arr1 = arr;
// 复制(对原数组不产生影响)
// Arrays.copyOf(原数组名称,新数组长度);
// 1.新数组长度=原数组长度
int[] arr2 = Arrays.copyOf(arr,arr.length);
System.out.println(arr2Str(arr2));
arr2[0] = 10;//(对原数组不产生影响)
System.out.println(arr2[0]);
// 2.新数组长度<原数组长度
int[] arr3 = Arrays.copyOf(arr,3);
System.out.println(arr2Str(arr3));
// 3.新数组长度超出原数组长度(超出部分使用默认值来填充)
int[] arr4 = Arrays.copyOf(arr,8);
System.out.println(arr2Str(arr4));
int[] arr = {10,400,29,45,23};
int[] newArray = arrCopy(arr);
System.out.println(Arrays.toString(newArray));
}
// 自己实现数组完全拷贝
public static int[] arrCopy(int[] arr){
int[] newArray = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
// 遍历原先的arr数组,将每个元素的值复制给新数组的对应元素
newArray[i] = arr[i];
}
return newArray;
}
3.求整型数组元素的平均值
public static void main(String[] args) {
int[] arr = {1,2,3,4,5,6};
System.out.println(avg(arr));
}
public static double avg(int[] arr){
int sum = 0;
for (int i:arr) {
sum += i;
}
return sum / (arr.length * 1.0);
}
4.在数组中查找元素(顺序查找)
给定一个整型数组和待查找的元素,返回该元素在数组中的下标(若不存在,返回 -1)
public static void main(String[] args) {
int[] arr = {1,2,3,4,5,6};
System.out.println(toFind(arr,5));
}
public static int toFind(int[] array,int val){
for (int i = 0; i < array.length; i++) {
if (array[i] == val){
return i;
}
}
return -1;
}
5.二分查找(若待查找数组是一个无序数组,无法使用二分查找的。)
public static void main(String[] args) {
int n = 1_0000_0000;
int[] array = new int[n];
for (int i = 0; i < array.length; i++) {
array[i] = i + 1;
}
System.out.println(binSearch(array,100));
System.out.println(count);
System.out.println(binSearchRecursion(array,10000));
}
// 递归二分查找
public static int binSearchRecursion(int[] arr,int toFind){
return binSearchRecursionHelper(arr,0,arr.length - 1,toFind);
}
private static int binSearchRecursionHelper(int[] arr,int l,int r,int toFind){
if(l > r){
return -1;
}
// int mid = ( l + r) >> 1;
int mid = l +(r - l) / 2;//避免溢出
if(arr[mid] == toFind){
return mid;
} else if (toFind < arr[mid]) {
return binSearchRecursionHelper(arr,l,mid - 1,toFind);
}else {
return binSearchRecursionHelper(arr,mid + 1,r,toFind);
}
}
// 计算查找了几次
static int count = 0;
public static int binSearch(int[] arr,int val){
int l = 0;
int r = arr.length - 1;
while (l <= r){
count ++;
// int mid = (l + r) >> 1;
int mid = l +(r - l) / 2;//避免溢出
if(arr[mid] == val){
return mid;
}else if(val < arr[mid]){
r = mid - 1;
}else {
l = mid + 1;
}
}
return -1;
}
int mid = (l + r) >> 1;
假设此时I和r都是一个非常大的整数,相加有可能导致溢出
比如:l = 10e; r = 20e; l + r 就会超出整型数值的最大值(2147483647 ~20e)
6.冒泡排序
算法核心思想,不断的将前一个元素和后一个元素进行大小比较若前一个元素比后一个元素大,就两两交换彼此元素。不断重复此过程,直到整个数组有序。
public static void main(String[] args) {
int[] arr = {1,8,3,6,9,2,5};
bubbleSort(arr);
System.out.println(Arrays.toString(arr));
}
// 冒泡排序优化
// 1.当走了arr.length-1次时,集合已经有序,此时可以少走一个循环
// 2.当内层循环没有元素交换时,整个集合有序
private static void bubbleSort(int[] arr){
// 引入标记位,标记内层循环是否有元素交换
boolean isSwap = false;
// 外层循环就是无序区间的元素个数arr.length
// 外层循环表示需要进行比较的次数
for (int i = 0; i < arr.length - 1; i++) {
// 内层循环在真正进行相邻元素大小比较,将大元素换到后面,小元素在前面
// 内层循环只需要在无序区间进行两两比较,有序元素已经不需要比较了
// arr.length - i表示无序空间元素的个数
for (int j = 0; j < arr.length- i -1; j++) {
// 保证 (j + 1) < arr.length 合法
// j < arr.length - 1
if(arr[j] > arr[j + 1]){
isSwap = true;
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
if(!isSwap){
// 说明没有元素交换,集合有序
break;
}
}
}
/**
// 冒泡排序
private static void bubbleSort(int[] arr){
// 外层循环就是无序区间的元素个数arr.length
// 外层循环表示需要进行比较的次数
for (int i = 0; i < arr.length; i++) {
// 内层循环在真正进行相邻元素大小比较,将大元素换到后面,小元素在前面
// 内层循环只需要在无序区间进行两两比较,有序元素已经不需要比较了
// arr.length - i表示无序空间元素的个数
for (int j = 0; j < arr.length- i -1; j++) {
// 保证 (j + 1) < arr.length 合法
// j < arr.length - 1
if(arr[j] > arr[j + 1]){
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
**/
7.数组逆序处理
public static void main(String[] args) {
int[] arr1 ={1,3,5,7,9};
reverse(arr1);
System.out.println(Arrays.toString(arr1));
int[] arr2 ={10,8,6,4,2};
reverse(arr2);
System.out.println(Arrays.toString(arr2));
}
public static void reverse(int[] arr){
int i = 0;
int j = arr.length - 1;
while (i < j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
i ++;
j --;
}
}
六、二维数组
基本语法动态初始化
数据类型[][] 数组名称 = new 数据类型[行数][列数] ;静态初始化
数据类型[[ 数组名称 = new 据类型口 {
[第一行数据),
(第二行数据),....};
public static void main(String[] args) {
int[][] arr = new int[][] {
{1,2,3,4},
{5,6,7,8},
{9,10,11,12}
};
// 遍历二维数组
// 外层循环表示行数
for (int i = 0; i < arr.length; i++) {
// 内层循环表示第i行的列数
for (int j = 0; j < arr[i].length; j++) {
System.out.print(arr[i][j] + "\t");
}
System.out.println();
}
}