【问题标题】:java OutOfMemoryError about FileOutputStream?关于 FileOutputStream 的 java OutOfMemoryError?
【发布时间】:2017-05-18 10:53:18
【问题描述】:

谢谢大家^_^,问题解决了:有一行太大(超过400M...我下载了一个损坏的文件,我没想到),所以抛出一个OutOfMemoryError

我想用java拆分文件,但总是抛出OutOfMemoryError: Java heap space,我在整个互联网上搜索,但似乎没有帮助:(

ps。文件大小为600M,超过30,000,000行,每行不超过100个字符。 (也许你可以像这样生成一个“级别文件”:{ id:0000000001,级别:1 id:0000000002,级别:2 ....(超过 3000 万) })

pss。将JVM内存大小设置得更大是行不通的,:(

psss。换了电脑,问题依旧/(ㄒoㄒ)/~~

无论我设置多大的-Xms或-Xmx,输出文件的大小总是一样的,(而且Runtime.getRuntime().totalMemory()确实改变了)

这是堆栈跟踪:

 Heap Size = 2058027008
    Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
        at java.util.Arrays.copyOf(Arrays.java:2882)
        at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:100)
        at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:515)
        at java.lang.StringBuffer.append(StringBuffer.java:306)
        at java.io.BufferedReader.readLine(BufferedReader.java:345)
        at java.io.BufferedReader.readLine(BufferedReader.java:362)
        at com.xiaomi.vip.tools.ptupdate.updator.Spilt.main(Spilt.java:39)
    ...

这是我的代码:

package com.updator;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;

public class Spilt {
    public static void main(String[] args) throws Exception {
        long heapSize = Runtime.getRuntime().totalMemory();

        // Print the jvm heap size.
        System.out.println("Heap Size = " + heapSize);

        String mainPath = "/home/work/bingo/";
        File mainFilePath = new File(mainPath);
        FileInputStream inputStream = null;
        FileOutputStream outputStream = null;
        try {
            if (!mainFilePath.exists())
                mainFilePath.mkdir();

            String sourcePath = "/home/work/bingo/level.txt";
            inputStream = new FileInputStream(sourcePath);
            BufferedReader bufferedReader = new BufferedReader(new FileReader(
                    new File(sourcePath)));

            String savePath = mainPath + "tmp/";
            Integer i = 0;
            File file = new File(savePath + "part"
                    + String.format("%0" + 5 + "d", i) + ".txt");
            if (!file.getParentFile().exists())
                file.getParentFile().mkdir();
            file.createNewFile();
            outputStream = new FileOutputStream(file);
            int count = 0, total = 0;
            String line = null;
            while ((line = bufferedReader.readLine()) != null) {
                line += '\n';
                outputStream.write(line.getBytes("UTF-8"));
                count++;
                total++;
                if (count > 4000000) {
                    outputStream.flush();
                    outputStream.close();
                    System.gc();
                    count = 0;
                    i++;
                    file = new File(savePath + "part"
                            + String.format("%0" + 5 + "d", i) + ".txt");
                    file.createNewFile();
                    outputStream = new FileOutputStream(file);
                }
            }

            outputStream.close();
            file = new File(mainFilePath + "_SUCCESS");
            file.createNewFile();
            outputStream = new FileOutputStream(file);
            outputStream.write(i.toString().getBytes("UTF-8"));
        } finally {
            if (inputStream != null)
                inputStream.close();
            if (outputStream != null)
                outputStream.close();
        }
    }
}

我想可能是:当 outputStream.close() 时,内存没有释放?

【问题讨论】:

  • 显示异常和堆栈跟踪
  • 查看以下已被其他人回答的链接-stackoverflow.com/questions/11578123/…
  • 你为什么使用Scanner?您不需要该功能,BufferedReader 就足够了,而且资源消耗更少。
  • @FlameHaze,我试过了,但是无论我设置多大的-Xms或-Xmx,输出文件的大小总是一样的,(Runtime.getRuntime().totalMemory()是真的改变了)
  • 堆栈很清楚:bufferedReader.readLine 抛出内存不足。要查找的最直接原因是:有一行不适合内存。 (您可以通过 System.out.println 行数来查看是哪一个)。

标签: java out-of-memory fileoutputstream


【解决方案1】:

因此,您打开原始文件并创建一个BufferedReader 和一个计数器。

char[] buffer = new char[5120];
BufferedReader reader = Files.newBufferedReader(Paths.get(sourcePath), StandardCharsets.UTF_8);
int lineCount = 0;

现在您读入缓冲区,并在字符进入时写入它们。

int read;

BufferedWriter writer = Files.newBufferedWriter(Paths.get(fileName), StandardCharsets.UTF_8);
while((read = reader.read(buffer, 0, 5120))>0){
    int offset = 0;
    for(int i = 0; i<read; i++){
        char c = buffer[i];
        if(c=='\n'){
           lineCount++;
           if(lineCount==maxLineCount){
              //write the range from 0 to i to your old writer.
              writer.write(buffer, offset, i-offset);
              writer.close();
              offset=i;
              lineCount=0;
              writer = Files.newBufferedWriter(Paths.get(newName), StandarCharset.UTF_8);
           }
        }
        writer.write(buffer, offset, read-offset);
    }
    writer.close();
}

这应该会降低内存使用率,并防止您一次读取太大的行。您可以不使用 BufferedWriters 并进一步控制内存,但我认为没有必要。

【讨论】:

  • 为什么是 5120?为什么要立即阅读 5120...?我的意思是如果一条线只有100长,那不应该更糟吗?
  • 5120是缓冲区大小,我随便挑的。由于正在使用缓冲读取器,因此没关系,一次只读取一个字符甚至可以正常工作。为什么你认为它对于 100 长的线会表现得更差?
【解决方案2】:

我已经用大文本文件进行了测试。(250Mb)

效果很好。

您需要为文件流添加try catch异常代码。

public class MyTest {
    public static void main(String[] args) {
        String mainPath = "/home/work/bingo/";
        File mainFilePath = new File(mainPath);
        FileInputStream inputStream = null;
        FileOutputStream outputStream = null;
        try {
            if (!mainFilePath.exists())
                mainFilePath.mkdir();

            String sourcePath = "/home/work/bingo/level.txt";
            inputStream = new FileInputStream(sourcePath);
            Scanner scanner = new Scanner(inputStream, "UTF-8");

            String savePath = mainPath + "tmp/";
            Integer i = 0;
            File file = new File(savePath + "part" + String.format("%0" + 5 + "d", i) + ".txt");
            if (!file.getParentFile().exists())
                file.getParentFile().mkdir();
            file.createNewFile();
            outputStream = new FileOutputStream(file);
            int count = 0, total = 0;

            while (scanner.hasNextLine()) {
                String line = scanner.nextLine() + "\n";
                outputStream.write(line.getBytes("UTF-8"));
                count++;
                total++;
                if (count > 4000000) {
                    outputStream.flush();
                    outputStream.close();
                    count = 0;
                    i++;
                    file = new File(savePath + "part" + String.format("%0" + 5 + "d", i) + ".txt");
                    file.createNewFile();
                    outputStream = new FileOutputStream(file);
                }
            }

            outputStream.close();
            file = new File(mainFilePath + "_SUCCESS");
            file.createNewFile();
            outputStream = new FileOutputStream(file);
            outputStream.write(i.toString().getBytes("UTF-8"));
        } catch (FileNotFoundException e) {
            System.out.println("ERROR: FileNotFoundException :: " + e.getStackTrace());
        } catch (IOException e) {
            System.out.println("ERROR: IOException :: " + e.getStackTrace());
        } finally {
            if (inputStream != null)
                try {
                    inputStream.close();
                    if (outputStream != null)
                        outputStream.close();

                } catch (IOException e) {
                    e.printStackTrace();
                }
        }
    }
}

如果问题仍然存在,请在 shell 提示符下使用以下命令更改 java 堆内存大小。

例如) Xmx1g : 1Gb 堆内存大小, MyTest : 类名

java -Xmx1g 我的测试

【讨论】:

  • 我再次尝试使用 2Gb 文本文件。但是,没有问题。我的系统环境:Intel i5 / Java 1.7 / 6Gb 内存。
  • 如果您的系统内存非常小,请减少行数。例如。 4000000 至 400
  • 我试过了,问题依旧:(,ps.我的环境:i7/Java1.6/16GB内存,输出文件总大小也一样
  • ,非常感谢...但是换堆内存不行.... /(ㄒoㄒ)/~~
猜你喜欢
  • 2013-03-01
  • 2015-03-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-24
  • 2011-01-28
  • 1970-01-01
  • 2012-04-28
相关资源
最近更新 更多