Java与C构造数组的语法有点不同,以下是从C到Java的个人理解。
C/C++与Java数组区别
对比学习:
以下仅考虑在堆栈的情况。
在C中,int arr[3];
数组在栈上。Java中int[] arr = new int[3];
在JVM虚拟内存堆上。
C中创建数组的语法,arr会在栈上空间分配一个地址,数组的地址不能被修改。
错误示范:int arr[3]; arr = NULL;
C数组在传参时,传递数组名会被解读成地址。
#include<cstdlib>
#include<cstring>
void test01()
{
//Java数组在堆区创建,并会进行默认初始化.
//以int[] 类型为例
//对比C,Java数组类似C中的动态数组。
int *arr = (int *)malloc(sizeof(int) * 10);
memset(arr, 0, sizeof(int) * 10);
free(arr);//需要手动释放
//对比C++。
int *arr2 = new int[10]();
delete[] arr2;//手动释放。
}
数组
数组:相同类型元素的一个集合。内存中的一段连续的空间。Java数组在堆上,而不会在栈上。
int[] arr=int [3];
在堆区上申请三个int类型连续空间。arr存储的是数组在堆区的地址。
arr把它理解为C指针(int *)
,arr存储数组首元素的低地址。
double[] array1=double [5];
double类型的数组
String[] array2=String [3];
字符串数组。
提炼语法。
T[] name = T [N];
- T为数据类型,基本数据类型,引用数据类型。
- name为数组名,将其理解为指向堆区空间数组首元素的指针。
- N为数组元素个数。N可为常量,也可为变量。
Java支持类似C中的变长数组,变量可以作为参数。
import java.util.Arrays;
import java.util.Scanner;
public class Example {
public static void main(String[] args) {
int n = 10;
Scanner scanner = new Scanner(System.in);
n = scanner.nextInt();//读取一个整数
int arr[] = new int[n];//开变量n个int类型的字节大小
System.out.println(Arrays.toString(arr));//打印数组。
}
}
- C语言采用的语法是
int arr[5];
Java不支持这种写法,但Java支持int arr[]={1,2,3};
这涉及下面的初始化问题。
动态初始化
- 由程序员指定堆区开多大的空间。
int[] arr = new int[5];
JVM会分配堆区的5个连续int大小的内存空间,把地址给arr。 - 可以分开写
int[] arr; arr = new int[5];
前面提过,把arr当成int*的指针理解。new int[5]
这句话功能是在堆区开辟空间,和返回地址。arr实际上存储了这个地址。
Q:如果只单独创建了这块空间,并没有指针(准确来说是引用)接受这个值,会不会内存泄露?
不会,Java不同于C/C++,它可以自动管理堆区内存,当没有指针(引用)指向这块空间时,会自动回收内存。(后面会略微提及JVM内存分布)
- new 和 指定长度是动态初始化的标志。
- 数组元素值,为其类型默认值(如int默认为0)。
C中的malloc函数会在堆区开辟空间,但不会初始化。数组内部全是垃圾值。
calloc函数会进行初始化,将每个字节初始化为0.
- 注意事项
Java中不会在数组声明左边指定长度。
int[5] = new int[5];//错误写法
Java支持C的写法,但是
int arr[5];
错误写法,左边不能放长度。
int arr[]=new int[5];
正确写法,Java风格的可以将类型与变量分离,清晰。
静态初始化
上面的动态初始化虽然叫初始化,但实际上是说明如何创建数组,因为我们不能自己设定值。
- 静态初始化特点是不用指定长度了
public class Example {
public static void main(String[] args) {
int[] arr= new int[]{1,2,3};//右值不能指定长度。数组长度由编译器决定。
}
}
public class Example {
public static void main(String[] args) {
int[] arr = {1, 2, 3};//new int[]也省略了。
int arr1[] = {1, 2, 3};//C语言风格
}
}
```
2. 可以分开写吗?
`new int[]{1,2,3}`没明确指定长度,但编译器根据初始化,可以确定,且new表达式会返回地址。
分开写没问题。
```Java
public class Example {
public static void main(String[] args) {
//分开写没问题!
int[] arr;
arr = new int[]{1,2,3};
//下面报错
int[] arr2;
arr2 = {1,2,3};
}
}
```
`int[] arr2 = {1, 2, 3};`这句话new 和长度可以省略。但是分开写` int[] arr2;
arr2 = {1,2,3};`就不能省略了。
#### 空数组
Java可以创建空数组`int[] = new int[0];``String arr2[] = new String[0];`
空数组不存储任何元素。那么它与null有区别吗?空数组的内存怎么看?
先不提类与对象的概念,用C结构体迁移一下。
首先空数组与null不一样,数组创建在Java中是作为一个一个的对象。对象可以看作一个类似C的结构体变量,数组对象存储了自己的信息,比如存储了自己的类型,长度等等。
总而言之,Java中的数组可以近似看作C的结构体,里面有成员,成员有长度length,类型type,以及C类型的数组(这里存储的是数组的实际元素)。
空数组它有地址,而null是特殊字面量,表示不指向任何对象。
### 一维数组使用
简单回顾:
数组的元素是通过索引访问的。数组索引从 0 开始,所以索引值从 0 到 array.length-1。(array.length具体是啥见下文。)
1. 数组访问
与C一样,通过索引(index)和[]操作符来操作数组元素。
例如`int[] arr=new int[]{1,2,3};`
获取下标为2的数组元素.`int i = arr[2];`即获取数组第三个元素。
修改下标为2的数组元素。`arr[2] = 4;`
2. 遍历数组
还记得Java中的数组类似C中的结构体。Java数组有个字段length记录了数组的长度。
因此,获取数组长度`arr.length`。
```java
//遍历且打印一维数组。
for(int i = 0;i<arr.length;i++)
{
System.out.print(arr[i]);
}
//反向遍历打印一维数组
for(int i = arr.length;i>=0;i++)
{
System.out.print(arr[i]);
}
```
后面会介绍一种更快的方法,数组改成字符串打印。
3. for each循环
Java提供了一种很强的循环,我们处理数组的时候不在关心数组索引,而是针对每一个元素。
`for(variable : collection) statement`
variable是声明的变量 来依次代表数组的值,collection 现在认为数组。 statement 是代码语句。
for each循环,对于数组的每一个元素,用一个临时变量x表示整个集合。第一次循环x表示数组的第一个元素,每经历一次循环,x表示数组的下一个元素。
```java
int[] array={1,2,3,4,5,6};
for(int x: array)//x存储array数组的第一个到最后一个元素
{
System.out.println(x);
}
```
如果你要在循环中要用到索引时,那么采用一般的for循环。
for each循环会遍历整个数组。
### Arrays类
更多内容翻JDK源码或者阅读API文档,其中大量方法都有很多重载形式。
Array类里面有很多操作数组的方法,具体怎么使用呢?
我先举个例子。
使用Arrays类前,需要在前面写`import java.util.Arrays;`
如果你一点不了解类与对象,那么我还是以C的结构体来迁移扩展。
#### 1. toString方法
Arrays类中的toString方法的作用是将数组的数据变成了字符串类型的数据。
原有数组没有改变,只是利用数组内部的数据构建返回了这么一个字符串。
```java
int[] array={1,2,3,4,5,6};
System.out.println(Arrays.toString(array));
//打印结果: [1, 2, 3, 4, 5, 6]
```
Arrays类视为一个结构体,不过这个结构体内部可以放函数的声明和定义。toString是Arrays类的一个函数,Array.toString就是调用Arrays结构体的toString函数,调用函数就要考虑传递参数的问题。`Arrays.toString(array);`将数组名作为此参数传递。
由打印结果可知:`[1, 2, 3, 4, 5, 6]`,该函数返回了左边的一个字符串。
* 模拟实现一下
```java
public static String toString(int[] array)
{
if(array.length==0)
return "[]";
String s ="["+array[0];
for(int i = 1;i<array.length;i++)
{
s+=", "+array[i];
}
s+="]";
return s;
}
public static void main(String[] args) {
int[] array={1,2,3,4,5,6};
System.out.println(toString(array));
}
```
toString方法重载,传递float,double类型的数组也可以。
感兴趣,可以翻一下Arrays类的toString源码。
关于toString方法现在了解这些足够了,后续出镜机会还很多。
#### 2. copyOf
copyOf方法原型`public static xxx[] copyOf(XXX [],int length);`
1. 第一个参数是要拷贝的数组副本。返回数组类型与第一个参数一致。
2. 第二个参数是数组长度。
```java
public static void main(String[] args) {
int[] array={1,2,3,4,5,6};
int[] array1 = Arrays.copyOf(array,array.length);
System.out.println(Arrays.toString(array1));
}
```
3. 模拟实现copyOf方法
```java
public static int[] copyOf(int[] array,int length)
{
int[] arr = new int[length];
for (int i = 0; i < length; i++) {
arr[i] = array[i];
}
return arr;
}
public static void main(String[] args) {
int[] array={1,2,3,4,5,6};
int[] array1 = copyOf(array,array.length);
System.out.println(Arrays.toString(array1));
}
```
#### 3.binarySearch(二分查找)
以下为回顾
* 顺序查找
```java
public static int find (int[] array,int key)
{
for (int i = 0; i < array.length; i++) {
if(key == array[i])
{
return i;//找到了返回索引
}
}
return -1;//找不到返回无效值(-1)
}
```
* 二分查找
Arrays类提供快速查找有序数组的binarySearch方法。
` public static int binarySearch(int[] a, int key);`
它会在int[] 数组中查找key元素的位置,如果查找成功则返回数组下标,否则返回-1;
```java
public static void main(String[] args) {
int[] array={1,2,3,4,5,6};
System.out.println(Arrays.binarySearch(array , 5));//4
System.out.println(Arrays.binarySearch(array,0));//-1
}
```
模拟实现binarySearch
```java
public static int binarySearch(int[] array , int key)
{
//数组必须有序,且为升序数组。
int left = 0;int right = array.length;
while(left<=right)
{
int mid = (right - left)/2+left;
if(array[mid]>key)
{
right = mid - 1;
}
else if (array[mid]<key)
{
left = mid + 1;
}
else
{
return mid;
}
}
return -1;
}
public static void main(String[] args) {
int[] array={1,2,3,4,5,6};
System.out.println(binarySearch(array , 5));//4
}
```
#### 4. sort排序
以下是Arrays类的sort函数的一种重载形式
`public static void sort(int[] a);`
例子:
```java
public static void main(String[] args) {
int[] array={47,38,20,19,0,4,2};
Arrays.sort(array);//传数组名
//array被排成升序了,打印观察一下。
System.out.println(Arrays.toString(array));
}
//打印结果:[0, 2, 4, 19, 20, 38, 47]
```
冒泡排序
```java
public static void bubbleSort(int[] array)
{
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 tmp = array[j];
array[j] = array[j+1] ;
array[j+1] = tmp;
}
}
}
}
public static void main(String[] args) {
int[] array={47,38,20,19,0,4,2};
bubbleSort(array);
System.out.println(Arrays.toString(array));
}
```
插入排序
```java
public static void insertSort(int[] array)
{
for(int i = 1;i<array.length;i++){
int tmp =array[i];//抽出来
int end = i - 1;//记录上一个位置
while(end>=0)
{
if(array[end]>tmp)
{
array[end+1]=array[end];//挪动位置
end--;
}
else
{
break;
}
}
array[end+1]=tmp;
}
}
public static void main(String[] args) {
int[] array={47,38,20,19,0,4,2,-1,99,100,57};
insertSort(array);
System.out.println(Arrays.toString(array));
}
```
#### equal方法
Arrays类提供了equal方法可以比较两个同类型的数组是否相等。
* 模拟实现及Arrays类的equals方法使用
```java
/*
* Main.java
*/
public static boolean equals(int[] a , int[] a2)
{
//两个指向同一个堆区数组必然相等
if(a==a2)
return true;
//其中一个为空引用必然不相等
if(a==null||a2==null)
return false;
//比较长度
int length = a.length;
if(a2.length!=length)
return false;
//开始逐下标匹配
for (int i = 0; i < length; i++) {
if(a[i]==a2[i])
;
else
return false;
}
return true;
}
public static void main(String[] args) {
int[] array = {1,2,3,4,5};
int[] array2 = {1,2,3,4,5};
System.out.println(Arrays.equals(array,array2));//调用Arrays类的方法
System.out.println(equals(array,array2));//调用自己当前类实现的equal方法
}
```