数组是一种有用的数据类型,用于管理在连续内存位置中建模最好的集合元素。下面是如何有效地使用它们。java
有使用 C 或者 FORTRAN 语言编程经验的人会对数组的概念很熟悉。它们基本上是一个连续的内存块,其中每一个位置都是某种数据类型:整型、浮点型或者诸如此类的数据类型。linux
Java 的状况与此相似,可是有一些额外的问题。git
让咱们在 Java 中建立一个长度为 10 的整型数组:程序员
int[] ia = new int[10];
复制代码
上面的代码片断会发生什么?从左到右依次是:github
int[]
将变量的类型声明为 int
数组(由 []
表示)。ia
。=
告诉咱们,左侧定义的变量赋值为右侧的内容。=
的右侧,咱们看到了 new
,它在 Java 中表示一个对象正在被初始化中,这意味着已为其分配存储空间并调用了其构造函数(请参见此处以获取更多信息)。int[10]
,它告诉咱们正在初始化的这个对象是包含 10 个整型的数组。由于 Java 是强类型的,因此变量 ia
的类型必须跟 =
右侧表达式的类型兼容。express
让咱们把这个简单的数组放在一段代码中,并尝试运行一下。将如下内容保存到一个名为 Test1.java
的文件中,使用 javac
编译,使用 java
运行(固然是在终端中):编程
import java.lang.*;
public class Test1 {
public static void main(String[] args) {
int[] ia = new int[10]; // 见下文注 1
System.out.println("ia is " + ia.getClass()); // 见下文注 2
for (int i = 0; i < ia.length; i++) // 见下文注 3
System.out.println("ia[" + i + "] = " + ia[i]); // 见下文注 4
}
}
复制代码
让咱们来看看最重要的部分。数组
ia
,这显而易见。ia.getClass()
。没错,ia
是属于一个类的对象,这行代码将告诉咱们是哪一个类。for (int i = 0; i < ia.length; i++)
,它定义了一个循环索引变量 i
,该变量遍历了从 0 到比 ia.length
小 1 的序列,这个表达式告诉咱们在数组 ia
中定义了多少个元素。ia
的每一个元素的值。当这个程序编译和运行时,它产生如下结果:bash
me@mydesktop:~/Java$ javac Test1.java
me@mydesktop:~/Java$ java Test1
ia is class [I
ia[0] = 0
ia[1] = 0
ia[2] = 0
ia[3] = 0
ia[4] = 0
ia[5] = 0
ia[6] = 0
ia[7] = 0
ia[8] = 0
ia[9] = 0
me@mydesktop:~/Java$
复制代码
ia.getClass()
的输出的字符串表示形式是 [I
,它是“整数数组”的简写。与 C 语言相似,Java 数组以第 0 个元素开始,扩展到第 <数组大小> - 1
个元素。如上所见,咱们能够看到数组 ia
的每一个元素都(彷佛由数组构造函数)设置为零。数据结构
因此,就这些吗?声明类型,使用适当的初始化器,就完成了吗?
好吧,并无。在 Java 中有许多其它方法来初始化数组。
像全部好的问题同样,这个问题的答案是“视状况而定”。在这种状况下,答案取决于初始化后咱们但愿对数组作什么。
在某些状况下,数组天然会做为一种累加器出现。例如,假设咱们正在编程实现计算小型办公室中一组电话分机接收和拨打的电话数量。一共有 8 个分机,编号为 1 到 8,加上话务员的分机,编号为 0。 所以,咱们能够声明两个数组:
int[] callsMade;
int[] callsReceived;
复制代码
而后,每当咱们开始一个新的累计呼叫统计数据的周期时,咱们就将每一个数组初始化为:
callsMade = new int[9];
callsReceived = new int[9];
复制代码
在每一个累计通话统计数据的最后阶段,咱们能够打印出统计数据。粗略地说,咱们可能会看到:
import java.lang.*;
import java.io.*;
public class Test2 {
public static void main(String[] args) {
int[] callsMade;
int[] callsReceived;
// 初始化呼叫计数器
callsMade = new int[9];
callsReceived = new int[9];
// 处理呼叫……
// 分机拨打电话:callsMade[ext]++
// 分机接听电话:callsReceived[ext]++
// 汇总通话统计
System.out.printf("%3s%25s%25s\n", "ext", " calls made",
"calls received");
for (int ext = 0; ext < callsMade.length; ext++) {
System.out.printf("%3d%25d%25d\n", ext,
callsMade[ext], callsReceived[ext]);
}
}
}
复制代码
这会产生这样的输出:
me@mydesktop:~/Java$ javac Test2.java
me@mydesktop:~/Java$ java Test2
ext calls made calls received
0 0 0
1 0 0
2 0 0
3 0 0
4 0 0
5 0 0
6 0 0
7 0 0
8 0 0
me@mydesktop:~/Java$
复制代码
看来这一天呼叫中心不是很忙。
在上面的累加器示例中,咱们看到由数组初始化程序设置的零起始值能够知足咱们的需求。可是在其它状况下,这个起始值可能不是正确的选择。
例如,在某些几何计算中,咱们可能须要将二维数组初始化为单位矩阵(除沿主对角线———左上角到右下角——之外全部全是零)。咱们能够选择这样作:
double[][] m = new double[3][3];
for (int d = 0; d < 3; d++) {
m[d][d] = 1.0;
}
复制代码
在这种状况下,咱们依靠数组初始化器 new double[3][3]
将数组设置为零,而后使用循环将主对角线上的元素设置为 1。在这种简单状况下,咱们可使用 Java 提供的快捷方式:
double[][] m = {
{1.0, 0.0, 0.0},
{0.0, 1.0, 0.0},
{0.0, 0.0, 1.0}};
复制代码
这种可视结构特别适用于这种应用程序,在这种应用程序中,它便于复查数组的实际布局。可是在这种状况下,行数和列数只在运行时肯定时,咱们可能会看到这样的东西:
int nrc;
// 一些代码肯定行数和列数 = nrc
double[][] m = new double[nrc][nrc];
for (int d = 0; d < nrc; d++) {
m[d][d] = 1.0;
}
复制代码
值得一提的是,Java 中的二维数组其实是数组的数组,没有什么能阻止无畏的程序员让这些第二层数组中的每一个数组的长度都不一样。也就是说,下面这样的事情是彻底合法的:
int [][] differentLengthRows = {
{1, 2, 3, 4, 5},
{6, 7, 8, 9},
{10, 11, 12},
{13, 14},
{15}};
复制代码
在涉及不规则形状矩阵的各类线性代数应用中,能够应用这种类型的结构(有关更多信息,请参见此 Wikipedia 文章)。除此以外,既然咱们了解到二维数组其实是数组的数组,那么如下内容也就不足为奇了:
differentLengthRows.length
复制代码
能够告诉咱们二维数组 differentLengthRows
的行数,而且:
differentLengthRows[i].length
复制代码
告诉咱们 differentLengthRows
第 i
行的列数。
考虑到在运行时肯定数组大小的想法,咱们看到数组在实例化以前仍须要咱们知道该大小。可是,若是在处理完全部数据以前咱们不知道大小怎么办?这是否意味着咱们必须先处理一次以找出数组的大小,而后再次处理?这可能很难作到,尤为是若是咱们只有一次机会使用数据时。
Java 集合框架很好地解决了这个问题。提供的其中一项是 ArrayList
类,它相似于数组,但能够动态扩展。为了演示 ArrayList
的工做原理,让咱们建立一个 ArrayList
对象并将其初始化为前 20 个斐波那契数字:
import java.lang.*;
import java.util.*;
public class Test3 {
public static void main(String[] args) {
ArrayList<Integer> fibos = new ArrayList<Integer>();
fibos.add(0);
fibos.add(1);
for (int i = 2; i < 20; i++) {
fibos.add(fibos.get(i - 1) + fibos.get(i - 2));
}
for (int i = 0; i < fibos.size(); i++) {
System.out.println("fibonacci " + i + " = " + fibos.get(i));
}
}
}
复制代码
上面的代码中,咱们看到:
Integer
的 ArrayList
的声明和实例化。add()
附加到 ArrayList
实例。get()
经过索引号检索元素。size()
来肯定 ArrayList
实例中已经有多少个元素。这里没有展现 put()
方法,它的做用是将一个值放在给定的索引号上。
该程序的输出为:
fibonacci 0 = 0
fibonacci 1 = 1
fibonacci 2 = 1
fibonacci 3 = 2
fibonacci 4 = 3
fibonacci 5 = 5
fibonacci 6 = 8
fibonacci 7 = 13
fibonacci 8 = 21
fibonacci 9 = 34
fibonacci 10 = 55
fibonacci 11 = 89
fibonacci 12 = 144
fibonacci 13 = 233
fibonacci 14 = 377
fibonacci 15 = 610
fibonacci 16 = 987
fibonacci 17 = 1597
fibonacci 18 = 2584
fibonacci 19 = 4181
复制代码
ArrayList
实例也能够经过其它方式初始化。例如,能够给 ArrayList
构造器提供一个数组,或者在编译过程当中知道初始元素时也可使用 List.of()
和 array.aslist()
方法。我发现本身并不常用这些方式,由于我对 ArrayList
的主要用途是当我只想读取一次数据时。
此外,对于那些喜欢在加载数据后使用数组的人,可使用 ArrayList
的 toArray()
方法将其实例转换为数组;或者,在初始化 ArrayList
实例以后,返回到当前数组自己。
Java 集合框架提供了另外一种相似数组的数据结构,称为 Map
(映射)。我所说的“相似数组”是指 Map
定义了一个对象集合,它的值能够经过一个键来设置或检索,但与数组(或 ArrayList
)不一样,这个键不须要是整型数;它能够是 String
或任何其它复杂对象。
例如,咱们能够建立一个 Map
,其键为 String
,其值为 Integer
类型,以下:
Map<String, Integer> stoi = new Map<String, Integer>();
复制代码
而后咱们能够对这个 Map
进行以下初始化:
stoi.set("one",1);
stoi.set("two",2);
stoi.set("three",3);
复制代码
等相似操做。稍后,当咱们想要知道 "three"
的数值时,咱们能够经过下面的方式将其检索出来:
stoi.get("three");
复制代码
在个人认知中,Map
对于将第三方数据集中出现的字符串转换为个人数据集中的一致代码值很是有用。做为数据转换管道的一部分,我常常会构建一个小型的独立程序,用做在处理数据以前清理数据;为此,我几乎老是会使用一个或多个 Map
。
值得一提的是,ArrayList
的 ArrayList
和 Map
的 Map
是极可能的,有时也是合理的。例如,假设咱们在看树,咱们对按树种和年龄范围累计树的数目感兴趣。假设年龄范围定义是一组字符串值(“young”、“mid”、“mature” 和 “old”),物种是 “Douglas fir”、“western red cedar” 等字符串值,那么咱们能够将这个 Map
中的 Map
定义为:
Map<String, Map<String, Integer>> counter = new Map<String, Map<String, Integer>>();
复制代码
这里须要注意的一件事是,以上内容仅为 Map
的行建立存储。所以,咱们的累加代码可能相似于:
// 假设咱们已经知道了物种和年龄范围
if (!counter.containsKey(species)) {
counter.put(species,new Map<String, Integer>());
}
if (!counter.get(species).containsKey(ageRange)) {
counter.get(species).put(ageRange,0);
}
复制代码
此时,咱们能够这样开始累加:
counter.get(species).put(ageRange, counter.get(species).get(ageRange) + 1);
复制代码
最后,值得一提的是(Java 8 中的新特性)Streams 还能够用来初始化数组、ArrayList
实例和 Map
实例。关于此特性的详细讨论能够在此处和此处中找到。
via: opensource.com/article/19/…
做者:Chris Hermansen 选题:lujun9972 译者:laingke 校对:wxy