我正在尝试在处理许多(数百万)字符串时优化我的内存使用量。我有一个约150万行的文件,我正在阅读,其中包含列中的十进制数字。例如,文件可能看起来像
16916576.643 4 -12312674.246 4 39.785 4 16916584.123 3 -5937726.325 3
36.794 3
16399226.418 6 -4129008.232 6 43.280 6 16399225.374 4 -1891751.787 4
39.885 4
12415561.671 9 -33057782.339 9 52.412 9 12415567.518 8 -25595925.487 8
49.950 8
15523362.628 5 -12597312.619 5 40.579 5 15523369.553 5 -9739990.371 5
42.003 5
12369614.129 8 -28797729.913 8 50.068 8 0.000 0.000
0.000
....
目前,我正在使用String.split("\\s+")
分隔这些数字,然后在Double.parseDouble()
的每个部分调用String[]
,这看起来像是:
String[] data = line.split("\\s+");
double firstValue = Double.parseDouble(data[0]);
double secondValue = Double.parseDouble(data[1]);
double thirdValue = Double.parseDouble(data[2]);
这最终会创建很多String
个对象。我也可能在行的开头或结尾有空格,所以在分割之前我必须在行上调用trim()
,这也会创建另一个String
对象。垃圾收集器处理这些String
对象,这会导致速度减慢。 Java中是否有更多内存有效的结构来执行此操作?我在考虑使用char[]
代替String
,但我不确定是否会有相当大的改进。
答案 0 :(得分:3)
如果您非常肯定这是一个严重的瓶颈,您可以随时将字符串直接解析为Double
。
// Keep track of my state.
private static class AsDoublesState {
// null means no number waiting for add.
Double d = null;
// null means not seen '.' yet.
Double div = null;
// Is it negative.
boolean neg = false;
void consume(List<Double> doubles, char ch) {
// Digit?
if ('0' <= ch && ch <= '9') {
double v = ch - '0';
if (d == null) {
d = v;
} else {
d = d * 10 + v;
}
// Count digits after the dot.
if (div != null) {
div *= 10;
}
} else if (ch == '.') {
// Decimal point - start tracking how much to divide by.
div = 1.0;
} else if (ch == '-') {
// Negate!
neg = true;
} else {
// Everything else completes the number.
if (d != null) {
if (neg) {
d = -d;
}
if (div != null) {
doubles.add(d / div);
} else {
doubles.add(d);
}
// Clear down.
d = null;
div = null;
neg = false;
}
}
}
}
private static List<Double> asDoubles(String s) {
// Grow the list.
List<Double> doubles = new ArrayList<>();
// Track my state.
AsDoublesState state = new AsDoublesState();
for (int i = 0; i < s.length(); i++) {
state.consume(doubles, s.charAt(i));
}
// Pretend a space on the end to flush an remaining number.
state.consume(doubles, ' ');
return doubles;
}
public void test() {
String s = "16916576.643 4 -12312674.246 4 39.785 4 16916584.123 3 -5937726.325 3 36.794 3";
List<Double> doubles = asDoubles(s);
System.out.println(doubles);
}
如果给出错误的数据,这将会严重破坏。例如。 123--56...392.86
将是一个完全有效的数字,6.0221413e+23
将是两个数字。
使用State
改进了AtomicDouble
,以避免创建所有这些Double
个对象。
// Keep track of my state.
private static class AsDoublesState {
// Mutable double value.
AtomicDouble d = new AtomicDouble();
// Mutable double value.
AtomicDouble div = new AtomicDouble();
// Is there a number.
boolean number = false;
// Is it negative.
boolean negative = false;
void consume(List<Double> doubles, char ch) {
// Digit?
if ('0' <= ch && ch <= '9') {
double v = ch - '0';
d.set(d.get() * 10 + v);
number = true;
// Count digits after the dot.
div.set(div.get() * 10);
} else if (ch == '.') {
// Decimal point - start tracking how much to divide by.
div.set(1.0);
} else if (ch == '-') {
// Negate!
negative = true;
} else {
// Everything else completes the number.
if (number) {
double v = d.get();
if (negative) {
v = -v;
}
if (div.get() != 0) {
v = v / div.get();
}
doubles.add(v);
// Clear down.
d.set(0);
div.set(0);
number = false;
negative = false;
}
}
}
}
答案 1 :(得分:1)
尝试使用Pattern
和Matcher
将字符串与已编译的正则表达式分开:
double[][] out = new double[2][2];
String[] data = new String[2];
data[0] = "1 2";
data[1] = "3 2";
Pattern pat = Pattern.compile("\\s*(\\d+\\.?\\d*)?\\s+?(\\d+\\.?\\d*)?\\s*");
Matcher mat = pat.matcher(data[0]);
mat.find();
out[0][0] = Double.parseDouble(mat.group(1));
out[0][1] = Double.parseDouble(mat.group(2));
mat = pat.matcher(data[1]);
mat.find();
out[1][0] = Double.parseDouble(mat.group(1));
out[1][1] = Double.parseDouble(mat.group(2));
答案 2 :(得分:1)
我们的应用程序中存在类似的问题,其中很多字符串被创建,我们做了很少的事情来帮助我们解决问题。
如果您使用的是32位JVM,那么您最多只能分配4 GB - 理论上的限制。所以转到64位。
描述您的申请
您需要一步一步地完成:
从visualvm开始(单击here获取详细信息)并测量创建的String,Double对象的数量,大小,时间等。
使用Flyweight模式和实习生字符串对象之一。 Guava图书馆有Interner。甚至,你甚至可以做到两倍。这将避免重复并使用弱引用缓存对象,例如这里
Interner<String> interner = Interners.newWeakInterner();
String a = interner.intern(getStringFromCsv());
String b = interner.intern(getStringFromCsv());
从here
复制您还可以使用扫描仪从文件中读取双倍内容,您的代码将更清晰,扫描程序也会在使用Double.ValueOf
时缓存双倍值。
这是代码
File file = new File("double_file.txt");
Scanner scan = new Scanner(file);
while(scan.hasNextDouble()) {
System.out.println( scan.nextDouble() );
}
scan.close();
您可以使用此代码查看演奏中是否有任何GAIN。
答案 3 :(得分:1)
你根本不需要生产任何垃圾:
BufferedInputStream
以避免char[]
和String
创建。没有非ASCII字符,因此您可以直接处理字节。double nextDouble()
的方法,该方法会读取字符直到下一个双人组合。\n
(忽略\r
,因为它只是\n
的一个不必要的附录;使用过\r
个{{1}}很久以前用作行分隔符)。