java数组.docx
《java数组.docx》由会员分享,可在线阅读,更多相关《java数组.docx(18页珍藏版)》请在冰豆网上搜索。
java数组
Java内存分配与管理是Java的核心技术之一,之前我们曾介绍过Java的内存管理与内存泄露以及Java垃圾回收方面的知识,今天我们再次深入Java核心,详细介绍一下Java在内存分配方面的知识。
一般Java在内存分配时会涉及到以下区域:
◆寄存器:
我们在程序中无法控制
◆栈:
存放基本类型的数据和对象的引用,但对象本身不存放在栈中,而是存放在堆中
◆堆:
存放用new产生的数据
◆静态域:
存放在对象中用static定义的静态成员
◆常量池:
存放常量
◆非RAM存储:
硬盘等永久存储空间
Java内存分配中的栈
在函数中定义的一些基本类型的变量数据和对象的引用变量都在函数的栈内存中分配。
当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当该变量退出该作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。
Java内存分配中的堆
堆内存用来存放由new创建的对象和数组。
在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。
在堆中产生了一个数组或对象后,还可以在栈中定义一个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量。
引用变量就相当于是为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。
引用变量就相当于是为数组或者对象起的一个名称。
引用变量是普通的变量,定义时在栈中分配,引用变量在程序运行到其作用域之外后被释放。
而数组和对象本身在堆中分配,即使程序运行到使用new产生数组或者对象的语句所在的代码块之外,数组和对象本身占据的内存不会被释放,数组和对象在没有引用变量指向它的时候,才变为垃圾,不能在被使用,但仍然占据内存空间不放,在随后的一个不确定的时间被垃圾回收器收走(释放掉)。
这也是Java比较占内存的原因。
数组是Java中的对象,它用来存储多个相同类型的基本数据类型或者对象引用。
一、声明数组
数组是通过说明它将要保存的元素类型来声明的,元素类型可以是对象或者基本类型。
类型后面的方括号可以在写在标识符的前面,也可以写在后面。
当然我们推荐还是写在前面。
1
int[]number1;
2
intnumber2[];
int[]number1;把方括号紧贴着类型写,会明确的告诉我们声明的是一个对象他的名字是number1,他的类型是数组类型,而且是只能存储int类型的数组。
而后一种写法是C程序员更喜欢的写法。
可以声明多维数组,可以声明对象类型的数组
1
String[][]s1;//二维数组
2
String[][][][]s2;//四维数组
3
String[]s3[];//怪异写法的二维数组,这样写也是正确的,但是不建议这么干
Java中的二维数组就是一维数组中的每一个元素都还是个数组,那么合起来就是二维数组了,以此类推。
最后记住一句话在声明数组的时候不能在方括号中写数组的长度,因为声明数组的过程并没有创建数组本身,只是定义了一个变量,但是变量并没被赋值。
二、构建数组|创建数组|实例化数组
构建数组意味着在堆上创建数组对象(所有的对象都存储在堆上,堆是一种内存存储结构,既然要存储就设计空间分配问题,因此此时需要指定数组的大小)。
而此时虽然有了数组对象,但数组对象里还没有值。
1
int[]scores;//声明数组
2
scores=newint[34];//创建数组
3
int[]i=newint[22];//声明并创建数组
构建数组意味着在堆上创建数组对象(所有的对象都存储在堆上,堆是一种内存存储结构,既然要存储就设计空间分配问题,因此此时需要指定数组的大小)。
1
int[][]xy=newint[2][3];//声明并创建二维数组
2
int[][]mn=newint[2][]; //声明并创建二维数组,只创建第一级数组也是可以的。
3
mn[0]=int[4];//分别定义第二级数组
4
mn[1]=int[5];//他们的长度可以不同
三、初始化数组|给数组赋值
初始化数组就是把内容放在数组中。
数组中的内容就是数组的元素。
他们可以是基本数据类型也可以是引用数据类型。
如同引用类型的变量中保存的是指向对象的引用而不是对象本身一样。
数组中保存的也是对象的引用而不是对象本身。
1
Pig[]pigs=newPig[3];//声明并创建猪数组
2
pigs[0]=newPig(); //给每一个元素赋值,创建了三个猪对象,此时数组里才真正有了对象
3
pigs[1]=newPig(); //数组用下标来赋值和访问,下标写在[]中,数组下标最大是声明数量减1
4
pigs[2]=newPig();
下面我们再看一个例子:
1
int[]numbers={0,1,2,3,4,5,6,7,8,9};
2
Pig[]pigs={newPig(),newPig(),newPig};
3
int[][]xy={{2,3},{4,5},{6,7}};
这种写法就是把声明、创建和初始化同时完成的快捷写法。
注意这种写法不能被分拆:
1
int[]numbers;
2
numbers={0,1,2,3,4,5,6,7,8,9};//这样的写法在java中是不允许的,这……很不幸
然而,下面的写法则是合法的:
1
int[]numbers;
2
numbers=newint[]{0,1,2,3,4,5,6,7,8,9}; //创建匿名数组并赋值
3
int[][]xy=newint[][]{{2,3},{4,5},{5,6}};//创建二维匿名数组并赋值
4
int[]x=newint[3]{1,2,3}; //这样的写法是错误的
我们看到这样的写法多了个创建匿名数组的过程,记住创建匿名数组的时候不要在中括号中填写数组的大小,否则会报错
java语言中,数组是一种最简单的复合数据类型。
数组是有序数据的集合,数组中的每个元素具有相同的数据类型,可以用一个统一的数组名和下标来唯一地确定数组中的元素。
数组有一维数组和多维数组。
★一维数组
1.一维数组的定义
typearrayName[];
类型(type)可以为Java中任意的数据类型,包括简单类型和复合类型。
例如:
intintArray[];
DatedateArray[];
2.一维数组的初始化
◇静态初始化
intintArray[]={1,2,3,4};
StringstringArray[]={"abc","How","you"};
◇动态初始化
1)简单类型的数组
intintArray[];
intArray=newint[5];
2)复合类型的数组
StringstringArray[];
StringstringArray=newString[3];/*为数组中每个元素开辟引用空间(32位)*/
stringArray[0]=newString("How");//为第一个数组元素开辟空间
stringArray[1]=newString("are");//为第二个数组元素开辟空间
stringArray[2]=newString("you");//为第三个数组元素开辟空间
3.一维数组元素的引用
数组元素的引用方式为:
arrayName[index]
index为数组下标,它可以为整型常数或表达式,下标从0开始。
每个数组都有一个属性length指明它的长度,例如:
intArray.length指明数组intArray的长度。
★多维数组
Java语言中,多维数组被看作数组的数组。
1.二维数组的定义
typearrayName[][];
type[][]arrayName;
2.二维数组的初始化
◇静态初始化
intintArray[][]={{1,2},{2,3},{3,4,5}};
Java语言中,由于把二维数组看作是数组的数组,数组空间不是连续分配的,所以不要求二维数组每一维的大小相同。
◇动态初始化
1)直接为每一维分配空间,格式如下:
arrayName=newtype[arrayLength1][arrayLength2];
inta[][]=newint[2][3];
2)从最高维开始,分别为每一维分配空间:
arrayName=newtype[arrayLength1][];
arrayName[0]=newtype[arrayLength20];
arrayName[1]=newtype[arrayLength21];
…
arrayName[arrayLength1-1]=newtype[arrayLength2n];
3)例:
二维简单数据类型数组的动态初始化如下,
inta[][]=newint[2][];
a[0]=newint[3];
a[1]=newint[5];
对二维复合数据类型的数组,必须首先为最高维分配引用空间,然后再顺次为低维分配空间。
而且,必须为每个数组元素单独分配空间。
例如:
Strings[][]=newString[2][];
s[0]=newString[2];//为最高维分配引用空间
s[1]=newString[2];//为最高维分配引用空间
s[0][0]=newString("Good");//为每个数组元素单独分配空间
s[0][1]=newString("Luck");//为每个数组元素单独分配空间
s[1][0]=newString("to");//为每个数组元素单独分配空间
s[1][1]=newString("You");//为每个数组元素单独分配空间
3.二维数组元素的引用
对二维数组中的每个元素,引用方式为:
arrayName[index1][index2]
例如:
num[1][0];
4.二维数组举例:
【例2.2】两个矩阵相乘
publicclassMatrixMultiply{
publicstaticvoidmain(Stringargs[]){
inti,j,k;
inta[][]=newint[2][3];//动态初始化一个二维数组
intb[][]={{1,5,2,8},{5,9,10,-3},{2,7,-5,-18}};//静态初始化
一个二维数组
intc[][]=newint[2][4];//动态初始化一个二维数组
for(i=0;i<2;i++)
for(j=0;j<3;j++)
a[i][j]=(i+1)*(j+2);
for(i=0;i<2;i++){
for(j=0;j<4;j++){
c[i][j]=0;
for(k=0;k<3;k++)
c[i][j]+=a[i][k]*b[k][j];
}
}
System.out.println("*******MatrixC********");//打印MatrixC标记
for(i=0;i<2;i++){
for(j=0;j<4;j++)
System.out.println(c[i][j]+"");
System.out.println();
}
}
}
6.4多维数组基础
在学校里,由于一个班的人数不多,所以按照顺序编号即可,当人数增多时,例如对于学校里的人,在编号时就要增加层次,例如XX班XX号。
在部队中也是这样,XX师XX团XX营XX连XX排XX班,这里的层次就比较深了。
为了管理数据的方便,一般要加深管理的层次,这就是多维数组的由来。
多维数组,指二维以及二维以上的数组。
二维数组有两个层次,三维数组有三个层次,依次类推。
每个层次对应一个下标。
在实际使用中,为了使结构清晰,一般对于复杂的数据都是用多维数组。
关于多维数组的理解,最终的是理解数组的数组这个概念,因为数组本身就是一种复合数据类型,所以数组也可以作为数组元素存在。
这样二维数组就可以理解成内部每个元素都是一维数组类型的一个一维数组。
三维数组可以理解成一个一维数组,内部的每个元素都是二维数组。
无论在逻辑上还是语法上都支持“数组的数组”这种理解方式。
通常情况下,一般用二维数组的第一维代表行,第二维代表列,这种逻辑结构和现实中的结构一致。
和一维数组类似,因为多维数组有多个下标,那么引用数组中的元素时,需要指定多个下标。
6.5多维数组语法
下面以二维数组为例,来介绍多维数组的语法。
6.5.1多维数组声明
多维数组的声明:
数据类型[][]数组名称;
数据类型[]数组名称[];
数据类型数组名称[][];
以上三种语法在声明二维数组时的功能是等价的。
同理,声明三维数组时需要三对中括号,中括号的位置可以在数据类型的后面,也可以在数组名称的后面,其它的依次类推。
例如:
int[][]map;
charc[][];
和一维数组一样,数组声明以后在内存中没有分配具体的存储空间,也没有设定数组的长度。
6.5.2多维数组初始化
和一维数组一样,多维数组的初始化也可以分为静态初始化(整体赋值)和动态初始化两种,其语法格式如下。
6.5.2.1静态初始化
以二维数组的静态初始化为例,来说明多维数组静态初始化的语法格式。
示例代码如下:
int[][]m={
{1,2,3},
{2,3,4}
};
在二维数组静态初始化时,也必须和数组的声明写在一起。
数值书写时,使用两个大括号嵌套实现,在最里层的大括号内部书写数字的值。
数值和数值之间使用逗号分隔,内部的大括号之间也使用逗号分隔。
由该语法可以看出,内部的大括号其实就是一个一维数组的静态初始化,二维数组只是把多个一维数组的静态初始化组合起来。
同理,三维数组的静态初始化语法格式如下:
int[][][]b={
{
{1,2,3},
{1,2,3}
},
{
{3,4,1},
{2,3,4}
}
};
说明:
这里只是演示语法格式,数值本身没有意义。
6.5.2.2动态初始化
二维数组动态初始化的语法格式:
数据类型[][]数组名称=new数据类型[第一维的长度][第二维的长度];
数据类型[][]数组名称;
数组名称=new数据类型[第一维的长度][第二维的长度];
示例代码:
byte[][]b=newbyte[2][3];
intm[][];
m=newint[4][4];
和一维数组一样,动态初始化可以和数组的声明分开,动态初始化只指定数组的长度,数组中每个元素的初始化是数组声明时数据类型的默认值。
例如上面初始化了长度为2X3的数组b,和4X4的数组m。
使用这种方法,初始化出的第二维的长度都是相同的,如果需要初始化第二维长度不一样的二维数组,则可以使用如下的格式:
intn[][];
n=newint[2][];//只初始化第一维的长度
//分别初始化后续的元素
n[0]=newint[4];
n[1]=newint[3];
这里的语法就体现了数组的数组概念,在初始化第一维的长度时,其实就是把数组n看成了一个一维数组,初始化其长度为2,则数组n中包含的2个元素分别是n[0]和n[1],而这两个元素分别是一个一维数组。
后面使用一维数组动态初始化的语法分别初始化n[0]和n[1]。
6.5.3引用数组元素
对于二维数组来说,由于其有两个下标,所以引用数组元素值的格式为:
数组名称[第一维下标][第二维下标]
该表达式的类型和声明数组时的数据类型相同。
例如引用二维数组m中的元素时,使用m[0][0]引用数组中第一维下标是0,第二维下标也是0的元素。
这里第一维下标的区间是0到第一维的长度减1,第二维下标的区间是0到第二维的长度减1。
6.5.4获得数组长度
对于多维数组来说,也可以获得数组的长度。
但是使用数组名.length获得的是数组第一维的长度。
如果需要获得二维数组中总的元素个数,可以使用如下代码:
int[][]m={
{1,2,3,1},
{1,3},
{3,4,2}
};
intsum=0;
for(inti=0;isum+=m[i].length;//第二维的长度相加
}
在该代码中,m.length代表m数组第一维的长度,内部的m[i]指每个一维数组元素,m[i].length是m[i]数组的长度,把这些长度相加就是数组m中总的元素个数。
3.10.3 数组拷贝
在Java中,允许将一个数组变量拷贝给另一个数组变量。
这时,两个变量将引用同一个数组:
图3-14显示了拷贝的结果。
如果希望将一个数组的所有值拷贝到一个新的数组中去,就要使用Arrays类的copyOf方法:
第2个参数是新数组的长度。
这个方法通常用来增加数组的大小:
如果数组元素是数值型,那么多余的元素将被赋值为0;如果数组元素是布尔型,则将赋值为false。
相反,如果长度小于原始数组的长度,则只拷贝最前面的数据元素。
图3-14 拷贝一个数组变量
数组to必须有足够的空间存放拷贝的元素。
注释:
在JavaSE6之前,用System类的arraycopy方法将一个数组的元素拷贝到另一个数组中。
调用这个方法的语法格式为:
数组to必须有足够的空间存放拷贝的元素。
例如,下面这段语句所得到的结果如图3-15所示。
它创建了两个数组,然后将第一个数组的后4个元素拷贝到第二个数组中。
拷贝从原始数组的第2个位置开始,一共拷贝4个元素,目标数组的起始位置为3。
输出结果为
C++注释:
Java数组与C++数组在堆栈上有很大不同,但基本上与分配在堆(heap)上的数组指针一样。
也就是说,
不同于
而等同于
Java中的[]运算符被预定义为检查数组边界,而且没有指针运算,即不能通过a加1得到数组的下一个元素。
图3-15 数组之间拷贝元素值
3.10.5 数组排序
要想对数值型数组进行排序,可以使用Arrays类中的sort方法:
这个方法使用了优化的快速排序算法。
快速排序算法对于大多数数据集合来说都是效率比较高的。
Array类还提供了几个使用很便捷的方法,在稍后的API注释中将介绍它们。
例3-7中的程序用到了数组,它产生一个抽彩游戏中的随机数值组合。
假如抽彩是从49个数值中抽取6个,那么程序可能的输出结果为:
要想选择这样一个随机的数值集合,就要首先将数值1,2,…,n存入数组numbers中:
而用第二个数组存放抽取出来的数值:
现在,就可以开始抽取k个数值了。
Math.random方法将返回一个0到1之间(包含0、不包含1)的随机浮点数。
用n乘以这个浮点数,就可以得到从0到n-1之间的一个随机数。
下面将result的第i个元素设置为numbers[r]存放的数值,最初是r+1。
但正如所看到的,numbers数组的内容在每一次抽取之后都会发生变化。
现在,必须确保不会再次抽取到那个数值,因为所有抽彩的数值必须不相同。
因此,这里用数组中的最后一个数值改写number[r],并将n减1。
关键在于每次抽取的都是下标,而不是实际的值。
下标指向包含尚未抽取过的数组元素。
在抽取了k个数值之后,就可以对result数组进行排序了,这样可以让输出效果更加清晰:
例3-7LotteryDrawing.java
java.util.Arrays1.2
staticStringtoString(type[]a) 5.0
返回包含a中数据元素的字符串,这些数据元素被放在括号内,并用逗号分隔。
参数:
a 类型为int、long、short、char、byte、boolean、float或double的数组。
statictypecopyOf(type[]a,intlength)6
statictypecopyOf(type[]a,intstart,intend)6
返回与a类型相同的一个数组,其长度为length或者end-start,数组元素为a的值。
参数:
a 类型为int、long、short、char、byte、boolean、float或double的数组。
start 起始下标(包含这个值)。
end 终止下标(不包含这个值)。
这个值可能大于a.length。
在这种情况下,结果为0或false。
length 拷贝的数据元素长度。
如果length值大于a.length,结果为0或false;否则,数组中只有前面length个数据元素的拷贝值。
staticvoidsort(type[]a)
采用优化的快速排序算法对数组进行排序。
参数:
a 类型为int、long、short、char、byte、boolean、float或double的数组。
staticintbinarySearch(type[]a,typev)
staticintbinarySearch(type[]a,intstart,intend,typev) 6
采用二分搜索算法查找值v。
如果查找成功,则返回相应的下标值;否则,返回一个负数值r。
-r-1是为保持a有序v应插入的位置。
参数: