Java:获取输入整数数组的最有效方法

时间:2018-07-29 23:25:24

标签: java arrays performance inputstream bufferedreader

我正在解决一个问题,该问题要求我将大量整数存储到整数数组中。输入的格式设置为,使得一行显示整数的数量,而下一行显示所有要存储的值。例如:

3
12 45 67

在该问题中,要存储的整数接近100,000。目前,我正在使用这种存储整数的方法:

Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();

int[] iVau = new int[n];

String[] temp = scanner.nextLine().split(" ");

for(int i = 0; i < n; i++) {
    iVau[i] = Integer.parseInt(temp[i]);
}

这很好,但是我要解决的问题有严格的时间限制,而我目前的解决方案超出了它。我知道有一种使用缓冲的读取器和输入流来存储此输入的更有效的方法,但是我不知道该怎么做,有人可以告诉我。

5 个答案:

答案 0 :(得分:2)

您使用Scanner的方式使您的程序一次将包含整数的字符串保存在内存中。输入的第二行有100000个数字,效率不高,您可以一个接一个地读取数字,而不必将前一个数字保留在内存中。因此,以这种方式,避免使用Scanner.readLine()应该会使您的程序运行更快。您不必一次读取整行,也不必第二次读取此String来解析其中的整数:您只需执行一次这两项操作即可。

这里是一个例子。方法testing()不使用任何扫描仪。您提供的是testing2()方法。文件tst.txt包含100000个数字。在我的Mac Mini(Intel Core i5@2.6GHz)上,该程序的输出为:

duration without reading one line at a time, without using a Scanner instance: 140 ms
duration when reading one line at a time with a Scanner instance: 198 ms

如您所见,不使用Scanner可使程序运行速度提高41%((198-140)/ 140 * 100的整数部分等于41)。

package test1;
import java.io.*;
import java.util.*;

public class Test {
    // Read and parse an Int from the stream: 2 operations at once
    private static int readInt(InputStreamReader ir) throws IOException {
        StringBuffer str = new StringBuffer();
        int c;
        do { c = ir.read(); } while (c < '0' || c > '9');
        do {
            str.append(Character.toString((char) c));
            c = ir.read();
        } while (!(c < '0' || c > '9'));
        return Integer.parseInt(str.toString());
    }

    // Parsing the input step by step
    private static void testing(File f) throws IOException {
        InputStreamReader ir = new InputStreamReader(new BufferedInputStream(new FileInputStream(f)));
        int n = readInt(ir);
        int [] iVau = new int[n];
        for (int i = 0; i < n; i++) iVau[i] = readInt(ir);
        ir.close();
    }

    // Your code
    private static void testing2(File f) throws IOException {
        Scanner scanner = new Scanner(f);
        int n = scanner.nextInt();
        int[] iVau = new int[n];
        scanner.nextLine();     
        String[] temp = scanner.nextLine().split(" ");
        for(int i = 0; i < n; i++)
            iVau[i] = Integer.parseInt(temp[i]);
        scanner.close();
    }

    // Compare durations
    public static void main(String[] args) throws IOException {
        File f = new File("/tmp/tst.txt");          

        // My proposal    
        long t = System.currentTimeMillis();
        testing(f);
        System.out.println("duration without reading one line at a time, without using a Scanner instance: " + (System.currentTimeMillis() - t) + " ms");       

        // Your code    
        t = System.currentTimeMillis();
        testing2(f);
        System.out.println("duration when reading one line at a time with a Scanner instance: " + (System.currentTimeMillis() - t) + " ms");
    }
}

注意:使用bash或zsh通过这种方式创建输入文件:

echo 100000 > /tmp/tst.txt
for i in {1..100000}
do
  echo -n $i" " >> /tmp/tst.txt
done

答案 1 :(得分:0)

我相信这就是您要寻找的。 BufferedReader一次只能读取一行,因此有必要将行分开并将String s强制转换为int s。

BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

try {
    int n = Integer.parseInt(br.readLine());
    int[] arr = new int[n];

    String[] line = br.readLine().split(" ");
    for (int i = 0; i < n; i++) {
        arr[i] = Integer.parseInt(line[i]);
    }
} catch (IOException e) {
    e.getStackTrace();
}

答案 2 :(得分:0)

只是想一想,String.split返回一个String数组。您说输入的值可能约为100,000。因此,为了以这种方式拆分数组,String.split必须遍历每个元素。现在,在将新的字符串数组解析为Integers时,已对集合进行了两次迭代。您可以通过一些小的调整就可以一次迭代完成此操作。

Scanner scanner = new Scanner(System.in);
String tmp = scanner.nextLine();
scanner = new Scanner(tmp); 

for(int i = 0; scanner.hasNextInt(); i++) {
  arr[i] = scanner.nextInt();
}

将扫描程序链接到String而不是将其保留在System.in上的原因是,它可以正确结束。它不会打开System.in来供用户输入最后一个令牌。我相信大写O表示法是原始片段为O(2n)的O(n)和O(2n)之间的区别

答案 3 :(得分:0)

我不太确定为什么OP必须在这里使用Integer.parseInt(s),因为Scanner可以直接通过new Scanner(File source)进行解析。

以下是此想法的演示/测试:

public class NextInt {
    public static void main(String... args) {
        prepareInputFile(1000, 500); // create 1_000 arrays which each contains 500 numbers;
        Timer.timer(() -> readFromFile(), 20, "NextInt"); // read from the file 20 times using Scanner.nextInt();
        Timer.timer(() -> readTest(), 20, "Split"); // read from the file 20 times using split() and Integer.parseInt();
    }

    private static void readTest() {
        Path inputPath = Paths.get(Paths.get("").toAbsolutePath().toString().concat("/src/main/java/io/input.txt"));
        try (Scanner scanner = new Scanner(new File(inputPath.toString()))) {
            int n = Integer.valueOf(scanner.nextLine());
            int[] iVau = new int[n];
            String[] temp = scanner.nextLine().split(" ");
            for (int i = 0; i < n; i++) {
                iVau[i] = Integer.parseInt(temp[i]);
            }
        } catch (IOException ignored) {
            ignored.printStackTrace();
        }
    }

    private static void readFromFile() {
        Path inputPath = Paths.get(Paths.get("").toAbsolutePath().toString().concat("/src/main/java/io/input.txt"));
        try (Scanner scanner = new Scanner(new File(inputPath.toString()))) {
            while (scanner.hasNextInt()) {
                int arrSize = scanner.nextInt();
                int[] arr = new int[arrSize];
                for (int i = 0; i < arrSize; ++i) {
                    arr[i] = scanner.nextInt();
                }
//                System.out.println(Arrays.toString(arr));
            }
        } catch (IOException ignored) {
            ignored.printStackTrace();
        }
    }

    private static void prepareInputFile(int arrCount, int arrSize) {
        Path outputPath = Paths.get(Paths.get("").toAbsolutePath().toString().concat("/src/main/java/io/input.txt"));
        List<String> lines = new ArrayList<>();
        for (int i = 0; i < arrCount; ++i) {
            int[] arr = new int[arrSize];
            for (int j = 0; j < arrSize; ++j) {
                arr[j] = new Random().nextInt();
            }
            lines.add(String.valueOf(arrSize));
            lines.add(Arrays.stream(arr).mapToObj(String::valueOf).collect(Collectors.joining(" ")));
        }
        try {
            Files.write(outputPath, lines);
        } catch (IOException ignored) {
            ignored.printStackTrace();
        }
    }
}

使用1_000数组对它进行本地测试,而每个数组都有500个数字,读取所有元素的成本约为:使用340ms的{​​{1}},而OP的方法{{1} }。

Scanner.nextInt()

因此,我真的怀疑问题在于输入内容中。

答案 4 :(得分:0)

因为在您的情况下,您知道元素的总数,所以您要做的就是从第二行读取X个整数。这是一个示例:

public static void main(String[] args) {
        Scanner in = new Scanner(System.in);

        int count = in.nextInt();
        int array[] = new int[count];

        for (int i = 0; i < count; i++) {
            array[i] = in.nextInt();
        }
}

如果这不够快,我怀疑,那么您可以按以下方式切换到BufferedReader的使用:

public static void main(String[] args) throws IOException {
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

        int count = Integer.parseInt(in.readLine());
        int array[] = new int[count];

        for (int i = 0; i < count; i++) {
            int nextInteger = 0;
            int nextChar = in.read();
            do {
                nextInteger = nextInteger * 10 + (nextChar - '0');
                nextChar = in.read();
            } while (nextChar != -1 && nextChar != (int)' ');
            array[i] = nextInteger;
        }
}

在您的情况下,输入将无效,因此这意味着每个整数将由单个空格分隔,并且输入将以EoF字符结尾。

如果两种方法都还不够慢,那么您可以继续查找有关 Java中的整数读取,竞争性编程 的更多文章,例如:https://www.geeksforgeeks.org/fast-io-in-java-in-competitive-programming/

在比赛中我最喜欢的语言仍然是 C :)祝您好运并享受!