逃跑的小刀 · v-for里面再嵌套一个v-for的写法_v ...· 4 月前 · |
千年单身的红金鱼 · python ...· 4 月前 · |
不爱学习的葡萄 · Mysql日期格式化为yyyymmdd_mo ...· 11 月前 · |
飘逸的甜瓜 · NodeJS中Buffer与字符串相互转换时 ...· 1 年前 · |
拉风的椅子 · java string 替换位空 java ...· 1 年前 · |
我刚接触Java,正在读取非常大的文件,需要一些帮助来理解问题并解决它。我们有一些遗留代码,必须进行优化,使其运行properly.The文件大小可以从10mb到10 it。只有当文件大小超过800mb时才会出现问题。
InputStream inFileReader = channelSFtp.get(path); // file reading from ssh.
byte[] localbuffer = new byte[2048];
ByteArrayOutputStream bArrStream = new ByteArrayOutputStream();
int i = 0;
while (-1 != (i = inFileReader.read(buffer))) {
bArrStream.write(localbuffer, 0, i);
byte[] data = bArrStream.toByteArray();
inFileReader.close();
bos.close();
我们得到了错误
java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:2271)
at java.io.ByteArrayOutputStream.grow(ByteArrayOutputStream.java:113)
at java.io.ByteArrayOutputStream.ensureCapacity(ByteArrayOutputStream.java:93)
at java.io.ByteArrayOutputStream.write(ByteArrayOutputStream.java:140)
有什么可以帮上忙的吗?
Java虚拟机(JVM)使用固定的内存上限运行,您可以对其进行如下修改:
java -Xmx1024m ....
例如,上面的选项(-Xmx...)将限制设置为1024 to。您可以根据需要进行修改(在您的机器、操作系统等的限制范围内)请注意,这与传统应用程序不同,传统应用程序会根据需要从操作系统分配越来越多的内存。
但是,更好的解决方案是修改您的应用程序,这样您就不需要加载
整体
将文件一次性存入内存。这样就不需要调优JVM,也不会占用大量内存。
你不能在内存中读取10 in的文本文件。您必须先读取X MB,对其执行某些操作,然后读取下一个X MB。
ByteArrayOutputStream写入内存中的缓冲区。如果这确实是您希望它工作的方式,那么您必须根据输入的最大可能大小来调整JVM堆的大小。此外,如果可能,您甚至可以在开始处理之前检查输入大小,以节省时间和资源。
另一种方法是流解决方案,其中在运行时使用的内存量是已知的(可能是可配置的,但在程序启动之前仍然是已知的),但它是否可行完全取决于应用程序的域(因为您不能再使用内存中的缓冲区),如果您不能/不想更改它,还可能取决于其余代码的体系结构。
尝试使用较大的缓冲区读取大小可能为10MB,然后检查。
这个问题是你正在做的事情所固有的。无论何时何地,将整个文件读入内存都不是一个好主意。除非你有一些相当惊人的硬件,否则你真的无法用当前的技术将一个10 be的文件读入内存。找到一种逐行、逐记录、逐块处理它们的方法……
尝试使用
java.nio.MappedByteBuffer
..。
http://docs.oracle.com/javase/7/docs/api/java/nio/MappedByteBuffer.html
您可以将文件的内容映射到内存,而无需手动复制。高级操作系统提供了内存映射,Java提供了API来利用这一特性。
如果我的理解是正确的,那么内存映射不会将文件的全部内容加载到内存中(意思是“根据需要加载和卸载部分内容”),所以我猜一个10 up的文件不会占用您的内存。
是否必须获得完整的ByteArray()输出流?
byte[] data = bArrStream.toByteArray();
最好的方法是逐行读取&逐行写入。您可以使用
BufferedReader
或者
Scanner
读取大文件,如下所示。
import java.io.*;
import java.util.*;
public class FileReadExample {
public static void main(String args[]) throws FileNotFoundException {
File fileObj = new File(args[0]);
long t1 = System.currentTimeMillis();
try {
// BufferedReader object for reading the file
BufferedReader br = new BufferedReader(new FileReader(fileObj));
// Reading each line of file using BufferedReader class
String str;
while ( (str = br.readLine()) != null) {
System.out.println(str);
}catch(Exception err){
err.printStackTrace();
long t2 = System.currentTimeMillis();
System.out.println("Time taken for BufferedReader:"+(t2-t1));
t1 = System.currentTimeMillis();
try (
// Scanner object for reading the file
Scanner scnr = new Scanner(fileObj);) {
// Reading each line of file using Scanner class
while (scnr.hasNextLine()) {
String strLine = scnr.nextLine();
// print data on console
System.out.println(strLine);
t2 = System.currentTimeMillis();
System.out.println("Time taken for scanner:"+(t2-t1));
}
你可以替换
System.out
使用您的
ByteArrayOutputStream
在上面的例子中。
请看下面的文章了解更多细节:
读取大文件
看看相关的SE问题:
扫描仪与BufferedReader
即使您可以增加JVM内存限制,但这也是不必要的,而且分配10 it这样的巨大内存来处理文件听起来有些夸张,而且会占用大量资源。
当前您使用的是一个"ByteArrayOutputStream“,它保留一个内部存储器来保存数据。代码中的这一行将最后读取的2KB文件块附加到此缓冲区的末尾:
bArrStream.write(localbuffer, 0, i);
bArrStream不断增长,最终会耗尽内存。
相反,你应该重新组织你的算法,并以流的方式处理文件:
InputStream inFileReader = channelSFtp.get(path); // file reading from ssh.
byte[] localbuffer = new byte[2048];
int i = 0;
while (-1 != (i = inFileReader.read(buffer))) {
//Deal with the current read 2KB file chunk here
inFileReader.close();
您好,我假设您正在读取大的txt文件,并且数据是逐行设置的,请使用逐行读取的方法。据我所知,你可以阅读高达6 6GB可能更多。我强烈建议您尝试这种方法。
DATA1 DATA2 ..。
// Open the file
FileInputStream fstream = new FileInputStream("textfile.txt");
BufferedReader br = new BufferedReader(new InputStreamReader(fstream));
String strLine;
//Read File Line By Line
while ((strLine = br.readLine()) != null) {
// Print the content on the console
System.out.println (strLine);
//Close the input stream
br.close();
代码片段的引用
以迭代方式逐行读取该文件。这将显著减少内存消耗。或者,您可以使用
FileUtils.lineIterator(theFile,"UTF-8");
由Apache Commons IO提供。
FileInputStream inputStream = null;
Scanner sc = null;
try {
inputStream = new FileInputStream(path);
sc = new Scanner(inputStream, "UTF-8");
while (sc.hasNextLine()) {
String line = sc.nextLine();
// System.out.println(line);
// note that Scanner suppresses exceptions
if (sc.ioException() != null) {
throw sc.ioException();
} finally {
if (inputStream != null) {
inputStream.close();
if (sc != null) {
sc.close();
}
}
使用命令行选项-Xmx运行Java,该选项设置堆的最大大小。
有关详细信息,请参阅此处。
您应该增加堆大小,如以下答案所述:
增加Java中的堆大小
但请记住,Java运行时和您的代码也会占用一些空间,因此请在所需的最大值上添加一些缓冲区。
简而言之,
不采取任何措施,您可以将电流限制提高1.5倍。这意味着,如果您能够处理800MB,则可以处理1200MB。这也意味着如果通过一些技巧java -Xm ....
您可以移动到当前代码可以处理7 7GB的点,您的问题就解决了,因为1.5倍将使您达到10.5 7GB,假设您的系统上有可用的空间,并且JVM可以获得该空间。
答案很长:
这个错误是不言而喻的。您达到了配置的实际内存限制。关于JVM的限制有很多猜测,我对此了解不多,因为我找不到任何官方信息。但是,您会受到诸如可用交换空间、内核地址空间使用、内存碎片等约束的限制。
现在发生的情况是ByteArrayOutputStream
如果不提供任何大小(这就是您的情况),则使用大小为32的默认缓冲区创建对象。每当您调用write方法,则会启动一个内部机制。The The The
openjdk实现版本7u40-b43
这似乎与您的错误的输出完全匹配,使用内部方法ensureCapacity
检查缓冲区是否有足够的空间来放置要写入的字节。如果没有足够的空间,则使用另一个内部方法grow以增大缓冲区的大小。方法grow
定义适当的大小并调用该方法copyOf
来自班级Arrays
来完成这项工作。缓冲区的适当大小是当前大小和保存所有内容(当前内容和要写入的新内容)所需的大小之间的最大值。方法copyOf
来自班级Arrays
(点击链接)为新缓冲区分配空间,将旧缓冲区的内容复制到新缓冲区并将其返回到
grow..。
您的问题发生在为新缓冲区分配空间时,write
,则会出现可用内存耗尽的情况:
java.lang.OutOfMemoryError: Java heap space..。
如果我们看一下细节,你会读到2048年的大块。所以
第一次写入时,缓冲区的大小将从32增加到2048
您的第二个调用将加倍到2
*
2048
您的第三次调用将使其达到2^2
*
2048,你必须在需要分配之前再写两次。
然后是2^3
*
2048,在再次分配之前,您将有时间进行4个更多的写入。
在某些情况下,您的缓冲区大小将为2^18
*
2048,等于2^19
*
1024或2^9
*
2^20 (512 MB)
然后是2^19
*
2048,即1024 MB或1 GB
在你的描述中不清楚的是,你可以以某种方式读到800MB,但不能超过800MB。你得跟我解释一下。
我希望你的限制恰好是2的幂(如果我们使用10个单位的幂,那就接近了)。在这一点上,我希望你马上就会遇到问题: 256MB,512MB,1 2GB,2 2GB,等等。
当达到该限制时,并不意味着内存不足,而只是意味着不可能再分配两倍于已有缓冲区大小的缓冲区。这一观察为您的工作提供了改进的空间:找到您可以分配的最大缓冲区大小,并通过调用适当的构造函数预先预留它
ByteArrayOutputStream bArrStream = new ByteArrayOutputStream(myMaxSize);
它的优点是减少了幕后内存分配的开销,让您满意。通过这样做,您将能够达到您现在拥有的1.5限制。这很简单,因为上一次增加缓冲区时,它从当前大小的一半增加到当前大小,并且在某些时候,您在内存中同时拥有当前缓冲区和旧缓冲区。但是你将不能超过你现在所拥有的3倍的限制。解释是完全相同的。
也就是说,我没有任何神奇的建议来解决这个问题,除了通过给定大小的块来处理数据之外,一次处理一个块。另一个好的方法是使用Takahiko Kawasaki的建议,并使用MappedByteBuffer
..。请记住,在任何情况下,您都需要至少10 GB的物理内存或交换内存才能加载10 GB的文件。
请参见
经过思考,我决定给出第二个答案。我考虑了第二个答案的优点和缺点,而且优点是值得去尝试的。所以就是这样。
大多数建议的考虑事项都忽略了一个给定的事实:数组的大小有一个内置限制(包括
ByteArrayOutputStream),您可以在Java中使用。而这一限制是由最大的int
值为2^31 -1(略小于2Giga)。这意味着您最多只能读取2 GB (-1字节),并将其放入一个ByteArrayOutputStream..。如果VM需要更多的控制,则数组大小的限制实际上可能会更小。
我的建议是使用ArrayList的byte[]
而不是单个byte[]
保存文件的全部内容。也去掉了不必要的输入步骤ByteArrayOutputStream
在把它放进决赛之前data
数组。以下是基于您的原始代码的示例:
InputStream inFileReader = channelSFtp.get(path); // file reading from ssh.
// good habits are good, define a buffer size
final int BUF_SIZE = (int)(Math.pow(2,30)); //1GB, let's not go close to the limit