8. JVM内存
(1)程序计数器:当前线程所执行的字节码文件的行号指示器(执行到哪行代码就添加一个行号)。
(2)本地方法栈:为了虚拟使用native方法服务。
- Java在实现底层源码的过程中有许多方法使用了native修饰,这个修饰的方法并不是为Java本身提供的实现,而是为了调用系统各种C++或C的类库提供的实现。
(3)栈区:在程序中定义变量就会在栈区开辟空间,每个对栈的操作都会形成一个栈帧,栈帧会随着程序操作而进行创建和销毁,创建就是在栈中开辟空间进行存储,销毁就是在栈空间中回收内存数据进行销毁并释放空间资源。栈区遵守先进后出原则,即最先创建的数据会在栈区的地步,最后创建的数据会在栈区的顶部,只有顶部的数据进行销毁后,才会进行后续数据的销毁,每次创建的数据都是在原有数据的上方即站顶部。栈区归程序所管,能够实现自动创建和回收空间,不需要程序员进行管理。
(4)堆区:被所有线程共享的一块内存区域,在虚拟机启动的时候自动创建。所有对象实例以及数组都在堆区分配空间和使用,堆区中的空间需要程序员手动创建,随意Java引入了一个关键字new。因为堆空间是程序员创建出来的,所以就需要进行对其进行管理(创建和销毁),Java为了方便程序员操作,提供了一个专门管理堆中内存的回收操作,这个操作就叫做GC(垃圾回收机制)。
- new关键字就相当于在堆中开辟空间。
- GC垃圾回收机制经历过很多迭代,从Java11开始Oracle确定了Java中GC回收机制启动全新G1垃圾回收机制。
(5)方法区:线程共享的内存区域,已被虚拟机加载的类的信息、常量、静态变量和方法都是在这个区域中国进行存储和操作。
9. 数组
数组属于引用数据类型,而引用数据类型是整个Java学习的关键。
(1)说明:数组是Java 中为了方便统一管理相同数据类型的数据集合,当面临大量相同数据类型需要存储和操作的时候,可以使用数组进行操作。
- 数组中数据是存储在堆中,而操作数据需要获取到数据在堆中的位置,这个获取对应位置的操作在Java中叫做“引用”,所以数组是引用数据类型。
9.1 创建数组
- 数组一共有4中创建方式
(1)标准创建方式:
数组中存储数据的数据类型[] 数组名字 = new 数组中存储数据的数据类型[数组长度];
- 在Java中数组是“定长“数组,一定要确定数组中存储元素的个数。存储的数据无论是超过这个存储范围还是没有达到这个存储范围,这个范围都是固定的,数组无法根据存储数据的多少进行动态的”扩容“和”减容“。
int[] arr = new int[10];
(2)第二种创建方式:
数组中存储数据的数据类型[] 数组名;
数组名 = new 数组中存储数据的数据类型[数组长度];
char[] buf; //此时这句话只是声明数组,并没有开辟堆空间,所以并不能使用。
buf = new char[5]; //只用进行初始化之后才可以进行赋值操作。
(3)第三种创建方式:
数组中存储数据的数据类型[] 数组名 = new 数组中存储数据的数据类型[] {存储的数据1,存储的数据2,存储的数据3,存储的数据4...};
- 不允许在new后面的
[]
中写数组长度。 - 这样创建数组会直接进行赋值而不是使用默认值,不会根据数组中存储数据的数据类型来使用默认值。
- 数组的长度根据
{}
中给定的数据个数决定。
boolean[] res = new boolean[] {true, false, true, true};
(4)第四种创建方式:
数组中存储数据的数据类型[] 数组名 = {存储的数据1,存储的数据2,存储的数据3,存储的数据4...};
double[] ds = {1.2, 1.01, 6.5, 7.4};
9.2 数组类型及初始值
类型 | 初始值 |
---|---|
byteshort、int | 0 |
long | 0L |
float | 0.0F |
double | 0.0D |
boolean | false |
char | '\uoooo'(表示空) |
引用数据类型 | null |
- 以上这些数据类型都可以作为数组中存储数据的数据类型,在创建数组时会根据数据的数据类型,进行赋初值(即创建的数组是有默认值的)。
9.3 数组操作
(1)对创建数组的过程进行说明:
int[] scores = new int[3];
- 创建的数组需要分为等号左边和等号右边两个不同的区域:
- 等号的左边:开辟在栈中。
- 等号的右边:开辟在堆中,并且是一个连续的存储空间。数组长度是多少空间就会被分为多少个小空间,根据存储数据的数据类型,每个小空间都会存储对应的默认值。
- Java中提出了一个概念“引用”,栈空间中存储的数据并不是数组中的具体数据,而是数组在堆中所开辟的空间“地址”,所以因为这个“地址”栈与堆之间就会形成一种联系,这种联系就是“引用”。
- 因为栈中存储的是数组在堆中的存储地址,而不是数组中具体的数据,所以不可以直接通过打印数组名来查看数组中的数据,打印数组名只能得到一个“伪地址”,所以Java为了方便我们对数组中的元素进行操作,提供了“下标”。
9.4 数组下标
(1)伪地址
- 通过对数组名记性打印,会得到一个伪地址,例如
[I@1b6d3586
。这个[I@1b6d3586
并不是地址而是一个根据Java内部的字符串拼接原则得到的一个拼接字符串。- [:代表数组
- I:代表数组中存储的元素类型
- @:没有任何特殊含义只是一个拼接符号
- 1b6d3586:这是一个十六进制的伪地址,Java是没有办法真正操作系统内存的。
- 伪地址之所以有效是因为它是根据Java中提供的hashcode方法换算而来的。
- hashcode方法是native方法,这个方法根据系统C++类库获取到真实的内存地址,转换成一个int类型数据给程序员。
- Java中允许程序员对hashcode这个方法进行改变,但是这个改变不会影响到真实地址,所以才称之为伪地址。又因为Java程序员只能通过hashcode来观察内存地址,所以就当伪地址为类型地址看待。
(2)下标语法:
- 获取数组中对应下标位置的数据。
数组名[下标值]
- 对数组中对应下标位置进行赋值操作,如果下标位置有值就进行修改操作。
数组名[下标值] = 值;
- 数组下标的范围:从0开始到数组长度-1结束。
- 需要访问数组中的元素时,可以使用数组提供的下标进行操作,但是在使用数组下标时,不允许出现下标值<0或者下标值>=数组长度的情况,否则就会出现
ArrayIndexOutOfBoundsException
(数组角标越界)异常信息。
9.5 遍历数组
(1)普通for循环
public class ArrayDemo3 {
public static void main(String[] args) {
int[] arr = new int[10];
for(int i = 0;i<arr.length;i++){
arr[i] = (int)(Math.random()*100);
}
System.out.println("通过随机赋值之后数组中值是:");
for(int i = 0 ; i<arr.length;i++){
System.out.println(arr[i]);
}
for(int i = 0;i<arr.length;i++){
if (i == 5){
arr[i] = 10000;
}
}
System.out.println("修改下标值为5的位置之后的值是:");
for(int i = 0 ; i<arr.length;i++){
System.out.println(arr[i]);
}
}
}
(2)增强for循环(foreach循环)
- 语法:
for (数组中存储数据的数据类型 变量名 : 数组名字) {
这里操作的就是()中变量名;
}
- 增强for循环特殊点在于for循环的()内声明了一个局部变量,通过每次循环数组获取数组中每一个元素的值,将这个元素赋值给局部便令,在循环体内就可以操作这个变量来进行对数组的处理操作。
- 因为是将数组中的数据获取出来之后赋值给了局部变量,所以你在操作变量的时候等于是在操作局部变量,而不是数组本身的数据。增强for循环多用于变量的计算和打印操作,不能对数组进行赋值操作。
public class ArrayDemo4 {
public static void main(String[] args) {
int[] arr = {1,2,3,4,5,6};
int sum = 0 ;
for(int a : arr){
sum += a;
}
System.out.println(sum);
for(int a : arr){
System.out.println("数组中存储的元素值是:"+a);
}
for(int a : arr){
a = 0;
}
System.out.println("将数组中每个元素的值都赋值为0之后:");
for(int a : arr){
System.out.println("数组中存储的元素值是:"+a);
}
}
}
(3)总结:
- 当前两种循环方式都可以对数组进行遍历操作。
- 普通for循环属于万能循环,它可以利用下标来完成对数组中本身存储的数据进行操作(推荐使用它来完成对数组的增删改查操作)。
- 增强for循环属于普通for循环的简化版本,它利用的是循环内部定义局部变量来获取数组的值,并对局部变量进行操作,并不能直接影响到数组中原有的数据。
9.6 数组的排序
数组进行排序的目的是让数组中的数据有序,即升序和降序。在没有明确要求的前提下,所有偏序皆为升序排序。
(1)数组中的排序方式:冒泡、选择、插入、快速、归并和堆排序。
(2)冒泡排序:
- 冒泡排序经过一轮后会将一个数放到正确的位置,利用这个原则将冒泡排序核心重复多变就可以将数组中的元素按照要求进行排序。
- 原理:将相邻的数组中的元素进行比较,如果前一个元素大于后一个元素则交换两个元素的位置,冒泡排序一轮就是重复执行这个过程。
public class ArrayDemo5 {
public static void main(String[] args) {
int[] arr= new int[10];
System.out.print("向数组中存储10个随机数据:");
System.out.print("[");
for(int i= 0;i<arr.length;i++){
arr[i] = (int)(Math.random()*100);
}
for(int i = 0;i<arr.length;i++){
if(i != arr.length-1){
System.out.print(arr[i]+",");
}else{
System.out.println(arr[i]+"]");
}
}
System.out.println("-------------------------------------华丽的分割线---------------------------------------");
for(int i = 0 ; i<arr.length;i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
System.out.print("第"+(i+1)+"次排序之后的结果是:");
System.out.print("[");
for(int m = 0;m<arr.length;m++){
if(m != arr.length-1){
System.out.print(arr[m]+",");
}else{
System.out.println(arr[m]+"]");
}
}
}
System.out.println("-------------------------------------华丽的分割线---------------------------------------");
System.out.print("排序之后数组中存储10个元素的值是:");
System.out.print("[");
for(int i = 0;i<arr.length;i++){
if(i != arr.length-1){
System.out.print(arr[i]+",");
}else{
System.out.println(arr[i]+"]");
}
}
}
}
-------------------------------冒泡排序核心代码------------------------------------
for(int i = 0 ; i<arr.length;i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
(3)选择排序:
- 选择排序经过一轮后会将一个数放到正确的位置,利用这个原则将冒泡排序核心重复多变就可以将数组中的元素按照要求进行排序。
- 原理:选择数组中某个位置下标固定,依次与下标后续的每个元素进行比较,如果固定位置的值大于后续比较的数据值,交换两个元素的位置,知道所有元素都比较完成。
public class ArrayDemo6 {
public static void main(String[] args) {
int[] arr= new int[10];
System.out.print("向数组中存储10个随机数据:");
System.out.print("[");
for(int i= 0;i<arr.length;i++){
arr[i] = (int)(Math.random()*100);
}
for(int i = 0;i<arr.length;i++){
if(i != arr.length-1){
System.out.print(arr[i]+",");
}else{
System.out.println(arr[i]+"]");
}
}
System.out.println("-------------------------------------华丽的分割线---------------------------------------");
for(int i= 0; i<arr.length;i++){
for(int j = i+1; j<arr.length;j++){
if(arr[i] > arr[j]){
int tmp= arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
}
System.out.println("-------------------------------------华丽的分割线---------------------------------------");
System.out.print("排序之后数组中存储10个元素的值是:");
System.out.print("[");
for(int i = 0;i<arr.length;i++){
if(i != arr.length-1){
System.out.print(arr[i]+",");
}else{
System.out.println(arr[i]+"]");
}
}
}
}
-------------------------------选择排序核心代码------------------------------------
for(int i= 0; i<arr.length;i++){
for(int j = i+1; j<arr.length;j++){
if(arr[i] > arr[j]){
int tmp= arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
}
(4)总结:上述就是提供两种对数组中元素进行排序的操作方式,主要是掌握这些排序操作的书写,所以需要进行记忆(面试可能会考)。如果需要升序排序,在比较的位置就使用>
号;如果需要降序排序,在比较的位置就使用<
号。在开发中一般都是使用系统提供好的排序方法,除非公司有特殊要求。
9.7 查找数组中的元素
在某些情况下,需要获取到数组中某个特定位置元素的值或者需要删除某个特定位置的值,这种情况下就需要对数据中元素进行查找操作。
(1)线性(顺序)查找
- 线性查找是数组中使用最为广泛的一种查找方法。
- 原理:从数组的第一个元素开始,逐一向后比较查询结果,找到后就可以返回其下标对应的值;没有找到返回-1。
- 优点:简单、易用、便于书写。
- 缺点:执行效率不可控(最优是1次,最差是元素在数组的最后一位)。
import java.util.Scanner;
public class ArrayDemo7 {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int[] arr = new int[10];
input(arr);
System.out.print("数组中数据是:[");
for(int i = 0;i<arr.length;i++){
if(i != arr.length-1){
System.out.print(arr[i]+",");
}else{
System.out.println(arr[i]+"]");
}
}
System.out.println("请输入您要查找的数据:");
int num = input.nextInt();
int index = findNumber(arr, num);
if(index < 0){
System.out.println("没有这个值在数组中");
}else{
System.out.println("数组中有这个值,位置是:"+index);
}
}
/**
* 随机向数组中添加数据
* @param arr int类型数组
*/
public static void input(int[] arr){
for(int i =0 ;i<arr.length;i++){
arr[i] = (int)(Math.random()*100);
}
}
/**
* 查找数组中是否存在指定的数据
* @param arr 要查找的int类型数组
* @param num 要查找的数据
* @return 找到了返回对应位置的下标 找不到就返回-1
*/
public static int findNumber(int[] arr,int num){
for(int i = 0 ; i<arr.length;i++){
if (arr[i] == num){
return i;
}
}
return -1;
}
}
(2)二分法(折半)查找
- 要求:二分查找的必要前提条件就是需要对要进行查找的数组进行排序。
- 优点:查找效率高。
- 缺点:实现复杂,而且需要对要查找的数据进行排序,否则就没法精确查找。
- 二分查找理论:
import java.util.Scanner;
public class ArrayDemo8 {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int[] arr = new int[10];
input(arr);
/
sortArray(arr);
System.out.print("数组中数据是:[");
for(int i = 0;i<arr.length;i++){
if(i != arr.length-1){
System.out.print(arr[i]+",");
}else{
System.out.println(arr[i]+"]");
}
}
System.out.println("请输入您要查找的数据:");
int num = input.nextInt();
int index = binarySearch(arr, num);
if(index < 0){
System.out.println("没有这个值在数组中");
}else{
System.out.println("数组中有这个值,位置是:"+index);
}
}
/**
* 随机向数组中添加数据
* @param arr int类型数组
*/
public static void input(int[] arr){
for(int i =0 ;i<arr.length;i++){
arr[i] = (int)(Math.random()*100);
}
}
/**
* 对数组进行排序
* @param arr 存储元素数组
*/
public static void sortArray(int[] arr){
for(int i = 0 ;i<arr.length;i++){
for(int j = i+1;j<arr.length;j++){
if(arr[i] > arr[j]){
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
}
}
/**
* 二分查找数组中是否存在指定的数据
* @param arr 要查找的int类型数组
* @param num 要查找的数据
* @return 找到了返回对应位置的下标 找不到就返回-1
*/
public static int binarySearch(int[] arr,int num){
//1.定义开始位置和结束位置
int beginIndex = 0;
int endIndex = arr.length-1;
//2.计算中间值middleIndex
int middleIndex = (beginIndex+endIndex)/2;
//3.循环遍历整个数组查询位置
while(true){
//三种情况一一列举
if(num > arr[middleIndex]){ //查询值>中间获取值
//修改开始位置
beginIndex = middleIndex+1;
}else if( num < arr[middleIndex]){//查询值 < 中间获取值
//修改结束位置
endIndex = middleIndex-1;
}else{ //查询值 == 中间获取值(找到了)
return middleIndex;//返回下标值
}
//重新计算中间位置值
middleIndex = (beginIndex+endIndex)/2;
//如果出现 开始值>结束值的时候 证明没有找到数据
if(beginIndex > endIndex){
return -1;
}
}
}
}