硅基计划2.0 学习总结 叁
文章目录
- IDEA中的基础调试
- 1. 打断点
- 2. Deugger选项卡
- 3. Deugger选项卡其他功能
- 4. Console选项卡
- 5. 条件断点
- 数组
- 1. 格式
- 2. 访问
- 3. 数组遍历
- 数组引用类型
- 1. 初识JVM套件
- 2. 基本类型变量和引用类型变量
- 3. 数组作为方法参数传递
- 4.补充
- 数组相关练习
- 数组拷贝
- 1. 拷贝格式示范
- 2. 请你判断是否是真的拷贝
- 3. 真正的拷贝
- 4. 部分拷贝
- 5. 初始深拷贝和浅拷贝
- 一些排序方法
- 1. 二分查找
- 2.冒泡排序
- 3. 二分查找
- Arrays方法常用功能
- 额外练习拓展
- 1. 改变数组原来的值
- 2. 奇数位于偶数之前
- 3. 两数之和
- 4. 只出现一次的数字
- 5. 出现n/2次以上的数字
- 6. 存在三个连续奇数
IDEA中的基础调试
1. 打断点
断点是什么意思?顾名思义就是打断程序执行的一个点
程序进行到你所打断点的这个地方之前就会停下来等着你
也就是说你打断点的那一行还没有执行
如何打?只需要在行号上点一下,会出现一个实心红色圆点那它的作用是什么?通过断点缩小bug范围,提高程序掌控力
2. Deugger选项卡
我们可以看到有很多小的图标按键,那他们都是来干什么的呢?
我会依照图片中标号顺序来进行讲解功能![]()
- 这个类似于折线箭头的是逐过程调试,根据代码一行一行来,快捷键F8
- 这个向下箭头是逐语句调试,可以一行一行走,重点是可以进入方法内执行,快捷键F7
- 这个向上箭头是跳出调试,可以跳出方法,也就是说不必等到方法结束可提前跳出,快捷键Shift+F8
- 这个两红色圆点是显示所有断点信息调试,可以罗列出本工程文件的所有断点的详细信息(包括所在行)和对断点进行批量管理,快捷键Ctrl+Shift+F8
5. 这个对红色圆点画斜线是**取消所有断点**,顾名思义,可以一键开启或者关闭所有断点 6. 第6和第7功能放在一块讲:两个红色的箭头都是代表强制的功能,比如强制步入,它既可以步入自定义方法类,也可以步入库类 7. 比如“println”这个方法内,快捷键:**Alt+Shift+F7**,强制布过差不多与之类似效果,大家灵活使用![]()
![]()
- 这个斜向下箭头是运行到光标处,就是你光标点到什么位置,程序就到哪里截断
- 这个是重新调试按钮,点击即可再次启动调试
- 这个是终止调试按钮,点击即可停止调试
- 这个“|>”是*8跳到下一个断点**
- 这个是暂停程序意思,顾名思义
3. Deugger选项卡其他功能
分为两个区域,左边区域显示调用堆栈情况,右边是变量的实时值
Java这一点比较只能,它自动会添加所有正在使用的变量,不再需要手动去添加
上方还有个方框,这个相当于一个计算器,可以探讨变量间的关系
4. Console选项卡
即控制台界面,主要是程序信息和内容的结果,不用过多介绍了
5. 条件断点
即需要满足特定条件的断点,比如
我们可以看到条件很显然不满足,那么就不会执行这个断点 ,进程直接结束了![]()
![]()
数组
1. 格式
第一种:
int [] array = {1,2,3,4,5};
第二种:int [] array = new int []{1,2,3,4,5};
第三种:int [] array ;
array = new int []{1,2,3,4,5};
切勿直接写成array = {1,2,3,4,5};
第四种:int [] array = new int [5];
第一种、第二种、第三种都是静态初始化,即在创建数组的时候给定初始值
要保证数组类型和你new的类型要一直哦包括初始化的元素而第四种是动态初始化,并未给定初始值,在程序运行时才决定
你是否好奇在未给定初始值的时候,各个类型数组里放的都是什么呢?
结果显而易见布尔类型为false,引用类型为null空指针,float类型为0.0,字符类型为“,”
2. 访问
可以使用数组下标进行访问,如果发生越界,Java会直接报错,产生数组越界异常
![]()
3. 数组遍历
我们遍历数组经常要知道数组大小,在C语言中我们使用
int length = sizeof(arr)/sizeof(arr[0])
显得比较麻烦,在Java中我们直接使用方法内置的功能数组名.length
即可
我们讲在Java中数组遍历方法
第一个是强遍历,即for-each
for(int x:array){System.out.println(x); }
这段代码的意思就是把数组array中元素每次循环放入变量x中,再打印出来
这种遍历不能在循环中针对下标所指元素进行修改
第二个是官方给的数组转换成字符串格式输出
输出格式:“[数组元素1,数组元素2,…]”
代码:先输入Arrays
回车导包,再输入这个方法中的功能.toString(指定哪个数组)
我们通过图片可以看到返回的数一个String类型,因此我们给出对应的String值去接收 `String ret = Arrays.toString(指定哪个数组)`![]()
数组引用类型
1. 初识JVM套件
- 程序计数器:小空间,存放下一条执行指令的地址
- 虚拟机栈:存储局部变量、方法的内存开辟等
- 堆:存放new对象(所有对象),伴随程序创建/销毁,只要数据还在用就不回收
- 方法区:存储已被虚拟机加载的信息、常量、静态变量等
- 本地方法栈:保存Native方法的局部变量
2. 基本类型变量和引用类型变量
int a = 10; int b = 20; int array = {1,2,3,4,5};
对于变量a和b,里边存储的是实际的值
对于变量array,它里面存的是数组首元素的地址,而不是数组的值,这就叫引用类型变量
我们画个图来加深理解![]()
我们接下来以一个例子加深你对这种引用的理解
public static void func() { int[] array1 = new int[3]; array1[0] = 5; array1[1] = 10; array1[2] = 15; int[] array2 = new int[]{2,3,4,5,6}; array2[0] = 30; array2[1] = 40;array1 = array2;//重点是这一步 array1[2] = 80; array1[3] = 90; array2[4] = 100; for (int i = 0; i < array2.length; i++) { System.out.println(array2[i]); } }
这段代码。你认为数组array2会打印出什么结果?我们还是通过画图来讲解
这是在array1=array2;
代码之前的数组样子而在执行`array1=array2;`之后,注意看![]()
数组array1被赋予array2,而你array2指向的是array2对应的数组,那说明什么?
说明array1本来指向的是它自己的数组,现在被赋予执行array2所指向的数组,也就是说
现在array1不在指向自己原来的数组,而是指向array2所指向的数组
这就是:array1这个引用指向了array2引用所指向的对象
即array1和array2都可以对array2指向的数组进行编辑也就是说,你在array1中做的修改实质上是对array2的修改,下面是原理图,辅助你理解
![]()
结合我们刚刚讲到的调试,看看数组元素的变化
![]()
![]()
空指针问题:
int [] array1 = null; System.out.println(array1.length);
程序会产生异常,即空指针异常,这只是翻译问题,Java中不存在指针,且未说明与0地址有明确关系
3. 数组作为方法参数传递
这里先说明一点,实参传参后即使形参怎么修改,实参都不会变,这个跟C语言一致,就不过多赘述
你认为下面两个数组经过方法调用后,打印的结果是什么呢?public static void fuction1 (int [] array){array = new int []{100,200,300,400,500};}public static void fuction2 (int [] array){array [0] = 99;}public static void main(String[] args) {int [] array = {5,6,7,8,9};fuction1(array);fuction2(array);}
为什么结果是这样的,只有fuction2成功把数组元素改变,而fuction1没有呢?
[5, 6, 7, 8, 9]
[99, 6, 7, 8, 9]我们画个图来讲解
因此,结果就未曾改变了 对于fuction2,由于在方法调用过程中并未创建新的对象,形参指向的还是原来的对象 方法调用完成后,虽然形参销毁,可是你在方法调用的过程中就已经对数组完成了修改 而且array数组是实参数组,并不会随着方法销毁而销毁,因此打印结果就跟原来数字发生了变化![]()
4.补充
在Java中return返回值可以是数组
return int [] {1,2,3,4};
,这跟C语言不一样
比如方法返回值使用数组返回,那么就用(数据类型) []
接收返回值就好
- 二维数组
public static void main6(String[] args) {//我们讲二维数组int [][] array = new int [][]{{1,2,3},{4,5,6}};System.out.println(array.length);//打印的是行数System.out.println(array[0].length);//指的是第一行列数int [][] array2 = new int [][]{{1,2},{4,5,6,7},{9,10}};//对于不规则数组,我们可以分别求对应行的列数System.out.println(array2[0].length);System.out.println(array2[1].length);System.out.println(array2[2].length);int [][] array3 = {{1,2,3},{4,5,6},{9,10},{12,14,16,18}};//也可以这么创建数组}
在二位数组创建中,行不可以省略,列可以,这跟C语言不一样
如果你使用length工具求长度,求的是行数
想求列数如果是规则二维数组,直接求第一行列数即可array2[0].length
如果是不规则数组,就要对每一行单独求列数,上面代码也有
我是真没想到二维数组创建还可以多行这么写,666
数组相关练习
- 模拟实现toString,效果是“[1,2,3,4,5]”
- 有序数组的逆置
public static void toString (int [] array){//一定要这么写,如果到这些会导>致错误System.out.print("[");for (int i = 0; i < array.length; i++) {if(i==4){System.out.print(array[i]);}else{System.out.print(array[i] + ", ");}}System.out.println("]");}public static void changeString(int [] array){int i = 0;int j = array.length-1;while(i<j){int temp = array[i];array[i]=array[j];array[j]=temp;i++;j--;}for (int k = 0; k < array.length; k++) {System.out.print(array[k]+" ");}}public static void main5(String[] args) {//我们做两个练习,第一个是模拟实现toString,效果是“[1,2,3,4,5]”int [] array1 = {1,2,3,4,5};toString(array1);System.out.println();//第二个是有序数组的逆置changeString(array1);}
以下是运行结果:
![]()
数组拷贝
1. 拷贝格式示范
我们肯定要先定义一个新的数组用来接收拷贝的结果
你可以定义拷贝数组的长度int [] array = {1,2,3,4,5}; int [] copy = new int [array.length]; for (int i = 0; i < array.length; i++) {copy [i] = array [i];//再把拷贝数组的每一项赋予array数组的值 } for(int x:copy){//再用强遍历打印数组元素System.out.print(x+" "); }
2. 请你判断是否是真的拷贝
int [] copy2 = array;for(int x:copy2){System.out.print(x+" ");}
那我问你你copy2数组是否是新的数组?我的回答为不是新的数组
因为你copy2只是引用了array数组所指向的对象,本质上还是一个引用类型,并未产生新内容![]()
3. 真正的拷贝
在Java中Arrays有个功能,就是拷贝数组,下面给上代码
int [] copy3 = Arrays.copyOf(array,array.length);//对应拷贝的数组,拷贝长度for(int x:copy3){System.out.print(x+" ");}
它的底层原理是什么呢?我们按住ctrl键转到源码分析
public static int[] copyOf(int[] original, int newLength) {int[] copy = new int[newLength];System.arraycopy(original, 0, copy, 0,Math.min(original.length, newLength));return copy;}
我们发现这个copy数组指向的是copyof里面new的这个新对象
发现其调用的是一个名字叫System.arraycopy
的方法,我们再转到其源码分析public static native void arraycopy(Object src, int srcPos,Object dest, int destPos,int length);
我们发现这个源码的方法中存在五个参数,我们一一来看
第一个参数是你要拷贝的数组,第二个参数是srcPos
表示从你要拷贝的数组的0下标开始
第三个参数是拷贝到copy数组的,第四个是从拷贝数组copy的0下标开始放拷贝的元素
第五个参数是求你拷贝的数组和原数组长度的最小值,为什么呢?
这是为了确定范围,以方便后续在范围拷贝或者是扩容的时候提供便利因此请你思考个问题,如果拷贝过来的copy数组比原数组要小,能拷贝吗?答案是可以
int [] copy4 = Arrays.copyOf(array,array.length/2);//拷贝范围减小for(int x:copy4){System.out.print(x+" ");}
我们打印发现其只拷贝了部分元素
那你是否想过,既然拷贝数组方法里面调用了
arraycopy
方法,我们是不是可以单独拎出来int [] copy5 = new int [array.length];
System.arraycopy(array,0,copy5,0,array.length);System.out.print("拷贝后的数组: " + Arrays.toString(copy5));
嗯,也确实实现了我的功能,但由于其源码过于复杂,我们看不到

细心的你不知道有没有发现这个
arraycopy
方法前面有个native
,这个是什么呢?
这种方法的底层逻辑是C/C++编写的,相比于copyof
来说,效率略微高一点。但是差别不大
那你再有没有想过,如果你拷贝的数组大小是原数组大小的多倍呢?那是不是相当于扩容了

注意看,对于超出数组范围的元素,其会被初始化成其数组类型默认初始化的值
但如果你扩容后把原数组所指向的对象给改了,指向了拷贝后的数组对象,那我问你
原数组是不是也会扩容array = copy6;System.out.print("原数组变化为:");for(int x:array){System.out.print(x+" ");}

4. 部分拷贝
既然可以整个拷贝,那可以不可以部分拷贝呢?答案是可以
请注意在Java中下标的范围一般是左闭右开区间
所以拷贝的数组元素范围**[0,3),所以并不会拷贝下标是3的元素**int [] copy7 = Arrays.copyOfRange(array,0,3);for(int x:copy7){System.out.print(x+" ");}
我们转到其源码进行底层逻辑的基础分析
public static int[] copyOfRange(int[] original, int from, int to) {int newLength = to - from;if (newLength < 0)throw new IllegalArgumentException(from + " > " + to);int[] copy = new int[newLength];System.arraycopy(original, from, copy, 0,Math.min(original.length - from, newLength));return copy;}
发现其
newLengt
求的是新的范围, 末 位 置 − 初 始 位 置 末位置-初始位置 末位置−初始位置,假设长度大于0,就进行拷贝
其数学公式是 要 拷 贝 的 数 组 的 长 度 − 你 传 参 传 的 起 始 位 置 下 标 与 新 数 组 长 度 的 最 小 值 要拷贝的数组的长度-你传参传的起始位置下标与新数组长度的最小值 要拷贝的数组的长度−你传参传的起始位置下标与新数组长度的最小值
这个方法返回值就是拷贝后的数组copy
如果新数组长度小于0,返回的是一个错误信息,返回的是一个字符串
5. 初始深拷贝和浅拷贝
我们给出两个例子帮助你理解
我们先来看浅拷贝的例子对于图片中的问题,答案是会改变![]()
我们再来看深拷贝的例子
通过图中的讲解,我想你应该能明白![]()
一些排序方法
1. 二分查找
对于二分查找我们不过多赘述,其核心就是先把数组排好序
而在Arrays方法中,有个排序功能
注意返回类型是void,无需数组类型接收,其底层原理很复杂int [] array2 = {1,5,4,3,9,6};Arrays.sort(array2);for(int x:array2){System.out.print(x+" ");}
![]()
2.冒泡排序
虽然之前已经讲过了,但这里讲的更深入
我们通过举例子来简单理解下
例子:[5,3,2,1,6,7]
我们可以这么理解冒泡排序,我们拿到了这么多元素,我们进行第一次排序的时候
就以这个图中数据举例,5和3比,非有序,发生交换
交换后5和2比,非有序,再交换
交换后5和1比,非有序,再交换
交换后5和6比,有序了,不交换了
6和7比,有序了,不交换
此时第一次冒泡排序完成
此时5已经放到了正确位置,数组变化为
[3,2,1,5,6,7],此时我们再进行下一次冒泡排序,排序3
…一致循环往复,直到全部变成有序了,排序就完成了
我们的代码就要使用两层循环来嵌套,外层是每一次的冒泡排序
内层是这一次冒泡排序中具体实现和判断
而且第二次循环起始点一定是外层循环起始点下标后一个元素,因为你这么想,你上一次排完了一个元素,外层循环往后走了一个元素
那你内层循环是不是只需要比较外层循环后面的元素就好了
他们的终止条件都是循环到数组最右侧位置
但是你再次想,我外层循环已经排好序的元素还要再比吗,也就是说比如刚刚的例子[3,2,1,5,6,7]
难道我还需要比较数组元素1后面的元素吗?很显然不用嘛,所以我们内存循环终止条件就写成假设数组长度是length,外层循环是j,那就可以写成
length-1-i
这样就可以少比较已经有序了的元素那你想下可以可以再优化下,比如你排到最后一次,发现数组已经都有序了
难道你还要再判断,再排序吗?很显然不用啊,所以我们加上个限制条件
我们没走完一次冒泡排序后检查下此时数组是否已经有序了
如果上一次冒泡排序并未发生元素交换,那说明数组就有序了,此时不用再排了,直接return或者break就好public static void bubbleSortEnd (int [] array){for (int i = 0; i < array.length-1; i++) {boolean flag = false;for (int j = i+1; j <array.length-1-i ; j++) {if(array[i]>array[j]){int temp = array[i];array[i]= array[j];array[j] = temp;flag = true;}}if(!flag){return;}}}
可是这里是Java啊,难道Arrays数组内没有内置的功能?还记得之前的sort功能吗,就是那个
而且还可以局部排序Arrays.sort(array,2,4);
从下标2闭区间到下标4开区间[2,4)
3. 二分查找
这个之前已经讲过了,想详细了解的可以参考这篇博客二分查找
在Java中,其Arrays方法内置了二分查找功能
int ret = Arrays.binarySearch(array1,3);
参数分别对应目标数组,想找到元素,其返回的是查找结果的下标
如果没找到元素,其返回值针对不同数组是一个随机的负数呢?我们转到其源码分析
我们看到其内部又调用了一个方法binarySearch0(a, 0, a.length, key);
private static int binarySearch0(int[] a, int fromIndex, int toIndex,int key) {int low = fromIndex;int high = toIndex - 1;while (low <= high) {int mid = (low + high) >>> 1;int midVal = a[mid];if (midVal < key)low = mid + 1;else if (midVal > key)high = mid - 1;elsereturn mid; // key found}return -(low + 1); // key not found.}
我们看到其内部就是二分查找的逻辑,对于找不到,我们看到返回的是
-(low + 1);
返回的就是最后一次查找的起始位(最后一次查找的起始下标)再+1并填上符号
毕竟下标不可能是负的嘛,这也让我们明白这是一个错误值
Arrays方法常用功能
- 判断两个数组是否相等
其返回值是布尔类型```Java int [] array1 = {1,2,3,4,5}; int [] array2 = {1,2,3,4,5}; boolean ret = Arrays.equals(array1,array2); System.out.println(ret);//返回true ```![]()
- 填充数组的内容
假如初始化数组你未给值,这时候我们就可以使用Arrays方法中的fill功能填充int [] array3 = new int [10]; Arrays.fill(array3,20);
也可以部分填充,逻辑跟其他功能类似
int [] array4 = new int [5]; Arrays.fill(array4,1,3,20);
对于方法内参数分别是:目标数组,起始下标(闭区间),最终下标(开区间),填充什么数
额外练习拓展
1. 改变数组原来的值
比如乘上一个2,我们拷贝原数组
再将拷贝后的数组乘上2,这样原数组就不会改变大小
public static int [] change (int [] array1){int [] copy = new int [array1.length];for (int i = 0; i < array1.length; i++) {copy[i] = array1[i]*2;}return copy;//返回拷贝后的数组
}public static void main1(String[] args) {int [] array1 = {1,2,3,4};int [] ret = change(array1);System.out.println(Arrays.toString(array1));System.out.print(Arrays.toString(ret));
}
2. 奇数位于偶数之前
我们可以定义两个下标i和j,分别从数组的开头和末尾考试找
i遇到偶数停下,j遇到奇数停下,此时才发生交换,其他时候不发生交换且i和j下标进行调整
循环到i=j时候,此时就排好数组元素了
int [] array2 = {5,6,7,8,9};int [] array3 = {1,3,5,7,9};int i = 0;int j = array2.length-1;while(i<j){while(array2[i] %2 != 0){i++;}while(array2[j] %2 == 0){j--;}int temp = array2[i];array2[i]= array2[j];array2[j] = temp;}System.out.print(Arrays.toString(array2));
}
请你思考下这个代码,是不是会产生数组越界,因此我们加一个判断条件
while(i<j&&array2[i] %2 != 0);
while(i<j&&array2[j] %2 == 0);
我们加入个判断条件i<j,判断是否越界
你可能还有疑问,存在前后两个元素同时是奇数或者同时是偶数吗,不存在
因为你前面是奇数的时候,下标加了一个,或者后面是偶数的时候,下标减了一个
只有前为偶后为奇情况才会做交换
3. 两数之和
这里只强调一点,第二层循环从上一次循环的下一个元素开始,为什么?
因为假设数组元素[2,4,4,8],比如我想找的两数之和为8,那如果内外层循环起始位置相同
比如这个数组第二个元素,4+4=8,直接返回两个相同下标,这显然不对嘛
4. 只出现一次的数字
我们采用异或特性,举个例子:4^1^2^1^2 = 4^(1^1)^(2^2) = 4^0 = 4
,这就找出来了
5. 出现n/2次以上的数字
先排序,再取中间元素就好
6. 存在三个连续奇数
我们可以定义一个奇数的计数器,当遇到奇数,我们++一次,遇到偶数直接归零
直到计数器为3时跳出循环,如果循环后计数器还没有满足3,那就说明数组中不存在连续的三个奇数
int [] array4 = {1,2,4,5,7,9};int count = 0;for (int k = 0; k < array4.length; k++) {if(array4[k] %2 != 0){count++;}else{count = 0;}}if(count>=3){System.out.println(true);}else{System.out.println(false);}