在处理较大文件时出现 java.lang.OutOfMemoryError 问题。

huangapple go评论78阅读模式
英文:

Getting java.lang.OutOfMemoryError on larger files

问题

我试图将一个大文件发送到我的服务器,但出现了错误:java.lang.OutOfMemoryError: 分配268435468字节内存失败,有33554432字节空闲内存和100MB内存直到OOM。虽然如果我尝试使用小文件,则一切正常。

这是我的代码:

public static byte[] getBytes(InputStream inputStream) throws IOException {
        ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
        int bufferSize = 1024;
        byte[] buffer = new byte[bufferSize];

        int len = 0;
        while ((len = inputStream.read(buffer)) != -1) {
            byteBuffer.write(buffer, 0, len);
        }

        return byteBuffer.toByteArray();
    }

上传函数

public static void uploadPDF(final String pdfname, Uri pdffile, String title, String photo, Context context, String ids){
        InputStream iStream;
        try {
            iStream = context.getContentResolver().openInputStream(pdffile);
            assert iStream != null;
            final byte[] inputData = getBytes(iStream);

            VolleyMultipartRequest volleyMultipartRequest = new VolleyMultipartRequest(Request.Method.POST, upload_URL,
                    response -> {
                        rQueue.getCache().clear();
                        try {
                            JSONObject jsonObject = new JSONObject(new String(response.data));

                        } catch (JSONException e) {
                            e.printStackTrace();
                        }
                    },
                    error -> Toast.makeText(context, error.getMessage(), Toast.LENGTH_SHORT).show()) {

                @Override
                protected Map<String, String> getParams() throws AuthFailureError {
                    Map<String, String> params = new HashMap<>();
                    params.put("ids", ids);
                    params.put("title", title);
                    params.put("photo", photo);
                    return params;
                }

                @Override
                protected Map<String, DataPart> getByteData() {
                    Map<String, DataPart> params = new HashMap<>();
                    params.put("filename", new DataPart(pdfname ,inputData));
                    return params;
                }
            };


            volleyMultipartRequest.setRetryPolicy(new DefaultRetryPolicy(
                    0,
                    DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
                    DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
            rQueue = Volley.newRequestQueue(context);
            rQueue.add(volleyMultipartRequest);



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

VolleyMultipartRequest类

public class VolleyMultipartRequest extends Request<NetworkResponse> {
    // ... 省略其余代码 ...
    private void buildDataPart(DataOutputStream dataOutputStream, DataPart dataFile, String inputName) throws IOException {
        dataOutputStream.writeBytes(twoHyphens + boundary + lineEnd);
        dataOutputStream.writeBytes("Content-Disposition: form-data; name=\"" +
                inputName + "\"; filename=\"" + dataFile.getFileName() + "\"" + lineEnd);
        if (dataFile.getType() != null && !dataFile.getType().trim().isEmpty()) {
            dataOutputStream.writeBytes("Content-Type: " + dataFile.getType() + lineEnd);
        }
        dataOutputStream.writeBytes(lineEnd);

        ByteArrayInputStream fileInputStream = new ByteArrayInputStream(dataFile.getContent());
        int bytesAvailable = fileInputStream.available();

        int maxBufferSize = 1024 * 1024;
        int bufferSize = Math.min(bytesAvailable, maxBufferSize);
        byte[] buffer = new byte[bufferSize];

        int bytesRead = fileInputStream.read(buffer, 0, bufferSize);

        while (bytesRead > 0) {
            dataOutputStream.write(buffer, 0, bufferSize);
            bytesAvailable = fileInputStream.available();
            bufferSize = Math.min(bytesAvailable, maxBufferSize);
            bytesRead = fileInputStream.read(buffer, 0, bufferSize);
        }

        dataOutputStream.writeBytes(lineEnd);
    }
    
    public class DataPart {
        // ... 省略其余代码 ...
    }
}

错误发生在这一行:

byteBuffer.write(buffer, 0, len);
英文:

I'm trying to send a large file to my server, but an error appears: java.lang.OutOfMemoryError: Failed to allocate a 268435468 byte allocation with 33554432 free bytes and 100MB until OOM. Although if I try with small files then all works well.

This is my code:

public static byte[] getBytes(InputStream inputStream) throws IOException {
ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
int len = 0;
while ((len = inputStream.read(buffer)) != -1) {
byteBuffer.write(buffer, 0, len);
}
return byteBuffer.toByteArray();
}

uploading function

public static void uploadPDF(final String pdfname, Uri pdffile, String title, String photo, Context context, String ids){
InputStream iStream;
try {
iStream = context.getContentResolver().openInputStream(pdffile);
assert iStream != null;
final byte[] inputData = getBytes(iStream);
VolleyMultipartRequest volleyMultipartRequest = new VolleyMultipartRequest(Request.Method.POST, upload_URL,
response -&gt; {
rQueue.getCache().clear();
try {
JSONObject jsonObject = new JSONObject(new String(response.data));
} catch (JSONException e) {
e.printStackTrace();
}
},
error -&gt; Toast.makeText(context, error.getMessage(), Toast.LENGTH_SHORT).show()) {
@Override
protected Map&lt;String, String&gt; getParams() throws AuthFailureError {
Map&lt;String, String&gt; params = new HashMap&lt;&gt;();
params.put(&quot;ids&quot;, ids);
params.put(&quot;title&quot;, title);
params.put(&quot;photo&quot;, photo);
return params;
}
@Override
protected Map&lt;String, DataPart&gt; getByteData() {
Map&lt;String, DataPart&gt; params = new HashMap&lt;&gt;();
params.put(&quot;filename&quot;, new DataPart(pdfname ,inputData));
return params;
}
};
volleyMultipartRequest.setRetryPolicy(new DefaultRetryPolicy(
0,
DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
rQueue = Volley.newRequestQueue(context);
rQueue.add(volleyMultipartRequest);
} catch (IOException e) {
e.printStackTrace();
}

public class VolleyMultipartRequest extends Request&lt;NetworkResponse&gt; {
private final String twoHyphens = &quot;--&quot;;
private final String lineEnd = &quot;\r\n&quot;;
private final String boundary = &quot;apiclient-&quot; + System.currentTimeMillis();
private Response.Listener&lt;NetworkResponse&gt; mListener;
private Response.ErrorListener mErrorListener;
private Map&lt;String, String&gt; mHeaders;
public VolleyMultipartRequest(int method, String url,
Response.Listener&lt;NetworkResponse&gt; listener,
Response.ErrorListener errorListener) {
super(method, url, errorListener);
this.mListener = listener;
this.mErrorListener = errorListener;
}
@Override
public Map&lt;String, String&gt; getHeaders() throws AuthFailureError {
return (mHeaders != null) ? mHeaders : super.getHeaders();
}
@Override
public String getBodyContentType() {
return &quot;multipart/form-data;boundary=&quot; + boundary;
}
@Override
public byte[] getBody() throws AuthFailureError {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
try {
// populate text payload
Map&lt;String, String&gt; params = getParams();
if (params != null &amp;&amp; params.size() &gt; 0) {
textParse(dos, params, getParamsEncoding());
}
// populate data byte payload
Map&lt;String, DataPart&gt; data = getByteData();
if (data != null &amp;&amp; data.size() &gt; 0) {
dataParse(dos, data);
}
// close multipart form data after text and file data
dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);
return bos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* Custom method handle data payload.
*
* @return Map data part label with data byte
* @throws AuthFailureError
*/
protected Map&lt;String, DataPart&gt; getByteData() throws AuthFailureError {
return null;
}
@Override
protected Response&lt;NetworkResponse&gt; parseNetworkResponse(NetworkResponse response) {
try {
return Response.success(
response,
HttpHeaderParser.parseCacheHeaders(response));
} catch (Exception e) {
return Response.error(new ParseError(e));
}
}
@Override
protected void deliverResponse(NetworkResponse response) {
mListener.onResponse(response);
}
@Override
public void deliverError(VolleyError error) {
mErrorListener.onErrorResponse(error);
}
/**
* Parse string map into data output stream by key and value.
*
* @param dataOutputStream data output stream handle string parsing
* @param params           string inputs collection
* @param encoding         encode the inputs, default UTF-8
* @throws IOException
*/
private void textParse(DataOutputStream dataOutputStream, Map&lt;String, String&gt; params, String encoding) throws IOException {
try {
for (Map.Entry&lt;String, String&gt; entry : params.entrySet()) {
buildTextPart(dataOutputStream, entry.getKey(), entry.getValue());
}
} catch (UnsupportedEncodingException uee) {
throw new RuntimeException(&quot;Encoding not supported: &quot; + encoding, uee);
}
}
/**
* Parse data into data output stream.
*
* @param dataOutputStream data output stream handle file attachment
* @param data             loop through data
* @throws IOException
*/
private void dataParse(DataOutputStream dataOutputStream, Map&lt;String, DataPart&gt; data) throws IOException {
for (Map.Entry&lt;String, DataPart&gt; entry : data.entrySet()) {
buildDataPart(dataOutputStream, entry.getValue(), entry.getKey());
}
}
/**
* Write string data into header and data output stream.
*
* @param dataOutputStream data output stream handle string parsing
* @param parameterName    name of input
* @param parameterValue   value of input
* @throws IOException
*/
private void buildTextPart(DataOutputStream dataOutputStream, String parameterName, String parameterValue) throws IOException {
dataOutputStream.writeBytes(twoHyphens + boundary + lineEnd);
dataOutputStream.writeBytes(&quot;Content-Disposition: form-data; name=\&quot;&quot; + parameterName + &quot;\&quot;&quot; + lineEnd);
dataOutputStream.writeBytes(lineEnd);
dataOutputStream.writeBytes(parameterValue + lineEnd);
}
/**
* Write data file into header and data output stream.
*
* @param dataOutputStream data output stream handle data parsing
* @param dataFile         data byte as DataPart from collection
* @param inputName        name of data input
* @throws IOException
*/
private void buildDataPart(DataOutputStream dataOutputStream, DataPart dataFile, String inputName) throws IOException {
dataOutputStream.writeBytes(twoHyphens + boundary + lineEnd);
dataOutputStream.writeBytes(&quot;Content-Disposition: form-data; name=\&quot;&quot; +
inputName + &quot;\&quot;; filename=\&quot;&quot; + dataFile.getFileName() + &quot;\&quot;&quot; + lineEnd);
if (dataFile.getType() != null &amp;&amp; !dataFile.getType().trim().isEmpty()) {
dataOutputStream.writeBytes(&quot;Content-Type: &quot; + dataFile.getType() + lineEnd);
}
dataOutputStream.writeBytes(lineEnd);
ByteArrayInputStream fileInputStream = new ByteArrayInputStream(dataFile.getContent());
int bytesAvailable = fileInputStream.available();
int maxBufferSize = 1024 * 1024;
int bufferSize = Math.min(bytesAvailable, maxBufferSize);
byte[] buffer = new byte[bufferSize];
int bytesRead = fileInputStream.read(buffer, 0, bufferSize);
while (bytesRead &gt; 0) {
dataOutputStream.write(buffer, 0, bufferSize);
bytesAvailable = fileInputStream.available();
bufferSize = Math.min(bytesAvailable, maxBufferSize);
bytesRead = fileInputStream.read(buffer, 0, bufferSize);
}
dataOutputStream.writeBytes(lineEnd);
}
public class DataPart {
private String fileName;
private byte[] content;
private String type;
public DataPart() {
}
public DataPart(String name, byte[] data) {
fileName = name;
content = data;
}
String getFileName() {
return fileName;
}
byte[] getContent() {
return content;
}
String getType() {
return type;
}
}
}

An error occurs on this line:

  byteBuffer.write(buffer, 0, len);

答案1

得分: 0

以下是您要求的翻译内容:

输入流提供了太多数据,无法将其保留在内存中。

选项1:

在读取文件时处理数据(在循环中)

选项2:

将其保存到文件中,并在不将整个内容加载到内存中的情况下从文件中处理。您可以在文件中来回跳转。


OutOfMemoryError 表明您尝试在堆上分配数据,但应用程序没有足够的可用 RAM 来使用。在32位设备(许多手机使用此架构)上,这还可以意味着您已耗尽可寻址空间。

它在写入 ByteArrayOutputStream 时发生,因为 ByteArrayOutputStream 在堆中存储其内容(字节数组),但无法分配足够的空间来增加内部字节数组的大小。

英文:

The input sream gives you too much data to keep it in RAM.

Option 1:

Process the data while reading the file(in the loop)

Option 2:

Save it to a file and process it from the file without loading the whole content to your RAM.

You can jump back and forth in the file.


The OutOfMemoryError says that you tried to allocate data on the heap but your application didn't have enough free RAM to use. On 32-Bit devices(that lots of phones are), it can also mean that you ran out of addressable space.

It occured while writing to the ByteArrayOutputStream because a ByteArrayOutoutStream stores it's content(the byte array) in the heap and it could not allocate enough space to increase the size of the internal byte array.

答案2

得分: 0

你无法在这种情况下使用 Volley:

为什么 Volley 不支持大型下载/上传?

Volley 不适用于大型下载或流式操作,因为在解析过程中 Volley 会将所有请求和响应都保存在内存中。

你将需要使用另一个 HTTP 客户端。除了 Volley 之外,几乎任何其他 HTTP 客户端都支持大型下载和上传。个人而言,我更喜欢 OkHttp,但如果你不想再添加另一个库依赖,你可以使用 HttpUrlConnection,尽管它比较繁琐。只需搜索“如何在 Android 上使用 X 上传文件”,其中 X 是你选择使用的 HTTP 客户端。

英文:

You cannot use Volley for this:

> Why doesn't Volley support large downloads/uploads?
> -
Volley is not suitable for large download or streaming operations, since Volley holds all requests and responses in memory during parsing.

You'll have to use another HTTP client. Pretty much any HTTP client other than Volley supports large downloads and uploads. Personally I prefer OkHttp, but if you prefer not to add yet another library dependency you can make do with HttpUrlConnection even though it's cumbersome. Just search for "how to upload file with X on android" where X is the http client you choose to use.

huangapple
  • 本文由 发表于 2020年4月10日 00:56:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/61126294.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定