Java,“扫描仪”的内存使用情况

时间:2011-11-15 11:50:11

标签: java memory io java.util.scanner

我正在运行一个在线自动程序评估平台,对于其中一个练习,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);
   }
}

根据要求,内存用作整数个数的函数:

  • 10,000:2.5Mb
  • 20,000:5Mb
  • 50,000:15Mb
  • 100,000:30Mb
  • 200,000:50Mb
  • 300,000:75Mb

4 个答案:

答案 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:我很难读取代码,因为:

  • value / values / sumValues / nb_values - (为什么不是maximumValues)? - 所有变量都是值,因此这无助于理解。
  • 循环通常用i和j或n索引。价值是误导性的
  • length_sequence也有误导性。序列长度是指,但每个人都只使用'长度',因为其他长度没有歧义。
  • 你使用长名称作为琐碎的东西,但是对于一个不那么微不足道的东西来说,这是一个神秘的缩写。我读了你的问题描述和代码,不知道你的代码是做什么的:nb_values你的意思是什么。非阻塞?空字节?附近?它是什么?

我的第一印象是,对于一系列的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,否则不要使用扫描仪。