当我们需要存储多个数据时,我们按照之前所学的知识会去定义多个变量去接收这组数据,数据少的话还可以一个一个定义,要是有成百上千的数据我们岂不是要一一定义变量去接收它?这样的话就太麻烦了,所以今天我们引出数组来解决这个问题。
数组:可以看成是相同类型元素的一个集合。在内存中是一段连续的空间。(存放相同数据类型的元素的集合)
这是我们生活中常见的例子,车库。(其中每个序号的下角标都为序号数-1)
在java中,包含6个整形类型元素的数组,就相当于上图中连在一起的6个车位,从上图中可以看到:
1.3.1 数组的创建
T:表示数组中存放元素的类型
T[]:表示数组的类型
N:表示数组的长度
1.3.2 数组的初始化
数组的初始化主要分为动态初始化以及静态初始化。
静态初始化:在创建数组时不直接指定数据元素个数,而直接将具体的数据内容进行指定
但是我们更习惯于偏向另一种写法,他们的意义还是一样的,没有变,只是在写法上更简便了一点,如下:
虽然省去了new T[], 但是编译器编译代码时还是会还原。
无论是静态初始化还是动态初始化,在中括号当中不能有任何的数字(指定数组长度和定义下角标除外)。
计算数组的长度:
我们可以通过数组名.length来自动获取数组的长度。
注意:
1.静态初始化虽然没有指定数组的长度,编译器在编译时会根据{}中元素个数来确定数组的长度。
2.静态初始化时, {}中数据类型必须与[]前数据类型一致。
3.静态初始化可以简写,省去后面的new T[]。(但是编译器编译代码时还是会还原)
4.数组也可以按照如下C语言个数创建,不推荐。(容易造成数组的类型就是int的误解[]如果在类型之后,就表示数组类型,因此int[]结合在一块写意思更清晰)
5.如果不确定数组当中内容时,使用动态初始化,否则建议使用静态态初始化。
6.静态和动态初始化也可以分为两步,但是省略格式不可以。
①+②=③ ①和②加起来的效果和③一样 注意②中的new int[] 不可以省略 否则程序报错。在②中可以对数组进行多次赋值,而在③中定义数组的时候整体赋值只有这一次机会
7.如果没有对数组进行初始化,数组中元素有其默认值。
如果数组中存储元素类型为基本类型,默认值为基类类型对应的默认值。
如果数组中存储元素类型为引用类型,默认值为null。
1.4.1 数组中元素访问
数组在内存中是一段连续的空间,空间的编号都是从0开始的,依次递增,该编号称为数组的下标,数组可以通过下标访问其任意位置的元素。如:
也可以通过[]对数组中的元素进行修改:
我们就可以知道既可以通过下标访问数组元素又可以改变数组里的元素。
注意:
1.4.2 遍历数组
“遍历” 是指将数组中的所有元素都访问一遍, 访问是指对数组中的元素进行某种操作。
1.如下我们可以用循环把每个元素都打印出来:
在数组中可以通过 数组对象.length 来获取数组的长度
2. 我们还可以用for - each 来增强for循环(还可以打印集合中的元素)
其中:前面是变量名,可以随意命名,只需要注意返回类型,:后面是数组名
3. 借助Java本身提供的一些方法来实现数组的打印
Arrays --> 工具类 -->主要的作用是帮你操作数组
但是结果却是另一种形式的
我们可以看到在Array.toString里面方法内部帮我们进行了这样的组装(把参数的数组,转化为字符串进行输出)
内存是一段连续的存储空间,主要用来存储程序运行时数据的。
例如我们之前讲到的,当我们生成了一个Java文件,在编译的过程中输入命令 Javac 文件名.java 就会生成一个.class的字节码文件,那么这个文件就是在JVM中的Java本地方法栈上执行的。JVM也对所使用的内存按照功能的不同进行了划分
Java虚拟机栈(JVM Stack):与方法调用相关的一些信息,每个方法在执行时,都会先创建一个栈帧,栈帧中包含有:局部变量表、操作数栈、动态链接、返回地址以及其他的一些信息,保存的都是与方法执行时相关的一些信息。比如:局部变量。当方法运行结束后,栈帧就被销毁了,即栈帧中保存的数据也被销毁了。
Java本地方法栈(Native Method Stack): 本地方法栈与虚拟机栈的作用类似. 只不过保存的内容是Native方法的局部变量. 在有些版本的 JVM 实现中(例如HotSpot), 本地方法栈和虚拟机栈是一起的(运行由一些c/c++代码编写的程序)(JVM其实是一个软件,由c/c++代码编写的软件)
堆(Heap): JVM所管理的最大内存区域. 使用 new 创建的对象都是在堆上保存 (例如前面的 new int[]{1, 2,3} ),堆是随着程序开始运行时而创建,随着程序的退出而销毁,堆中的数据只要还有在使用,就不会被销毁。(只要是对象,都在堆上分配内存)
程序计数器 (PC Register): 只是一个很小的空间, 保存下一条执行的指令的地址
方法区(Method Area): 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据. 方法编译出的的字节码就是保存在这个区域
现在我们只简单关心堆 和 虚拟机栈这两块空间,后序JVM中还会更详细介绍
基本数据类型创建的变量,称为基本变量,该变量空间中直接存放的是其所对应的值;而引用数据类型创建的变量,一般称为对象的引用,其空间中存储的是对象所在空间的地址。
举例:
输出结果:
引用数据类型创建的变量不同的返回类型在①处会出现不同的字母,且真实的地址取不到,只能取到相对应的哈希值。
从上图可以看到,引用变量并不直接存储对象本身,可以简单理解成存储的是对象在堆中空间的起始地址。通过该地址,引用变量便可以去操作对象。
引用array2指向了array1引用的对象(不要说它是引用指向引用!),并且在array2里面改变元素值的时候,array1的值也会改变。
当两个数组存的元素是一样的时候,他们存的不是同一组元素,在堆里面是分配了不同的地址,地址就不一样。
null 在 Java 中表示 “空引用” , 也就是一个不指向对象的引用。
图中array这个引用 不指向任何的对象,再举一个例子:
运行结果:
当我们看到发生了空指针异常,我们就应该找前面看哪个引用是空的。
关于引用的总结:(引用是一个变量,里面存的是地址)
参数传基本数据类型
当我们进行基本类型数据传参之后发现实参并没有被改变,而实参只是拷贝了一份值给了形参,形参的改变并不会影响实参。
参数传数组类型(引用数据类型)
当我们对引用数据类型传参时又会发生什么呢?
我们可以看到第一组数据并没有被改变,第二组数据被改变了。我们这里的第一组数据和上面基本数据类型传参一样的,形参发生了改变并没有影响实参,而第二组数据在实参把对象地址传给形参的时候,形参和实参是同时指向一个对象的,所以此时形参的改变也会改变实参。
总结: 所谓的 “引用” 本质上只是存了一个地址. Java 将数组设定成引用类型, 这样的话后续进行数组参数传参, 其实只是将数组的地址传入到函数形参中. 这样可以避免对整个数组的拷贝(数组可能比较长, 那么拷贝开销就会很大).
输入一串数组,输出结果是字符串:
输出结果:
在这里面需要注意的是if语句作用是把4后面的逗号去掉。
数组拷贝的第二种方式:Arrays自带的拷贝(常用)
注意:在这个方法里面有许多类型的拷贝,不止int类型
Array.copyof(参数一,参数二),里面的参数一是代表了你要拷贝的数组,参数二是你要拷贝的新数组的长度。如果参数二拷贝的长度大于原来数组的长度,那么内容都会补0,可以看作是扩容了。
数组拷贝的第三种方式:System.arraycopy
System.arraycopy(参数一,参数二,参数三,参数四,参数五),其中参数分别代表:你要拷贝的数组,开始拷贝数组的下标,目的地数组,目的地数组的位置,你要拷贝的长度。
数组拷贝的第四种方式:array.clone();(不完全是拷贝,相当于模拟了一个副本)
从输出结果上看并不是指向的同一个对象。
求double类型的数组平均数:
在返回值的时候记得任意分子或者分母乘一个1.0,这样可以把结果转化为double类型的数,或者也可以强制类型转换。
给定一个数组, 再给定一个元素, 找出该元素在数组中的位置。
二分查找又称为折半查找,原理就是拿首尾两个元素,取中间的元素(mid)和你要查找的元素(key)进行对比,如果mid小于key的话,就需要把最左边的元素(left)挪到原来mid的值的右边一位,然后再和最右边的值(right)生成一个新的中间值mid2,再进行对比挪位,如果mid等于key的话就直接返回输出了,如果mid大于key的话,就需要把最右边的元素(right)挪到原来mid的值的左边一位,然后再和最左边的值(left)生成一个新的中间值mid2,再进行对比挪位,直到mid和key相等。但这些前提条件是left必须小于或等于right,并且数组是顺序排序,这些原理才能成立。代码展示:
那我们遇到了一个不是顺序的数组如何使用二分查找呢?
我们引出了这个库里面自带的方法Array.sort(array),这个方法可以帮我们对数组进行升序排序,注意是升序排序!!!代码展示:
那么在这里我们说明一下:
在Java里面,许多方法都已经帮我们做好了,用起来很方便,相较于c和c++,我么很多方法不需要自己去把底层原理弄懂然后自己实现,例如这一题二分查找我们库的里面就给出了方法
简单有效利用了库里面的方法进行了查找,当然,在面试或者做题的时候,注意题目条件可不可以直接调用库里面的方法, 还是需要自己去写出来,值得我们重视!!
此处我们拓展一下库里的方法:
1.判断两个数组内容是否是一样的
注意两个数组必须是一样的类型,且方法的返回类型是boolean,所以判断的结果只有true和false。
2.填充数组的元素
我们也可以部分填充:0下标到4下标,区间左闭右开,所以4下标元素取不到。
原理:①比较相邻的元素。如果第一个比第二个大,就交换他们两个。 ②对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。 ③针对所有的元素重复以上的步骤,除了最后一个。 ④持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
图中的循环就是形成原理,其中定义的flg变量作用是进行优化处理,如果当一组数组提前完成了排序,那么flg就会实现然后break跳出循环,我们就不需要进行多余的排序了,这就是最高效的冒泡排序了。
给定一个数组, 将里面的元素逆序排列
思路:设定两个下标, 分别指向第一个元素和最后一个元素. 交换两个位置的元素.然后让前一个下标自增, 后一个下标自减, 循环继续即可.
二维数组的打印方式:
1.for循环打印:
i代表行,j代表列,在这里面由于一维数组是一个特殊的二维数组,所以array.length代表了二维数组里面包含了几个一维数组(行数),而array[i].length代表了在数组里有多少个元素(列数)。
2.使用foreach进行打印
第一个foreach是二维数组里面装了一个一维数组,所以注意此处!第二个foreach是一维数组里装的元素。