Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/322.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
java中数字字符串的快速解析_Java_Arrays_Performance_Parsing - Fatal编程技术网

java中数字字符串的快速解析

java中数字字符串的快速解析,java,arrays,performance,parsing,Java,Arrays,Performance,Parsing,对于如何在Java中将包含双精度数字的ASCII文件解析为双精度数字数组,我发现了很多不同的建议。我目前使用的大致如下: stream = FileInputStream(fname); breader = BufferedReader(InputStreamReader(stream)); scanner = java.util.Scanner(breader); array = new double[size]; // size is known upfront idx = 0; t

对于如何在Java中将包含双精度数字的ASCII文件解析为双精度数字数组,我发现了很多不同的建议。我目前使用的大致如下:

stream = FileInputStream(fname);
breader = BufferedReader(InputStreamReader(stream));
scanner = java.util.Scanner(breader);    
array = new double[size]; // size is known upfront
idx = 0;
try {
        while(idx<size){
           array[idx] = scanner.nextDouble();
           idx++;
        }
}
catch {...}
(总结我在评论中已经提到的一些事情:)

您应该小心使用手动基准测试。这个问题的答案指出了一些基本的警告。然而,这种情况并不容易出现经典陷阱。事实上,情况可能恰恰相反:当基准测试仅包含读取文件时,您很可能不是在对代码进行基准测试,而主要是对硬盘进行基准测试。这涉及到缓存的常见副作用

然而,显然,除了纯文件IO之外,还有一个开销

您应该知道,
Scanner
类功能强大且非常方便。但在内部,它是一个由大型正则表达式组成的庞然大物,并且对用户隐藏了巨大的复杂性——当您打算只读取
双值时,这种复杂性根本不需要

有一些开销较小的解决方案

不幸的是,最简单的解决方案仅适用于输入中的数字由行分隔符分隔的情况。然后,将该文件读入数组可以写成

double result[] = 
    Files.lines(Paths.get(fileName))
        .mapToDouble(Double::parseDouble)
        .toArray();
这甚至可能相当快。如果一行中有多个数字(如您在注释中所述),则可以扩展:

double result[] = 
    Files.lines(Paths.get(fileName))
        .flatMap(s -> Stream.of(s.split("\\s+")))
        .mapToDouble(Double::parseDouble)
        .toArray();

因此,关于如何有效地从文件中读取一组由空格分隔(但不一定由换行分隔)的
double
值的一般问题,我编写了一个小测试

这不应被视为一个真正的基准,也不应对此持保留态度,但它至少试图解决一些基本问题:它使用不同的方法多次读取不同大小的文件,因此在以后的运行中,硬盘缓存对所有方法的效果都应相同:

更新以生成注释中所述的样本数据,并添加了基于流的方法

显然,扫描器施加了相当大的开销,这在更直接地从流中读取时可以避免

这可能不是最终的答案,因为可能会有更高效和/或更优雅的解决方案(我期待着看到它们!),但可能至少会有所帮助


编辑

一点小小的评论:一般来说,这两种方法在概念上存在一定的差异。粗略地说,区别在于谁决定读取的元素数量。在伪代码中,这种差异是

double array[] = new double[size];
for (int i=0; i<size; i++) 
{
    array[i] = readDoubleFromInput();
}

您最初使用扫描仪的方法与第一种类似,而我提出的解决方案与第二种更为相似。但是,假设
大小确实是实际大小,并且潜在的错误(例如输入中的数字太少或太多)不会出现或以其他方式处理,那么这不会造成很大的差异。

您能告诉我们“类似的代码是用C编写的”吗?您是否分析了代码以检查哪些部分是性能瓶颈?@DraganBozanovic不,我没有。我没有太多的Java经验,我使用emacs来编程。这是一段非常小的代码,我想瓶颈应该很容易找到。。它确实位于while循环中:),我不怀疑它来自
数组[idx]
和内存分配。在您调用的库中有很多代码。尽管如此,我还是建议您尝试使用分析器。另外,请记住Java具有JIT,这可能会在优化代码时在开始时消耗一些时间。例如,您是否可以尝试将同一段代码执行100次,并检查最后几次迭代的执行时间?10次迭代需要10倍以上的时间。时间都花在循环中了。非常感谢你,Marco13:)这看起来真的很好!我将使用它,看看它如何在我的数据上工作,但最后一个解决方案似乎给出了“C”结果。我在想,你能不能修改它,以便在有限大小的缓冲区上使用StringTokenizer,并将其放入循环中?通过这种方式,您可以在更大的缓冲区上工作,并且仍然使用StringTokenizer。无论如何,我确信我做错了,再次感谢你的帮助:-)@angainor肯定有混合解决方案,比如在“块”(而不是一个大的、单一的缓冲区)中读取文件,然后处理这些块。更详细的分析可能是值得的,但它们可能会变得更复杂-例如,必须确保数据块不会分割数字,如
0.123 0.234 0.3
(cut)
45 0.456
-这当然可以处理,但可能有点麻烦。顺便说一句:我会在几分钟后添加一个小的(更一般的)评论作为编辑…@Marco13:只是好奇而已。我们可以直接将流标记为双数,而不是来回将字符串解析为双数吗?@dragon66理论上,我们可以读取每个字符,并像在
FloatingDecimal.readJavaFormatString
中一样处理它,使用
字符串的字符。然而,一个字符一个字符地读取流通常不是一个好主意:通常应该进行一些缓冲(即,至少应该将一定数量的字符读入
char[]
),这将非常接近
字符串。很难预测任何性能优势,但我的直觉是,努力与性能增益的比率将相当糟糕…@Marco13:我的意思是,在您的代码中,您使用case StreamTokenizer.TT_WORD作为字符串获取下一个令牌。为什么不使用case StreamTokenizer.TT_编号?
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.StreamTokenizer;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Locale;
import java.util.Random;
import java.util.Scanner;
import java.util.StringTokenizer;
import java.util.stream.Stream;

public class ReadingFileWithDoubles
{
    private static final int MIN_SIZE = 256000;
    private static final int MAX_SIZE = 2048000;

    public static void main(String[] args) throws IOException
    {
        generateFiles();

        long before = 0;
        long after = 0;
        double result[] = null;

        for (int n=MIN_SIZE; n<=MAX_SIZE; n*=2)
        {
            String fileName = "doubles"+n+".txt";

            for (int i=0; i<10; i++)
            {
                before = System.nanoTime();
                result = readWithScanner(fileName, n);
                after = System.nanoTime();

                System.out.println(
                    "size = " + n + 
                    ", readWithScanner                  " + 
                        (after - before) / 1e6 + 
                    ", result " + result);

                before = System.nanoTime();
                result = readWithStreamTokenizer(fileName, n);
                after = System.nanoTime();

                System.out.println(
                    "size = " + n + 
                    ", readWithStreamTokenizer          " + 
                        (after - before) / 1e6 +
                    ", result " + result);

                before = System.nanoTime();
                result = readWithBufferAndStringTokenizer(fileName, n);
                after = System.nanoTime();

                System.out.println(
                    "size = " + n + 
                    ", readWithBufferAndStringTokenizer " + 
                        (after - before) / 1e6 + 
                    ", result " + result);

                before = System.nanoTime();
                result = readWithStream(fileName, n);
                after = System.nanoTime();

                System.out.println(
                    "size = " + n + 
                    ", readWithStream                   " + 
                        (after - before) / 1e6 + 
                    ", result " + result);
            }
        }

    }



    private static double[] readWithScanner(
        String fileName, int size) throws IOException
    {
        try (
            InputStream is = new FileInputStream(fileName);
            InputStreamReader isr = new InputStreamReader(is);
            BufferedReader br = new BufferedReader(isr);
            Scanner scanner = new Scanner(br))
        {
            // Do this to avoid surprises on systems with a different locale!
            scanner.useLocale(Locale.ENGLISH);

            int idx = 0;
            double array[] = new double[size];
            while (idx < size)
            {
                array[idx] = scanner.nextDouble();
                idx++;
            }
            return array;
        }
    }

    private static double[] readWithStreamTokenizer(
        String fileName, int size) throws IOException
    {
        try (
            InputStream is = new FileInputStream(fileName);
            InputStreamReader isr = new InputStreamReader(is);
            BufferedReader br = new BufferedReader(isr))
        {
            StreamTokenizer st = new StreamTokenizer(br);            
            st.resetSyntax();
            st.wordChars('0', '9');
            st.wordChars('.', '.');
            st.wordChars('-', '-');
            st.wordChars('e', 'e');
            st.wordChars('E', 'E');
            double array[] = new double[size];
            int index = 0;
            boolean eof = false;
            do
            {
                int token = st.nextToken();
                switch (token)
                {
                    case StreamTokenizer.TT_EOF:
                        eof = true;
                        break;

                    case StreamTokenizer.TT_WORD:
                        double d = Double.parseDouble(st.sval);
                        array[index++] = d;
                        break;
                }
            } while (!eof);
            return array;
        }
    }

    // This one is reading the whole file into memory, as a String,
    // which may not be appropriate for large files
    private static double[] readWithBufferAndStringTokenizer(
        String fileName, int size) throws IOException
    {
        double array[] = new double[size];
        try (
            InputStream is = new FileInputStream(fileName);
            InputStreamReader isr = new InputStreamReader(is);
            BufferedReader br = new BufferedReader(isr))
        {
            StringBuilder sb = new StringBuilder();
            char buffer[] = new char[1024];
            while (true)
            {
                int n = br.read(buffer);
                if (n == -1)
                {
                    break;
                }
                sb.append(buffer, 0, n);
            }
            int index = 0;
            StringTokenizer st = new StringTokenizer(sb.toString());
            while (st.hasMoreTokens())
            {
                array[index++] = Double.parseDouble(st.nextToken());
            }
            return array;
        }
    }

    private static double[] readWithStream(
        String fileName, int size) throws IOException
    {
        double result[] = 
            Files.lines(Paths.get(fileName))
                .flatMap(s -> Stream.of(s.split("\\s+")))
                .mapToDouble(Double::parseDouble)
                .toArray();
        return result;
    }


    private static void generateFiles() throws IOException 
    {
        for (int n=MIN_SIZE; n<=MAX_SIZE; n*=2)
        {
            String fileName = "doubles"+n+".txt";
            if (!new File(fileName).exists())
            {
                System.out.println("Creating "+fileName);
                writeDoubles(new FileOutputStream(fileName), n);
            }
            else
            {
                System.out.println("File "+fileName+" already exists");
            }
        }
    }
    private static void writeDoubles(OutputStream os, int n) throws IOException
    {
        OutputStreamWriter writer = new OutputStreamWriter(os);
        Random random = new Random(0);
        int numbersPerLine = random.nextInt(4) + 1;
        for (int i=0; i<n; i++)
        {
            writer.write(String.valueOf(random.nextDouble()));
            numbersPerLine--;
            if (numbersPerLine == 0)
            {
                writer.write("\n");
                numbersPerLine = random.nextInt(4) + 1;
            }
            else
            {
                writer.write(" ");
            }
        }
        writer.close();
    }
}
...
size = 1024000, readWithScanner                  9932.940919, result [D@1c7353a
size = 1024000, readWithStreamTokenizer          1187.051427, result [D@1a9515
size = 1024000, readWithBufferAndStringTokenizer 1172.235019, result [D@f49f1c
size = 1024000, readWithStream                   2197.785473, result [D@1469ea2    ...
double array[] = new double[size];
for (int i=0; i<size; i++) 
{
    array[i] = readDoubleFromInput();
}
double array[] = new double[size];
int index = 0;
while (thereAreStillNumbersInTheInput())
{
    double d = readDoubleFromInput();
    array[index++] = d;
}