上篇文章从数组来了解java中的引用和java中的传址操作中我们意图想让读者通过简单的数组使用来让读者明白什么是java中的引用和java中的传址操作。
那么本文就接着来着重来讲一讲java中有关的数组的更多知识
数组知识点讲解
刨去上篇文章说过的数组的创建和使用,我们今天来了解一些更多的有关数组的知识点
认识null
通过对上篇博文的学习我们已经知道了引用指向堆区里面的一片空间,回想起C语言,我们肯定会想起指针里面有NULL空指针,那么我们java里面有没有呢?答案是有的。
相应的,我们可以将null赋值给java里面的引用,这表示此引用,不指向任何一片空间。
随之而来的,如果我们对空引用进行操作的话,就有可能会报出的异常,如下图代码。
public static void main(String[] args) {
int[] arr = null;
for(int x:arr){
System.out.println(x);
}
}
对空引用进行操作,就会报出如下的空指针异常,特别的,在我们设计传参为引用类的时候,要对引用是否为空引用进行检查。
数组作为返回值
相信通过上篇博文读者一定知道了java里面的引用代表了啥?(即指向了堆里面的一片空间,我们可以通过引用直接对空间内容进行修改)那么了解了数组作为参数,我们就想一个问题: 数组可不可以作为一个方法返回值呢?
思考这个问题之前,我们还是和C语言对照一下。
对于C语言中,如果我们写如下代码
#include<stdio.h>
#include<stdlib.h>
#define sz 10
void print(int arr[])
{
for (int i=0;i<sz;i++)
{
printf("%d\n",arr[i]);
}
printf("************\n");
}
//一个简单的打印数组的函数
int* copy2(int arr[])
{
int *tmp = malloc(sz*sizeof(int));
for (int i = 0; i < sz; i++)
{
tmp[i] = arr[i];
}
return tmp;
}//使用malloc函数在堆区开辟空间来存储一个要被赋值的数组,然后返回数组名,即地址
int* copy1(int arr[])
{
int tmp[sz] = { 0 };
for (int i = 0; i < sz; i++)
{
tmp[i] = arr[i];
}
return tmp;
}
//不在堆区开辟空间,只在栈区定义一个要被赋值的数组,然后返回地址
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9 ,10 };
int *arr1 = copy1(arr);
int *arr2 = copy2(arr);
print(arr1);
print(arr2);
}
//输出结果:
//-858993460
//-858993460
//-858993460
//-858993460
//-858993460
//-858993460
//-858993460
//-858993460
//-858993460
//-858993460
//************
//1
//2
//3
//4
//5
//6
//7
//8
//9
//10
************
通过结果我们发现在堆区开辟空间存储数组的方法copy2最终成功的返回了我们被赋值的数组,而至于没有开辟空间的方法copy1失败了。
到这,我们至少成功的返回了一个数组,但是,C语言中这样的开辟空间没有释放的话,随着程序的运行会产生很多内存碎片,因为它不会自动释放在堆区开辟的空间。所以,C语言中我们想返回一个数组的尝试算是失败了。
但是在java中我们可以很简单的返回一个数组,具体看下面的代码
public static int[] Copy(int[] arry){
int[] tmp = new int[arry.length];
for (int i = 0; i < arry.length; i++) {
tmp[i] = arry[i];
}
return tmp;
}
public static void main(String[] args) {
int[] arry = {1,2,3,4,5,6,7,8};
System.out.println(Arrays.toString(Copy(arry)));
}
//输出结果:
//[1, 2, 3, 4, 5, 6, 7, 8]
我们发现java可以很简单的返回一个数组,而且不会有什么后遗症,是因为java的内存是由JVM(java虚拟机)自动回收的。
这里有一个点:即java的引用类是在堆区开辟空间的,当Copy方法结束后,我们创建的tmp数组如果没有什么再次被引用(其实可以理解为如果你创建的数组将不被使用了),那么它将被JVM自动回收。如上图代码,我们在方法结束以后仍旧要使用tmp数组,所以没有被销毁。
二维数组
对于二维数组我们同样和C语言进行一些对比。
对于C语言来说,请看如下代码
int arry[][3] = { { 9, 8, 7 }, { 6, 5, 4 }, { 3, 2, 1 } };
printf("%d\n",*(int*)arry);//输出9(这里我将指向第一个一维数组的地址强转为int类型,解引用之后就访问到了一维数组里面的第一个元素)
printf("%d\n", *(int*)(arry+1));//输出6
printf("%d\n", *(int*)(arry+2));//输出3
printf("%d\n",*(arry[0]+1));//输出8
printf("%d\n", *(arry[0] + 2));//输出7
printf("%d\n", *(arry[1] + 1));//输出5
printf("%d\n", *(arry[1] + 2));//输出4
从上述代码中我们可以看到在C语言中的上述二维数组,其实是由三个一维数组构成的,二维数组名+1或者+2其实代表了各个一维数组首地址之间的转换,而像arry[0]或者arry[1]这样的操作才让数组名从指向一个一维数组变成指向一个一维数组里面的数,所以上述代码的后四行代码我就不需要进行强转来显示。
大概明白了C语言里面的二维数组的特点,我们就可以来类比的了解java里面的二维数组了
java的二维数组和C语言里面的数组很像,但是java里面没有C的解引用这些东西,所以它里面就很明确
java里二维数组存的是每个一维数组的地址,每个一维数组又存储在堆里面。
我们可以通过下面的图解来清楚的了解
假如我们有一个4*4的二维数组,那么它在内存中就如同下面形式进行存储。
浅拷贝与深拷贝
在上文数组作为返回值的那节我们使用拷贝数组的例子,再结合二维数组的存储形式
我们就可以来进一步了解一下何为深拷贝,何为浅拷贝。
区分深拷贝和浅拷贝的关键点就是改变拷贝后的数组,是否影响之前的数组,如果影响,那么为浅拷贝,如果不影响,那么为深拷贝
如上文的拷贝方式,即下面的代码
public static int[] Copy(int[] arry){
int[] tmp = new int[arry.length];
for (int i = 0; i < arry.length; i++) {
tmp[i] = arry[i];
}
return tmp;
}
这样返回一个新的tmp数组,即使我们修改了tmp里面的值,也不会对原来的arry数组有影响。对于数组里面存的如果是基本数据类型来说,上述这样的方法就是深拷贝
但是想一想,如果上述的arry里面存的不是基本数据类型呢?如果是一个对象的地址呢?或者其他东西的地址呢?我们就通过tmp里面的值对对应的空间内容进行修改,这样的话上述的方法就是浅拷贝,因为我们可以通过拷贝之后的数组去修改之前的内容。
对于深拷贝和浅拷贝当下了解这么多就足够了,至于含有对象地址的深拷贝要等我们学会了接口以后才能解决。
一些数组练习
学到这,就让我们来做一些小练习吧!
数组转字符串
实现toString方法
public static String MyToString(int[] arrary){
String tmp;
tmp ="[";
for (int i = 0; i < arrary.length-1; i++) {
tmp+=arrary[i];
tmp+=",";
}
tmp+=arrary[arrary.length-1];
tmp+="]";
return tmp;
}
找数组中的最大元素
public static int FindMax(int[] arry){
if(arry==null){
System.out.println("The Array Is NULL");
return -1;
}
int max = arry[0];
for (int x:arry) {
if(x>max){
max=x;
}
}
return max;
}
二分查找数组元素
public static int BinarySearch(int[] arry,int find){
if(arry==null){
System.out.println("Arry Is Null");
return -1;
}
int left = 0 ;
int right = arry.length-1;
while(left<=right){
int mid = (left+right)/2;
if(find == arry[mid]){
return mid+1;
}else if(find > arry[mid]){
left = mid+1;
}else{
right = mid-1;
}
}
return -1;
}
冒泡排序
public static void main(String[] args) {
int[] arry ={ 1,3,2,4,5,6,7,34,23,45,56,90 };
System.out.println(Arrays.toString(arry));
bobblesort(arry);
System.out.println(Arrays.toString(arry));
}
public static void bobblesort(int[] array){
for (int i = 0; i < array.length-1; i++) {
boolean Flag = true ;
for (int j = 0; j < array.length-i-1; j++) {
if(array[j]>array[j+1]){
Flag =false;
int tmp = array[j+1];
array[j+1] = array[j];
array[j] = tmp;
}
}
if(Flag){
return;//若在一次循环中数组没有交换,那么即证明数组已经排好序了,就不用再冒泡了
}
}
}
数组的奇偶排列
交换一组数据的奇偶,让偶数在前,奇数在后
public static void Sort(int[] array){
int left = 0;
int right =array.length-1;
while(left<=right){
//从左边开始向右找奇数
while(array[left]%2==0){
left++;
}
//从右边开始向左找偶数
while(array[right]%2!=0){
right--;
}
//交换奇偶,让偶数再前面,奇数在后面
int tmp = array[left];
array[left] = array[right];
array[right] = tmp;
}
检查数组是否有序
public static boolean isSorted(int[] arry){
if(arry==null){
return false;
}else{
for (int i = 0; i < arry.length-1; i++) {
if(arry[i]>arry[i+1]){
return false;
}
}
return true;
}
}