Java学习笔记
Java学习笔记是一个持续更新的系列,工作多年,抽个空对自身知识做一个梳理和总结归纳,温故而知新,同时也希望能帮助到更多正在学习Java 的同学们。
本系列目录
入门篇
基础篇
数组
数组是储存数据类型相同的变量的集合,属于引用数据类型,自身也是堆中的一个对象。
声明方式
在Java中声明数组有两种方式,一是先声明数组长度,后赋值(动态初始化),二是声明数组时直接赋值,无需声明长度(静态初始化)。
实际操作时有三种方式:
-
type[] 变量名 =
new
type[数组中元素的个数];
比如:int[] a = new int[10];
数组名,也即引用a,指向数组元素的首地址。
推荐此种方式,更能表明数组类型 -
type 变量名[] =
new
type[数组中元素的个数];
如:int a[] = new int[10];
这种方式和C语言一样
-
定义时直接初始化
type[] 变量名 =new
type[]{逗号分隔的初始化值};
其中红色部分可省略,所以又有两种:int[] a = {1,2,3,4}; int[] a = new int[]{1,2,3,4};
其中
int[] a = new int[]{1,2,3,4};
的第二个方括号中不能加上数组长度,因为元素个数是由后面花括号的内容决定的。
内存分配
Java 语言是典型的静态语言, Java 数组也是静态的。
当数组被初始化之后,该数组所占的内存空间、数组长度都是不可变的。
由于数组属于引用对象,它从声明之后,就会保存在堆内存中,栈中只会保存指向堆中数组的地址。
例如
int a[] = new int[4];
a[0]=1;
a[1]=3;
Java 数组的变量对象并不是数组本身,它只是指向堆内存中的地址,它保存在栈中,上图左边为栈右边为堆。
基础运用
数组长度
Java中的每个数组都有一个名为length的属性,表示数组的长度。
length属性是public final int的,即length是只读的。
数组长度一旦确定,就不能改变大小。
int a[] = new int[10];
int n=a.length;//获取数组长度
数组索引
索引数组存储一系列经过组织的值,其中的每个值都可以通过使用一个无符号整数值进行访问。
数组索引始终从0开始,且添加到数组中的每个后续元素的索引以 1 为增量递增
索引的用法
数组通过[index]
来获取指定位置的值,其中index
为索引值
int a[] = {1,2,3,4};
int index_1=a[0];//根据索引获取数组的数据
int index_2=a[1];//根据索引获取数组的数据
int index_3=a[2];//根据索引获取数组的数据
int index_4=a[3];//根据索引获取数组的数据
按照索引和数值的关系,如下表格
0 | 1 | 2 | 3 |
---|---|---|---|
1 | 2 | 3 | 4 |
数组也可以配合循环语句进行业务处理
int a[] = {1,2,3,4};
for(int i=0;i<a.length;i++){
System.out.println(a[i]);//动态打印数组值
}
//也可以使用另一种循环方式
for(int temp:a){
System.out.println(temp);//动态打印数组值
}
//还可以使用while和do-while,后续章节描述
结合索引与数值关系可以看出来,数组属于行数据,那么有行就有列,就可以构成表格数据格式,也就是二维数组。
二维数组
二维数组的每个元素都是一个一维数组,这些数组不一定都是等长的。
二维数组是数组的数组,一维数组可以看成行数据,那么二维数组就是表格形式,即每一行数据,都有多列值。
示例
int[][] i = new int[4][];
i[0]={1,2,3};
i[1]={1,3,4};
i[2]={1,4};
i[3]={1,5};
0 | 1 | 2 | 3 |
---|---|---|---|
1 | 1 | 1 | 1 |
2 | 3 | 4 | 5 |
3 | 4 |
二维数组定义方式和一维数组一样,也是两种方式
动态方式
二维数组可以在声明的时候就指定数组大小,然后通过索引进行动态数据填充。
如:
type[][] i = new type[2][];//推荐
type i[][] = new type[2][3];
声明二维数组的时候可以只指定第一维大小,空缺出第二维大小,之后再指定不同长度的数组。但是注意,第一维大小不能空缺(不能只指定列数不指定行数)。
静态方式
二维数组也可以在定义的时候初始化,使用花括号的嵌套完成,这时候不指定两个维数的大小,并且根据初始化值的个数不同,可以生成不同长度的数组元素。
如:
int i[][]={{1,2},{2,3},{3,4,5}};
二维数据的循环
int [] arr[] ={{1,2},{2,3,4},{3,4,5,6}};
for(int i=0;i<arr.length;i++){ //0,1,2
for(int j=0;j<arr[i].length;j++){ //每一个一维数组的长度
System.out.print(arr[i][j]+"\t");
}
System.out.println();
}
数组拷贝
对数组的引用地址或者值进行复制。
数组属于引用对象,复制数组的引用地址,使两个数组指向同一个内存地址,因此修改一个数组时也会影响另一个。
拷贝数组的指针(引用地址)
public static void main(String[] args) {
int[] array1={11,22,33,44};
int[] array2=new int[5];
array2=array1;
array1[0]=1;
System.out.println(array2[0]);//两个数组指向同一内存,所以修改array1,即修改array2
}
拷贝数组的值
public static void main(String[] args) {
int [] array1={11,22,33,44};//源数组
int [] array2=new int[4];//目标数组,长度必须大于等于源数组,负责会报错
for (int i = 0; i <array1.length ; i++) {
array2[i]=array1[i];
}
//打印结果
for(int temp:array2){
System.out.println(temp);//动态打印数组值
}
}
arraycopy方法
Java提供了System.arraycopy()
方法,用于将一个数组的元素快速拷贝到另一个数组。
public static void main(String[] args) {
int [] array1={11,22,33,44};//源数组
int [] array2=new int[4];//目标数组,长度必须大于等于源数组,负责会报错
//拷贝
System.arraycopy(array1, 0, array2, 0, array1.length);
//打印结果
for(int temp:array2){
System.out.println(temp);//动态打印数组值
}
}
arraycopy方法的参数详解
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,int length);
-
src
表示源数组
-
srcPos
表示源数组中拷贝元素的起始位置。
-
dest
表示目标数组,目标数组的长度至少大于等于原数组。
-
destPos
表示拷贝到目标数组的起始位置
-
length
表示拷贝元素的个数
数组反转
对数组的数值进行反转
int [] array={11,22,33,44,55};//源数组
for(int i=0;i<array.length/2;i++){
int n=array[i];//获取第i个元素
array[i]=array[array.length-i-1];//赋予对应的尾部元素
array[array.length-i-1]=n;//将尾部元素值替换为i位置的元素
}
for (int n:array) {
System.out.println(n);//打印结果
}
数组的查询
线性查找
查询目标值需要逐个遍历元素进行比较。
int [] array={11,22,33,44,55};//源数组
int n=33;//查找是否存在33,
for(int i=0;i<array.length;i++){
if(array[i]==n){
System.out.println(n);//打印结果
break;
}
}
二分法查找
二分法要求数组必须有序,在有序的情况下,拿到中间值与目标值比较,然后根据结果大小再取剩下的半截数组重复以上步骤,直到找到目标为止。
int [] array={11,22,33,44,55,66,77,88};//源数组
int n=33;//查找是否存在33,
int begin=0;
int end=array.length-1;
while(begin<=end){
int middle = (begin+end)/2;//取中间的
if(n==array[middle]){//找到目标
System.out.println("目标下标为"+middle);
break;
}else if(array[middle]>n){//大于目标,则取左侧半截数组
end = middle-1;
}else{
begin=middle+1;////小于目标,则取右侧半截数组
}
}
数组的排序算法
将数组中的元素,按照逻辑顺序重新排列的方式称为排序,数组进行排序通常是为了快速查找目标。
排序是计算机内经常进行的一种操作,其目的是将一组“无序”的记录序列调整为“有序”的记录序列。
十大排序算法
排序算法不限制计算机语言
计算方式 | 算法名称 |
---|---|
选择排序 | 直接选择排序 |
选择排序 | 堆排序 |
交换排序 | 冒泡排序 |
交换排序 | 快速排序 |
插入排序 | 直接插入排序 |
插入排序 | 拆半插入排序 |
插入排序 | Shell排序 |
归并排序 | 归并排序 |
桶式排序 | 桶式排序 |
基数排序 | 基数排序 |
除了以上十种,还有其他排序算法,一般根据三个维度来衡量其优劣
- 时间复杂度
分析关键字的比较次数和记录移动次数 - 空间复杂度
分析排序算法中需要多少辅助内存 - 稳定性
若记录A与记录B的关键字值相等,排序后A与B的先后次序保持不变,则称这种排序算法是稳定的
按照计算机情况可分为两类,即内部排序和外部排序。
内部排序:整个排序过程不需要借助于外部存储器,所有操作都在内存完成。
外部排序:参与排序的数据非常多,计算量很大,需要借助外设来参与存储。
冒泡排序
冒泡排序属于交换排序,交换排序主要是通过表中两个记录的比较,不符号升序或者降序要求,则将两者交换,而冒泡排序是通过对待排序序列从前向后,依次比较相邻元素的值大小,发现逆序则交互,是排序值较大的元素逐渐从前往后移动。
int [] array={7,11,55,4,78,69,99,88};//源数组
for (int i = 0; i <array.length ; i++) {
for (int j = 0; j < array.length- i - 1; j++) {
if(array[j]>array[j+1]){//比较相邻两个值得大小,符合要求则互换位置
int temp=array[j];
array[j]=array[j+1];
array[j+1]=temp;
}
}
}
for (int n:array ) {
System.out.println(n);
}
//输出结果
4
7
11
55
69
78
88
99
快速排序
属于比较算法中目前计算速度最快的算法,它是从数列中挑选一个”基准“元素,然后逐个对比,数组中其他元素比基准元素小的放在基准元素前面,比它大的放在后面,这个操作也可以称为分区操作,然后重复整个操作直到数组变得有序。
快速排序基于分治思想,它的时间平均复杂度很容易计算得到为O(NlogN)
public static void main(String[] args) throws InterruptedException {
int [] array={7,11,55,4,78,69,99,88};//源数组
int len;
if(array == null
|| (len = array.length) == 0
|| len == 1) {
return ;
}
sort(array, 0, len - 1);//快速排序核心算法
for (int n:array) {
System.out.println(n);
}
}
/**
* 快排核心算法,递归实现
* @param array
* @param left
* @param right
*/
public static void sort(int[] array, int left, int right) {
if(left > right) {
return;
}
// base中存放基准数
int base = array[left];
int i = left, j = right;
while(i != j) {
// 顺序很重要,先从右边开始往左找,直到找到比base值小的数
while(array[j] >= base && i < j) {
j--;
}
// 再从左往右边找,直到找到比base值大的数
while(array[i] <= base && i < j) {
i++;
}
// 上面的循环结束表示找到了位置或者(i>=j)了,交换两个数在数组中的位置
if(i < j) {
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
}
// 将基准数放到中间的位置(基准数归位)
array[left] = array[i];
array[i] = base;
// 递归,继续向基准的左右两边执行和上面同样的操作
// i的索引处为上面已确定好的基准值的位置,无需再处理
sort(array, left, i - 1);
sort(array, i + 1, right);
}
其他数组问题
实现两个数组相交,返回结果符合样例(腾讯机试题)
import java.util.*;
public class Test {
// 样例1:
// 输入:
// A = [1, 3, 8, 4, 6]
// B = [3, 4, 9, 4]
// 输出:[3, 4]
//
// 样例2:
// 输入:
// A = [1, 3, 8, 4, 6, 4]
// B = [3, 4, 9]
// 输出:[3, 4]
//
// 样例3:
// 输入:
// A = [1, 3, 8, 4, 6, 4]
// B = [3, 4, 9, 4]
// 输出:[3, 4, 4]
public static void main(String[] args) {
int[] A = new int[]{1, 3, 8, 4, 6, 4, 9, 9};
int[] B = new int[]{3, 4, 9, 4, 9};
for (int n:toArr3(A, B)) {
System.out.println(n);
}
}
/***
* 二分法
* @param a
* @param b
* @return
*/
public static int[] toArr3(int[] a, int[] b) {
Arrays.sort(a);
Arrays.sort(b);
List<Integer> temp=new ArrayList<>();
for(int i=0,j=0;i<a.length && j<b.length;) {
if(a[i] == b[j]) {
temp.add(a[i]);
i++;
j++;
} else if(a[i] > b[j]) {
j++;
} else {
i++;
}
}
return temp.stream().mapToInt(Integer::valueOf).toArray();
}
/**
* hashmap
* @param a
* @param b
* @return
*/
public static int[] toArr2(int[] a, int[] b) {
Map<Integer,Integer> map=new HashMap<>();
for (int i = 0; i < a.length ; i++) {
Integer n=map.get(a[i]);
map.put(a[i],n==null?1:++n);
}
List<Integer> temp=new ArrayList();
for (int i = 0; i < b.length ; i++) {
if(map.containsKey(b[i])){
int j=map.get(b[i]);
if(j>0){
temp.add(b[i]);
map.put(b[i],--j);
}
}
}
return temp.stream().mapToInt(Integer::valueOf).toArray();
}
/**
* 标记数组
* @param a
* @param b
* @return
*/
public static int[] toArr(int[] a, int[] b) {
List<Integer> temp=new ArrayList();
boolean[] bl=new boolean[b.length];
for(int i = 0; i< a.length; i++){
int index= a[i];
for (int j = 0; j < b.length; j++) {
if(index== b[j]&&!bl[j]){
temp.add(index);
bl[j]=true;
break;
}
}
}
return temp.stream().mapToInt(Integer::valueOf).toArray();
}
}