- iOS/Objective-C 元类和类别
- objective-c - -1001 错误,当 NSURLSession 通过 httpproxy 和/etc/hosts
- java - 使用网络类获取 url 地址
- ios - 推送通知中不播放声音
关于如何在 Java 中将包含 double 字的 ASCII 文件解析为 double 组,我发现了很多不同的建议。我目前使用的大致如下:
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 {...}
对于包含 100 万个数字的示例文件,此代码大约需要 2 秒。用 C 编写的类似代码,使用 fscanf
,需要 0.1 秒(!)显然我完全错了。我想调用 nextDouble()
太多次是错误的方法,因为开销很大,但我想不出更好的方法。
我不是 Java 专家,因此我需要一点帮助:你能告诉我如何改进这段代码吗?
编辑 对应的C代码如下
fd = fopen(fname, "r+");
vals = calloc(sizeof(double), size);
do{
nel = fscanf(fd, "%lf", vals+idx);
idx++;
} while(nel!=-1);
最佳答案
(总结了一些我已经在评论中提到的东西:)
您应该小心使用手动基准测试。问题的答案How do I write a correct micro-benchmark in Java?指出了一些基本的注意事项。然而,这种情况不太容易出现经典陷阱。事实上,情况可能恰恰相反:当基准测试完全包括读取文件时,那么您很可能不是在对代码进行基准测试,而是主要对硬盘进行基准测试。这涉及缓存的常见副作用。
但是,显然存在超出纯文件 IO 的开销。
您应该知道 Scanner
类非常强大和方便。但在内部,它是一个由大型正则表达式组成的野兽,对用户隐藏了巨大的复杂性——当您的意图只是读取 double
值时,这种复杂性根本没有必要!
有一些开销较少的解决方案。
不幸的是,最简单的解决方案仅适用于输入中的数字由行分隔符分隔的情况。然后,将这个文件读入一个数组可以写成
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
值的一般问题,用空格分隔(但不一定用换行符分隔),我写了一个小测试。
这不应该被视为一个真正的基准,并持保留态度,但它至少试图解决一些基本问题:它读取不同大小、多次、不同方法的文件,因此对于后面的运行,硬盘缓存的效果对于所有方法应该是一样的:
已更新以生成评论中所述的示例数据,并添加了基于流的方法
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();
}
}
它比较了 4 种方法:
Scanner
阅读,就像在您的原始代码片段中一样StreamTokenizer
读取 String
,并用StringTokenizer
对其进行分解Stream
,然后将其平面映射到标记的 Stream
,然后再映射到 DoubleStream
将文件作为一个大的 String
读取可能并不适合所有情况:当文件变得(很大)大时,将整个文件作为一个 String
保存在内存中> 可能不是一个可行的解决方案。
测试运行(在一台相当旧的 PC 上,硬盘驱动器较慢(无固态))大致显示了这些结果:
...
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 ...
显然,扫描器强加了相当大的开销,当更直接地从流中读取时可以避免这种开销。
这可能不是最终答案,因为可能有更高效和/或更优雅的解决方案(我期待看到它们!),但也许它至少有帮助。
EDIT
一个小评论:一般来说,这些方法之间存在一定的概念差异。粗略地说,区别在于谁决定读取的元素数量。在伪代码中,这个区别是
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;
}
您使用扫描仪的原始方法写成第一个,而我提出的解决方案与第二个更相似。但这在这里应该没有太大区别,假设 size
确实是 real 大小,并且潜在的错误(例如输入中的数字太少或太多)不会'出现或以其他方式处理。
关于java - 在java中快速解析数字字符串,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33064276/
fiddle :http://jsfiddle.net/rtucgv74/ 我正在尝试将第一个字符与 3 位数字匹配。所以下面的代码应该提醒f234。但反而返回 null ? 源代码: var reg
复制代码 代码如下: Dim strOk,strNo strOk = "12312321$12
我想找 {a number} / { a number } / {a string}模式。我可以得到number / number工作,但是当我添加 / string它不是。 我试图找到的例子: 15
我,我正在做一个模式正则表达式来检查字符串是否是: 数字.数字.数字,如下所示: 1.1.1 0.20.2 58.55541.5221 在java中我使用这个: private static Patt
我有一个字符串,我需要检查它是否在字符串的末尾包含一个数字/数字,并且需要将该数字/数字递增到字符串末尾 +1 我会得到下面的字符串 string2 = suppose_name_1 string3
我正在寻找一个正则表达式 (数字/数字),如(1/2) 数字必须是 1-3 位数字。我使用 Java。 我认为我的问题比正则表达式更深。我无法让这个工作 String s ="(1/15)";
谁能帮我理解为什么我在使用以下代码时会出现类型错误: function sumOfTwoNumbersInArray(a: [number, number]) { return a[0] +
我看到有些人过去也遇到过类似的问题,但他们似乎只是不同,所以解决方案也有所不同。所以这里是: 我正在尝试在 Google Apps 脚本中返回工作表的已知尺寸范围,如下所示: var myRange
我试图了解python中的正则表达式模块。我试图让我的程序从用户输入的一行文本中匹配以下模式: 8-13 之间的数字“/” 0-15 之间的数字 例如:8/2、11/13、10/9 等。 我想出的模式
简单地说,我当前正在开发的程序要求我拆分扫描仪输入(例如:2 个火腿和奶酪 5.5)。它应该读取杂货订单并将其分成三个数组。我应该使用 string.split 并能够将此输入分成三部分,而不管中间字
(number) & (-number) 是什么意思?我已经搜索过了,但无法找到含义 我想在 for 循环中使用 i & (-i),例如: for (i = 0; i 110000 .对于i没有高于
需要将图像ID设置为数字 var number = $(this).attr('rel'); number = parseInt(number); $('#carousel .slid
我有一个函数,我想确保它接受一个字符串,后跟一个数字。并且可选地,更多的字符串数字对。就像一个元组,但“无限”次: const fn = (...args: [string, number] | [s
我想复制“可用”输入数字的更改并将其添加或减去到“总计”中 如果此人将“可用”更改为“3”,则“总计”将变为“9”。 如果用户将“可用”更改为“5”,则“总计”将变为“11”。 $('#id1').b
我有一个与 R 中的断线相关的简单问题。 我正在尝试粘贴,但在获取(字符/数字)之间的断线时遇到问题。请注意,这些值包含在向量中(V1=81,V2=55,V3=25)我已经尝试过这段代码: cat(p
很难说出这里问的是什么。这个问题是含糊的、模糊的、不完整的、过于宽泛的或修辞性的,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开它,visit the help center 。 已关
我在 Typescript 中收到以下错误: Argument of type 'number[]' is not assignable to parameter of type 'number' 我
在本教程中,您将通过示例了解JavaScript 数字。 在JavaScript中,数字是基本数据类型。例如, const a = 3; const b = 3.13; 与其他一些编程语言不同
我在 MDN Reintroduction to JavaScript 上阅读JavaScript 数字只是浮点精度类型,JavaScript 中没有整数。然而 JavaScript 有两个函数,pa
我们在 Excel 中管理库存。我知道这有点过时,但我们正在发展商业公司,我们所有的钱都被困在业务上,没有钱投资 IT。 所以我想知道我可以用Excel自动完成产品编号的方式进行编程吗? 这是一个产品
我是一名优秀的程序员,十分优秀!