我正在运行一个在线自动程序评估平台,对于其中一个练习,Java“Scanner”正在使用太多的内存(我们刚刚开始支持Java,所以之前没有出现问题)。当我们向初学者教授算法时,我们不能仅仅要求他们通过读取另一个字节后的一个字节来重新编码。
根据我们的测试,扫描仪使用高达200字节读取一个整数...
练习:10 000个整数,哪个100个连续整数的窗口有最大值?
内存使用量很小(你只需要记住最后100个整数)但是在带有“Scanner / nextInt()”的经典版本和手动版本(见下文)之间我们可以看到内存中2.5 Mb的差异。
2.5 Mb读取10 000个整数==> 200字节读取一个整数?
是否有任何简单的解决方案可以向初学者解释,或者是以下功能(或类似)?
public static int read_int() throws IOException
{
int number = 0;
int signe = 1;
int byteRead = System.in.read();
while (byteRead != '-' && ((byteRead < '0') || ('9' < byteRead)))
byteRead = System.in.read();
if (byteRead == '-'){
signe = -1;
byteRead = System.in.read();
}
while (('0' <= byteRead) && (byteRead <= '9')){
number *= 10;
number += byteRead - '0';
byteRead = System.in.read();
}
return signe*number;
}
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int nbValues = sc.nextInt();
int widthWindow = sc.nextInt();
int values[] = new int[widthWindow];
int sumValues = 0;
for (int idValue = 0; idValue < widthWindow; idValue++){
values[idValue] = sc.nextInt();
sumValues += values[idValue];
}
int maximum = sumValues;
for (int idValue = widthWindow; idValue < nbValues; idValue++)
{
sumValues -= values[ idValue % widthWindow ];
values[ idValue % widthWindow ] = sc.nextInt();
sumValues += values[ idValue % widthWindow ];
if (maximum < sumValues)
maximum = sumValues;
}
System.out.println(maximum);
}
}
根据要求,内存用作整数数量的函数:
我们最终决定重写(部分)Scanner类。这样我们只需要包含我们的扫描器而不是Java的扫描器,其余的代码保持不变。我们不再有任何内存问题,程序速度提高了20倍。
以下代码来自我的同事ChristophDürr:
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
class Locale {
final static int US=0;
}
public class Scanner {
private BufferedInputStream in;
int c;
boolean atBeginningOfLine;
public Scanner(InputStream stream) {
in = new BufferedInputStream(stream);
try {
atBeginningOfLine = true;
c = (char)in.read();
} catch (IOException e) {
c = -1;
}
}
public boolean hasNext() {
if (!atBeginningOfLine)
throw new Error("hasNext only works "+
"after a call to nextLine");
return c != -1;
}
public String next() {
StringBuffer sb = new StringBuffer();
atBeginningOfLine = false;
try {
while (c <= ' ') {
c = in.read();
}
while (c > ' ') {
sb.append((char)c);
c = in.read();
}
} catch (IOException e) {
c = -1;
return "";
}
return sb.toString();
}
public String nextLine() {
StringBuffer sb = new StringBuffer();
atBeginningOfLine = true;
try {
while (c != '\n') {
sb.append((char)c);
c = in.read();
}
c = in.read();
} catch (IOException e) {
c = -1;
return "";
}
return sb.toString();
}
public int nextInt() {
String s = next();
try {
return Integer.parseInt(s);
} catch (NumberFormatException e) {
return 0; //throw new Error("Malformed number " + s);
}
}
public double nextDouble() {
return new Double(next());
}
public long nextLong() {
return Long.parseLong(next());
}
public void useLocale(int l) {}
}
通过在我的问题中集成代码可以更快,我们通过阅读caracter之后的“建立”数字。
这是Scanner的nextInt()代码
public int nextInt(int radix) {
// Check cached result
if ((typeCache != null) && (typeCache instanceof Integer)
&& this.radix == radix) {
int val = ((Integer)typeCache).intValue();
useTypeCache();
return val;
}
setRadix(radix);
clearCaches();
// Search for next int
try {
String s = next(integerPattern());
if (matcher.group(SIMPLE_GROUP_INDEX) == null)
s = processIntegerToken(s);
return Integer.parseInt(s, radix);
} catch (NumberFormatException nfe) {
position = matcher.start(); // don't skip bad token
throw new InputMismatchException(nfe.getMessage());
}
}
正如您所看到的,它是基数和符号识别,使用缓存等。因此额外的内存使用全部来自旨在提高扫描仪效率的功能。
您可以将所有值读入数组,然后开始对数组求和。
在读取数组时,您仍然需要那么多内存,但在阅读之后,它可以免费用于其他目的。
你的代码的结构将受益,imho,因为现在你可以为你的数字使用不同的源 - 例如util.Random,并仍然搜索数组中的最大总和,或搜索相同的数组以获得不同的序列长度,而无需重读输入。
顺便说一句:我很难读取代码,因为:
我的第一印象是,对于一系列的Ints:
3 9 2 4 6 4 3 2 4 4 5 6 9 3 2 1 9 9 9
你会搜索一个长度为3到第9个值的序列(不计算3和9本身)并搜索最大值(2 + 4 + 6),(4 + 6 + 4),...(4 + 4) +5),但结果是34.您添加前9个值。
建议:
import java.util.Scanner;
class MaxChunk {
int chunksize;
public int[] readValues () {
Scanner sc = new Scanner (System.in);
chunksize = sc.nextInt ();
int length = sc.nextInt ();
int values[] = new int [length];
for (int i = 0; i < length; i++)
{
values[i] = sc.nextInt();
}
return values;
}
public int calc (int values[]) {
int sum = 0;
for (int i = 0; i < chunksize; i++)
{
sum += values[i];
}
int maximum = sum;
for (int j = chunksize; j < values.length; j++)
{
sum -= values [j - chunksize];
sum += values [j];
if (maximum < sum)
maximum = sum;
}
return maximum;
}
public static void main (String[] args) {
MaxChunk maxChunk = new MaxChunk ();
int values[] = maxChunk.readValues ();
System.out.println (maxChunk.calc (values));
}
}
echo "3 9 2 4 6 4 3 2 4 4 5 6 9 3 2 1 9 9" | java MaxChunk
收益14。
我在调查我正在开发的Android应用程序中的严重内存膨胀时遇到了这个问题。
Android有一个记录所有分配的工具。
事实证明,对于仅解析一个nextDouble()调用,Java会进行128次分配。前8位超过1000字节,最大的是4102字节(!)
不用说,这完全无法使用。我们正在努力保持低电池电量,这确实无济于事。
我将尝试使用已发布的替换扫描程序代码,谢谢!
这是证据:
4047 4102 char[] 13 java.lang.AbstractStringBuilder enlargeBuffer
4045 3070 char[] 13 java.lang.String <init>
4085 2834 char[] 13 java.lang.AbstractStringBuilder enlargeBuffer
4048 2738 char[] 13 java.lang.AbstractStringBuilder enlargeBuffer
4099 1892 char[] 13 java.lang.AbstractStringBuilder enlargeBuffer
4108 1264 char[] 13 java.lang.AbstractStringBuilder enlargeBuffer
4118 1222 char[] 13 java.lang.AbstractStringBuilder enlargeBuffer
4041 1128 int[] 13 java.util.regex.Matcher usePattern
[...]
第二列是分配大小(可能以字节为单位,但Android设备监视器未指定)。
一句话:除非你有足够的电量和CPU,否则不要使用扫描仪。