int[] array=new int[10];
假设我们有从 array[0]
到 array[9]
存储的值,如果我想要打印元素而不使用
array.length()
或者 for (int a: array)
我该怎么做?
我的基本问题是JVM如何确定数组的结尾,是在解析数组时遇到null还是遇到垃圾值时结束?array.length()
函数的内置代码是什么?
数组的第10个位置存储了什么?
Java与C/C++在数组方面使用不同的范例。C/C++使用终止符(也称为“垃圾”)值,如NULL来表示数组的结尾。在Java中,数组更像是具有特殊“实例变量”(instance variable)的对象,该变量length
指示数组中有多少个插槽。这个特殊的“实例变量”在数组创建时设置,是只读的。可以通过说array.length
来访问它。
Java希望代码知道何时停止到达数组的结尾,方法是确保它们不指定大于length - 1
的索引。但是,JVM出于安全原因检查每次访问数组。如果JVM发现一个小于0
或大于length - 1
的数组索引,则JVM会抛出IndexOutOfBoundsException
异常。
如果我要打印元素而不使用
array.length()
由于我们总是可以检查长度,因此在Java中不需要在数组结尾处使用标记。在最后一项之后没有任何特殊的内容(它可能是其他变量的内存)。
for(int a: array) {
// code of loop body here
}
这段代码会被编译器神奇地转换为:
for (int i = 0; i < array.length; i++) {
int a = array[i];
// code of loop body here
}
i
索引变量对用户的代码不可访问。这段代码仍然隐式地使用了array.length。.length
变量可以被称为常量。 - Paŭlo Ebermann数组是带有长度字段的对象。在循环时,Java会加载长度字段并将迭代器与其进行比较。
请参见JLS中的10.7 数组成员
for(:)
的参考链接?例如,它在处理 array
和 Collection
时如何工作?我想在前者中会有特殊情况。 - user166390JVM内部可以以任何它认为合适的方式跟踪数组的长度。实际上,当您尝试获取数组的长度时,Java编译器会发出一个名为arraylength
的字节码指令,这表明由JVM决定跟踪数组长度的最佳方法。
大多数实现可能将数组存储为一块内存,其第一个条目是数组的长度,其余元素是实际的数组值。这使得实现可以在O(1)中查询数组的长度以及数组中的任何值。但是,如果实现希望的话,它可以将元素存储在哨兵值后面(如您所建议的)。但我不相信任何实现都会这样做,因为查找长度的成本将随数组大小呈线性增长。
至于foreach循环的工作原理,编译器将该代码转换为类似于以下内容的东西:
for (int i = 0; i < arr.length; ++i) {
T arrayElem = arr[i];
/* ... do work here ... */
}
gets
、strcpy
等中的所有安全漏洞即可。我认为在Java中采用这种方式并不是一个好主意,因为您已经知道数组的大小。 - templatetypedefarrays
的渐进性能要求在JLS的某个地方有记录。 - user166390好的,开始吧 :-)
在C语言中,有很多种处理数组的方式。接下来,我将讲述关于string*
的内容(并使用类型为string*
的变量strings
)。这是因为t[]
"有效地分解"成t*
,而char*
是"C字符串"的类型。因此,string*
表示指向"C字符串"的指针。这掩盖了关于C语言中"数组"和"指针"的一些追求严谨的问题。(请记住:只因为指针可以被访问为p[i]
并不意味着在C语言中它是一个数组类型。)
现在,strings
(类型为string*
)没有办法知道它的大小——它只代表某个字符串的指针或者可能是NULL。现在,让我们看看一些我们可以"知道"大小的方法:
使用哨兵值。 在这里,我假设使用NULL作为哨兵值(或者对于整数“数组”,它可能是-1等)。请记住,C语言没有要求数组必须有哨兵值,因此这种方法与下面的两种方法一样,只是惯例。
string* p;
for (p = strings; p != NULL; p++) {
doStuff(*p);
}
外部跟踪数组大小。
void display(int count, string* strings) {
for (int i = 0; i < count; i++) {
doStuff(strings[i]);
}
}
struct mystrarray_t {
int size;
string* strings;
}
void display(struct mystrarray_t arr) {
for (int i = 0; i < arr.size i++) {
doStuff(arr.strings[i]);
}
}
在Java中,每个数组对象都有一个固定的大小,可以通过arr.length
访问。有特殊的字节码魔法使其工作(在Java中,数组非常神奇),但在语言级别上,这只是一个只读整数字段,永远不会改变(请记住,每个数组对象都有一个固定的大小)。编译器和JVM/JIT可以利用这个事实来优化循环。
与C不同,Java 保证尝试访问超出边界的索引将导致异常(出于性能原因,即使它没有被公开,这也需要JVM跟踪每个数组的长度)。在C中,这只是未定义的行为。例如,如果哨兵值不在对象内部(读作“所需访问的内存”)中,则示例#1将导致缓冲区溢出。
然而,在Java中使用哨兵值是没有任何问题的。与带有哨兵值的C形式不同,这也可以避免IndexOutOfBoundExceptions(IOOB),因为长度保护是最终限制。哨兵只是一个提前退出。
// So we can add up to 2 extra names later
String names[] = { "Fred", "Barney", null, null };
// This uses a sentinel *and* is free of an over-run or IOB Exception
for (String n : names) {
if (n == null) {
break;
}
doStuff(n);
}
或者可能允许IOOB异常,因为我们做了一些愚蠢的事情,比如忽略了数组知道它们的长度这个事实:(有关“性能”的评论请参见)。
// -- THERE IS NO EFFECTIVE PERFORMANCE GAIN --
// Can ONLY add 1 more name since sentinel now required to
// cleanly detect termination condition.
// Unlike C the behavior is still well-defined, just ill-behaving.
String names[] = { "Fred", "Barney", null, null };
for (int i = 0;; i++) {
String n = strings[i];
if (n == null) {
break;
}
doStuff(n);
}
另一方面,我不鼓励使用这种原始的代码——最好只在几乎所有情况下使用适当的数据类型,例如List。
编码愉快。
定义“垃圾值”的含义。(提示:由于一切都是二进制的,除非使用哨兵值,否则根本不存在这样的东西,而这只是不好的做法)。
数组的长度存储在Array
实例中作为成员变量。这并不复杂。
null
是有效的数组元素。)null
是荒谬的概念,因为null
与任何Java原始类型都不兼容。)try {
int i = 0;
while (true) {
System.out.println(arr[i++]);
}
catch (ArrayIndexOutOfBoundsException e) {
// so we are past the last array element...
}
这在技术上是可行的,但这是不好的实践。你不应该使用异常来控制流程。
try {
for(int i=0 ; ; i++) {
System.out.println(arr[i]);
}
}
catch(IndexOutOfBoundsException ex) {}
但那是一种可怕的做事方式!
所有在区间 [0,9] 之外的数组访问都会导致 ArrayIndexOutOfBoundsException
,而不仅仅是位置 10。因此,从概念上说,你可以说你的整个内存(通过索引从Integer.MIN_VALUE
到Integer.MAX_VALUE
)都填充了哨兵值,除了数组本身的空间外,当读取或写入一个被哨兵填充的位置时,就会触发异常。(每个数组都有自己的整个内存要使用)。
class Array<X> {
private final int length;
private final Class<X> componentType;
/**
* invoked on new X[len] .
*/
public Array<X>(int len, Class<X> type) {
if(len < 0) {
throw new NegativeArraySizeException("too small: " + len);
}
this.componentType = type;
this.len = len;
// TODO: allocate the memory
// initialize elements:
for (int i = 0; i < len; i++) {
setElement(i, null);
}
}
/**
* invoked on a.length
*/
public int length() {
return length;
}
/**
* invoked on a[i]
*/
public X getElement(int index) {
if(index < 0 || length <= index)
throw new ArrayIndexOutOfBoundsException("out of bounds: " + index);
// TODO: do the real memory access
return ...;
}
/**
* invoked on a[i] = x
*/
public X setElement(int index, X value) {
if(index < 0 || length <= index) {
throw new ArrayIndexOutOfBoundsException("out of bounds: " + index);
}
if(!componentType.isInstance(value)) {
throw new ArrayStoreException("value " + value + " is of type " +
value.getClass().getName() + ", but should be of type "
+ componentType.getName() + "!");
}
// TODO: do the real memory access
return value;
}
}
()
。 - Michael Berrystruct myarray_t { int length; void* data; }
- user166390