我正在运行一个在线自动程序评估平台,对于其中一个练习,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);
}
}
根据要求,内存用作整数个数的函数:
答案 0 :(得分:1)
我们最终决定重写(部分)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之后阅读caracter来“建立”数字,从而更快。
答案 1 :(得分:0)
这是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());
}
}
正如您所看到的,它是基数和符号识别,使用缓存等。因此额外的内存使用全部来自旨在提高扫描仪效率的功能。
答案 2 :(得分:0)
您可以将所有值读入数组,然后开始对数组求和。
在读取数组时,您仍然需要那么多内存,但在阅读之后,它可以免费用于其他目的。
您的代码的结构将受益,imho,因为现在您可以为您的数字使用不同的源 - 例如util.Random,仍然搜索数组中的最大总和,或搜索相同的数组以获得不同的序列长度,没有重新阅读输入。
BTW:我很难读取代码,因为:
我的第一印象是,对于一系列的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.
答案 3 :(得分:0)
我在调查严重记忆臃肿时遇到了这个问题 我正在开发的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,否则不要使用扫描仪。