我必须在内存中保留数千个字符串,以便在Java中串行访问。我应该把它们存储在数组中还是应该使用某种列表?
由于数组将所有数据保存在一个连续的内存块中(与list不同),使用数组存储数千个字符串会导致问题吗?
我必须在内存中保留数千个字符串,以便在Java中串行访问。我应该把它们存储在数组中还是应该使用某种列表?
由于数组将所有数据保存在一个连续的内存块中(与list不同),使用数组存储数千个字符串会导致问题吗?
当前回答
请记住,ArrayList封装了一个数组,因此与使用原始数组相比没有什么区别(除了在java中使用List更容易)。
选择数组而不是数组列表的唯一有意义的情况是,当你存储基本类型时,比如byte、int等,你需要通过使用基本类型数组获得特定的空间效率。
其他回答
不要在没有适当基准测试的情况下陷入优化的陷阱。正如其他人建议的那样,在做出任何假设之前使用分析器。
您所列举的不同数据结构具有不同的用途。列表在开头和结尾插入元素时非常有效,但在访问随机元素时却很困难。数组具有固定的存储,但提供快速的随机访问。最后,ArrayList通过允许数组增长来改进与数组的接口。通常,要使用的数据结构应该由如何访问或添加存储的数据来决定。
About memory consumption. You seem to be mixing some things. An array will only give you a continuous chunk of memory for the type of data that you have. Don't forget that java has a fixed data types: boolean, char, int, long, float and Object (this include all objects, even an array is an Object). It means that if you declare an array of String strings [1000] or MyObject myObjects [1000] you only get a 1000 memory boxes big enough to store the location (references or pointers) of the objects. You don't get a 1000 memory boxes big enough to fit the size of the objects. Don't forget that your objects are first created with "new". This is when the memory allocation is done and later a reference (their memory address) is stored in the array. The object doesn't get copied into the array only it's reference.
这里给出的许多微基准测试发现,像array/ArrayList读取这样的事情需要几纳秒。如果所有内容都在L1缓存中,这是非常合理的。
更高级别的缓存或主存访问的数量级可能是10nS-100nS,而L1缓存的数量级更接近1nS。访问ArrayList有一个额外的内存间接,在实际的应用程序中,你可以几乎从不或每次都支付这个代价,这取决于你的代码在访问之间所做的事情。当然,如果你有很多小的数组列表,这可能会增加你的内存使用,使你更有可能缓存丢失。
原来的海报似乎只使用一个,在短时间内访问了很多内容,所以应该没有太大的困难。但是对于其他人来说可能有所不同,在解释微基准测试时应该注意。
Java Strings, however, are appallingly wasteful, especially if you store lots of small ones (just look at them with a memory analyzer, it seems to be > 60 bytes for a string of a few characters). An array of strings has an indirection to the String object, and another from the String object to a char[] which contains the string itself. If anything's going to blow your L1 cache it's this, combined with thousands or tens of thousands of Strings. So, if you're serious - really serious - about scraping out as much performance as possible then you could look at doing it differently. You could, say, hold two arrays, a char[] with all the strings in it, one after another, and an int[] with offsets to the starts. This will be a PITA to do anything with, and you almost certainly don't need it. And if you do, you've chosen the wrong language.
我写了一个比较数组列表和数组的基准测试。在我的老式笔记本电脑上,遍历5000个元素的数组列表1000次的时间比等效的数组代码慢了大约10毫秒。
所以,如果你什么都不做,只是迭代列表,而且你做了很多,那么也许它值得优化。否则,我会使用列表,因为当你需要优化代码时,它会让你更容易。
注意:我确实注意到,使用for String s: stringsList比使用老式的for循环访问列表要慢50%左右。去图…这是我计时的两个函数;数组和列表由5000个随机(不同的)字符串填充。
private static void readArray(String[] strings) {
long totalchars = 0;
for (int j = 0; j < ITERATIONS; j++) {
totalchars = 0;
for (int i = 0; i < strings.length; i++) {
totalchars += strings[i].length();
}
}
}
private static void readArrayList(List<String> stringsList) {
long totalchars = 0;
for (int j = 0; j < ITERATIONS; j++) {
totalchars = 0;
for (int i = 0; i < stringsList.size(); i++) {
totalchars += stringsList.get(i).length();
}
}
}
如果你有几千个,考虑使用trie。trie是一种树状结构,它合并了存储字符串的公共前缀。
例如,如果字符串是
intern
international
internationalize
internet
internets
该树将存储:
intern
-> \0
international
-> \0
-> ize\0
net
->\0
->s\0
字符串需要57个字符(包括空结束符'\0')来存储,再加上存储它们的String对象的大小。(事实上,我们可能应该四舍五入到16的倍数,但是……)粗略地称它为57 + 5 = 62字节。
这个trie需要29个存储空间(包括空结束符'\0'),加上对trie节点的sizeof,这些节点是一个数组的引用和一列子trie节点。
在这个例子中,结果可能是一样的;对于成千上万的人来说,只要你有共同的前缀,它可能会更少。
现在,在其他代码中使用trie时,必须转换为String,可能使用StringBuffer作为中介。如果在trie之外,同时使用了许多字符串作为字符串,这是一种损失。
但如果你一次只使用几个——比如,在字典中查找东西——trie可以为你节省很多空间。绝对比存储在HashSet中的空间要小。
你说你是“连续地”访问它们——如果这意味着按字母顺序访问,如果你深度优先迭代,trie显然也会免费给你字母顺序。
我来这里是为了更好地感受使用列表而不是数组对性能的影响。我不得不为我的场景调整代码:数组/列表的~1000个整型,主要使用getter,即数组[j] vs. list.get(j)
从7个中选择最好的并不科学(前几个列表的速度慢2.5倍),我得到了这样的结果:
array Integer[] best 643ms iterator
ArrayList<Integer> best 1014ms iterator
array Integer[] best 635ms getter
ArrayList<Integer> best 891ms getter (strange though)
用数组大约快30%
现在发表文章的第二个原因是,没有人会提到使用嵌套循环编写数学/矩阵/模拟/优化代码的影响。
假设你有三个嵌套层,而内部循环的速度是原来的两倍,那么你的性能就会下降8倍。一天就能完成的事情现在需要一个星期。
*编辑 这里非常震惊,我试图声明int[1000]而不是Integer[1000]
array int[] best 299ms iterator
array int[] best 296ms getter
使用Integer[] vs. int[]表示双倍的性能打击,带有迭代器的ListArray比int[]慢3倍。真的认为Java的列表实现类似于本机数组…
参考代码(多次调用):
public static void testArray()
{
final long MAX_ITERATIONS = 1000000;
final int MAX_LENGTH = 1000;
Random r = new Random();
//Integer[] array = new Integer[MAX_LENGTH];
int[] array = new int[MAX_LENGTH];
List<Integer> list = new ArrayList<Integer>()
{{
for (int i = 0; i < MAX_LENGTH; ++i)
{
int val = r.nextInt();
add(val);
array[i] = val;
}
}};
long start = System.currentTimeMillis();
int test_sum = 0;
for (int i = 0; i < MAX_ITERATIONS; ++i)
{
// for (int e : array)
// for (int e : list)
for (int j = 0; j < MAX_LENGTH; ++j)
{
int e = array[j];
// int e = list.get(j);
test_sum += e;
}
}
long stop = System.currentTimeMillis();
long ms = (stop - start);
System.out.println("Time: " + ms);
}