在上一篇中,我们已经了解了数组,它是一种引用类型,本篇将详细介绍数组的内存分配等知识点。数组用来存储同一种数据类型的数据,一旦初始化完成,即所占的空间就已固定下来,即使某个元素被清空,但其所在空间仍然保留,因此数组长度将不能被改变。当仅定义一个数组变量(int[] numbers)时,该变量还未指向任何有效的内存,因此不能指定数组的长度,只有对数组进行初始化(为数组元素分配内存空间)后才可以使用。数组初始化分为静态初始化(在定义时就指定数组元素的值,此时不能指定数组长度)和动态初始化(只指定数组长度,由系统分配初始值)。

//静态初始化
int[] numbers = new int[] { 3, 5, 12, 8, 7 };
String[] names = { "Miracle", "Miracle He" };//使用静态初始化的简化形式
//动态初始化
int[] numbers = new int[5];
String[] names = new String[2];

建议不要混用静态初始化和动态初始化,即不要既指定数组的长度的同时又指定每个元素的值。当初始化完毕后,就可以按索引位置(0~array.length-1)来访问数组元素了。当使用动态初始化时,如在对应的索引位未指定值的话,系统将指定相应数据类型对应的默认值(整数为0,浮点数为0.0,字符为'\u0000',布尔类型为false,引用类型为null)。

public class TestArray {
public static void main(String[] args) {
String[] names = new String[3];
names[0] = "Miracle";
names[1] = "Miracle He";
//以下代码将输出Miracle Miracle He null
/*
for(int i = 0; i < names.length;i++) {
System.out.print(names[i] + " ");
}
*/
//还可以使用foreach来遍历
for(String name : names) {
System.out.print(name + " ");
}
}
}

请注意:java中是没有foreach这个关键字的,其语法是for(type item : items)来表示,但foreach只能用于遍历元素的值而不能改变,必须使用for才能实现。

public class TestForEach {
public static void main(String[] args) {
int[] numbers = { 3, 5, 12, 8, 7 };
for(int number : numbers) {
int num = number * 10;
System.out.print(num + ",");
}
System.out.println("");
//numbers仍然未发生变化(如果换成for将改变)
for(int i = 0;i < numbers.length;i++) {
System.out.print(numbers[i] + ",");
}
}
}

以上简单的介绍了数组的初始化和应用,接下来讲详细介绍数组(数组引用和数组元素)在内存中的存放形式。首先给出结论:数组引用变量是存放在栈内存(stack)中,数组元素是存放在堆内存(heap)中,通过栈内存中的指针指向对应元素的在堆内存中的位置来实现访问,以下图来说明数组此时的存放形式。

那什么是栈内存和堆内存呢?我举例作一一解释。当执行方法时,该方法都会建立自身的内存栈,以用来将该方法内部定义的变量逐个加入到内存栈中,当执行结束时方法的内存栈也随之销毁,我们说所有变量存放在栈内存中,即随着寄存主体的消亡而消亡;反之,当我们创建一个对象时,这个对象被保存到运行时数据区中,以便反复利用(因为创建成本很高),此时不会随着执行方法的结束而消亡,同时该对象还可被其他对象所引用,只有当这个对象没有被任何引用变量引用时,才会在垃圾回收在合适的时间点回收,我们说此时变量所指向的运行时数据区存在堆内存中。

只有类型兼容(即属于同一数据类型体系且遵守优先级由低到高原则),才能将数组引用传递给另一数组引用,但仍然不能改变数组长度(仅仅只是调整数组引用指针的指向)。

public class TestArrayLength {
public static void main(String[] args) {
int[] numbers = { 3, 5, 12 };
int[] digits = new int[4];
System.out.println("digits数组长度:" + digits.length);//4
for(int number : numbers) {
System.out.print(number + ",");//3,5,12,
}
System.out.println("");
for(int digit : digits) {
System.out.print(digit + ",");//0,0,0,0,
}
System.out.println("");
digits = numbers;
System.out.println("digits数组长度:" + digits.length);//3
}
}

虽然看似digits的数组长度看似由4变成3,其实只是numbers和digits指向同一个数组而已,而digits本身失去引用而变成垃圾,等待垃圾回收来回收(但其长度仍然为4),但其内部运行机制如下图所示。

因此当我们看一个数组时(或者其他引用变量),通常看成两部分:数组引用变量和数组元素本身,而数据元素是存放在堆内存中,只能通过数组引用变量来访问。

从上述的示例中看出数组中存放的是基本类型,其实数组中还可以存放引用类型的。而存放基本类型的内存分布已经解释了,而存放引用类型的内存分布则相对复杂了。来看一段非常简单的程序。

public class TestPrimitiveArray {
public static void main(String[] args) {
//1.定义数组
int[] numbers;
//2.分配内存空间
numbers = new int[4];
//3.为数组元素指定值
for(int i = 0;i < numbers.length;i++) {
numbers[i] = i * 10;
}
}
}

按以上步骤的内存分布示意图:

从图中可看出数组元素直接存放在堆内存中,当操作数组元素时,实际上是操作基本类型的变量。接下来再看一段程序:

class Person {
public int age;
public String name;
public void display() {
System.out.println(name + "的年龄是: " + age);
}
}
public class TestReferenceArray {
public static void main(String[] args) {
//1.定义数组
Person[] persons;
//2.分配内存空间
persons = new Person[2];
//3.为数组元素指定值
Person p1 = new Person();
p1.age = 28;
p1.name = "Miracle";
Person p2 = new Person();
p2.age = 30;
p2.name = "Miracle He";
persons[0] = p1;
persons[1] = p2;
//输出元素的值
for(Person p : persons) {
p.display();
}
}
}

对于数组元素为引用类型在内存中的存储与基本类型不一样,此时数组元素仍然存放引用,指向另一块内存,在其中存放有效的数据。

谈到这里,不知是否有朋友要问:Java的多维数组是什么样的?我的回答是:可以有。为什么呢?从底层来看,数组元素可以存放引用类型,包含数组。也就是说在数组元素的内部还可以包含数组(如int[][] numbers = new int[length][]),也即二维数组可当作一维数组(数组长度为length)来处理,也可以同时指定多个维度的长度(如int[][] matrix = new int[length][width]),不过必须至少指定最左端的数组长度length。由此我们得出结论: 任何多维数组(维度为n,n>1)都当作一维数组,其数组元素为n-1维数组

public class TestMultiArray {
public static void main(String[] args) {
//1.定义二维数组
int[][] numbers;
//2.分配内存空间
numbers = new int[3][];
//可以把numbers看作一维数组来处理
for(int i = 0;i < numbers.length;i++) {
System.out.print(numbers[i] + ",");//null,null,null
}
System.out.println("");
//3.为数组元素指定值
numbers[0] = new int[2];
numbers[0][1] = 1;
for(int i = 0;i < numbers[0].length;i++) {
System.out.print(numbers[0][i] + ",");//0,1
}
}
}
最后,简单介绍一下Arrays(位于java.util下)的静态方法:binarySearch、copyOf、copyOfRange、equals、fill、sort、toString等方法(具体用法参见JDK)。

import java.util.Arrays;
public class TestArrays {
public static void main(String[] args) {
int[] a = {3, 4, 5, 6};
int[] b = {3, 4, 5, 6};
System.out.println("a和b是否相等:" + Arrays.equals(a, b));//true
System.out.println("5在a中的位置:" + Arrays.binarySearch(a, 5));//2
int[] c = Arrays.copyOf(a, 6);
System.out.println("a和c是否相等:" + Arrays.equals(a, c));//false
System.out.println("c的元素:" + Arrays.toString(c));//3,4,5,6,0,0
Arrays.fill(c, 2, 4, 1);//将c中第3个到第5个元素(不包含)赋值为1
System.out.println("c的元素:" + Arrays.toString(c));//3,4,1,1,0,0
Arrays.sort(c);
System.out.println("c的元素:" + Arrays.toString(c));//0,0,1,1,3,4
}
}

接下来,给出两个数组实际应用场景的示例。

import java.util.Arrays;
public class NumberToRMB {
private String[] numbers = { "零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖" };
private String[] units = { "拾", "佰","仟" };
/**
* 把一个浮点数分成整数部分和小数部分
* @param number 要进行分割的浮点数
* @return 由整数部分和小数部分组成的字符串数组
*/
private String[] divide(double number) {
long zheng = (long)number;
long xiao = Math.round((number - zheng) * 100);
return new String[] { zheng + "", String.valueOf(xiao) };
}
/**
* 把一个四位数字字符串转化四位人民币大写字符串
* @param str 要转化的四位数字字符串
* @return 四位人民币大写字符串
*/
private String toRMBString(String str) {
String money = "";
for(int i = 0, len = str.length(); i < len; i++) {
int num = str.charAt(i) - 48;
if(i != len - 1 && num != 0) {
money += numbers[num] + units[len - 2 - i];
} else {
money += numbers[num];
}
}
return money;
}
public static void main(String[] args) {
NumberToRMB rmb = new NumberToRMB();
System.out.println(Arrays.toString(rmb.divide(2346.789)));
System.out.println(rmb.toRMBString("2346"));
}
}
import java.io.*;
public class WZQ {
//定义一个二维数组当作棋盘
private String[][] board;
//定义棋盘大小
private static int BOARD_SIZE = 15;
//初始化棋盘
private void initBoard() {
board = new String[BOARD_SIZE][BOARD_SIZE];
for(int i = 0; i < BOARD_SIZE; i++) {
for(int j = 0; j < BOARD_SIZE; j++) {
board[i][j] = "+";
}
}
}
//打印棋盘
private void printBoard() {
for(int i = 0; i < BOARD_SIZE; i++) {
for(int j = 0; j < BOARD_SIZE; j++) {
System.out.print(board[i][j]);
}
System.out.println("");
}
}
//开始下棋
public void play() throws Exception {
initBoard();
printBoard();
//获取键盘输入
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String input = null;
do {
if(input != null) {
String[] pos = input.split(",");
int x = Integer.parseInt(pos[0]);
int y = Integer.parseInt(pos[1]);
board[x - 1][y - 1] = "●";
printBoard();
}
System.out.print("请输入你下棋的坐标(以x,y的形式):");
} while((input = br.readLine()) != null);
}
public static void main(String[] args) throws Exception {
WZQ wzq = new WZQ();
wzq.play();
}
}

数字转化为人民币大写程序中,利用了一维数组表示大写及单位;五子棋游戏中,利用了二维数组表示棋盘。从程序中可看到throws Exception表示不处理任何异常,将在后续的篇章中继续讲解。

Java学习之旅基础知识篇:数组及引用类型内存分配的更多相关文章

  1. Java学习之旅基础知识篇:数据类型及流程控制

    经过开篇对Java运行机制及相关环境搭建,本篇主要讨论Java程序开发的基础知识点,我简单的梳理一下.在讲解数据类型之前,我顺便提及一下Java注释:单行注释.多行注释以及文档注释,这里重点强调文档注 ...

  2. Java学习之旅基础知识篇:面向对象之封装、继承及多态

    Java是一种面向对象设计的高级语言,支持继承.封装和多态三大基本特征,首先我们从面向对象两大概念:类和对象(也称为实例)谈起.来看看最基本的类定义语法: /*命名规则: *类名(首字母大写,多个单词 ...

  3. Java学习1——计算机基础知识

    本文包含了一些计算机基础知识:计算机组成:Windows常用快捷键:DOS常用命令:计算机语言发展史.

  4. JVM基础知识2 垃圾收集器与内存分配策略

    如何判断堆中的哪些对象可以被回收 主流的程序语言都是使用根搜索算法(GC Roots Tracing)判定对象是否存活 基本思路是:通过一系列名为“GC Roots”的对象作为起点,从这些节点开始向下 ...

  5. java学习笔记之基础知识

    1.class不加修饰符默认default,只在当前包里能用. 2.构造函数前面的修饰符的作用类似class的,限制引用的权限. 3.java对象的创建其实就是类的实例化,类的实例化就是在堆上copy ...

  6. Java白皮书学习笔记+Head First Java--用于自我复习 基础知识篇

    本笔记是摘与Hava白皮书上面的内容,用来给自己做提醒的,因此大概并不适合Java的学习者作为笔记参考使用. 以我的水平现在还看不懂这个... 一.基础知识篇 1.常量 final关键字指示常量,只能 ...

  7. 「Java面试题/知识点精华集」20000+字的Java基础知识篇(2020最新版) !

    本文已经收录进我的 79K Star 的 Java 开源项目 JavaGuide:https://github.com/Snailclimb/JavaGuide (「Java学习+面试指南」一份涵盖大 ...

  8. 《Java核心技术·卷Ⅰ:基础知识(原版10》学习笔记 第5章 继承

    <Java核心技术·卷Ⅰ:基础知识(原版10>学习笔记 第5章 继承 目录 <Java核心技术·卷Ⅰ:基础知识(原版10>学习笔记 第5章 继承 5.1 类.超类和子类 5.1 ...

  9. 【Java面试】基础知识篇

    [Java面试]基础知识篇 Java基础知识总结,主要包括数据类型,string类,集合,线程,时间,正则,流,jdk5--8各个版本的新特性,等等.不足的地方,欢迎大家补充.源码分享见个人公告.Ja ...

随机推荐

  1. Sping中的IOC四种注解的简单记录

    @Component 通用注解,不推荐使用,要用的话,任何类的头上都能使用,但是这个类到底是干嘛用得就不知道了. @Controller 控制层使用,也就是MVC中的C,主要的时候使用的时候注意配合@ ...

  2. 畅通工程续 (dijkstra)

    畅通工程续 Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submi ...

  3. Trim(',')的作用去除最有一个','

    public bool XMLDataImport()        { List<string> sqllist = new List<string>();          ...

  4. iOS关于UITabView和UIAlertController,UIAlertAction以及UINavigation,值修改的传递页面推送

    关于UITabView和UIAlertController,UIAlertAction以及UINavigation,值修改的传递 集合嵌套集合的操作 声明 两个必须的的代理 实现部分代码 - (voi ...

  5. python_eval的用法

    1. eval用法: 将字符串str当成有效的表达式来求值并返回计算结果. 2. eval的功能: math当成一个计算器很好用. 将字符串转换为list,tuple,dict. 3. 举例 # -* ...

  6. Nginx日志配置及日志切割

    日志配置 日志对于统计排错来说非常有利的.本文总结了nginx日志相关的配置如access_log.log_format.open_log_file_cache.log_not_found.log_s ...

  7. JavaScript的Cookie操作

    JavaScript是运行在客户端的脚本,因此一般是不能够设置Session的,因为Session是运行在服务器端的. 而cookie是运行在客户端的,所以可以用JS来设置cookie. 假 设有这样 ...

  8. Memcached源码分析之从SET命令开始说起

    作者:Calix 如果直接把memcached的源码从main函数开始说,恐怕会有点头大,所以这里以一句经典的“SET”命令简单地开个头,算是回忆一下memcached的作用,后面的结构篇中关于命令解 ...

  9. jsoup的介绍使用(转)

    jsoup文档: http://www.open-open.com/jsoup/ 原链接: http://www.oschina.net/question/12_14127 jsoup 简介 Java ...

  10. 原生JavaScript之“淘宝轮播图”

    轮播图是我们学习原生js的必经之路 它包含很多基本知识的运用,像this的使用,DOM的操作,还有setInterval的使用和清除,浮动与定位等等,很好的考察了我们的基础知识牢不牢固, 话不多说,直 ...