All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.n52.javaps.utils.LargeBufferStream Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2016-2023 52°North Spatial Information Research GmbH
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.n52.javaps.utils;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;

/**
 * A fully buffered output stream using local disk storage for large data.
 * 

* Initially this output stream buffers to memory, like ByteArrayOutputStream * might do, but it shifts to using an on disk temporary file if the output gets * too large. *

* The content of this buffered stream may be sent to another OutputStream only * after this stream has been properly closed by {@link #close()}. */ public class LargeBufferStream extends OutputStream { private static final int DEFAULT_IN_CORE_LIMIT = 128 * 1024 * 1024; /** Chain of data, if we are still completely in-core; otherwise null. */ private ArrayList blocks; /** * Maximum number of bytes we will permit storing in memory. *

* When this limit is reached the data will be shifted to a file on disk, * preventing the JVM heap from growing out of control. */ private int inCoreLimit; /** * Location of our temporary file if we are on disk; otherwise null. *

* If we exceeded the {@link #inCoreLimit} we nulled out {@link #blocks} and * created this file instead. All output goes here through {@link #diskOut}. */ private File onDiskFile; /** If writing to {@link #onDiskFile} this is a buffered stream to it. */ private OutputStream diskOut; /** Create a new empty temporary buffer. */ public LargeBufferStream() { inCoreLimit = DEFAULT_IN_CORE_LIMIT; blocks = new ArrayList(inCoreLimit / Block.SZ); blocks.add(new Block()); } @Override public void write(final int b) throws IOException { if (blocks == null) { diskOut.write(b); return; } Block s = last(); if (s.isFull()) { if (reachedInCoreLimit()) { diskOut.write(b); return; } s = new Block(); blocks.add(s); } s.buffer[s.count++] = (byte) b; } @Override public void write(final byte[] b, int off, int len) throws IOException { int offCopy = off; int lenCopy = len; if (blocks != null) { while (lenCopy > 0) { Block s = last(); if (s.isFull()) { if (reachedInCoreLimit()) { break; } s = new Block(); blocks.add(s); } final int n = Math.min(Block.SZ - s.count, lenCopy); System.arraycopy(b, offCopy, s.buffer, s.count, n); s.count += n; lenCopy -= n; offCopy += n; } } if (lenCopy > 0) { diskOut.write(b, offCopy, lenCopy); } } private Block last() { return blocks.get(blocks.size() - 1); } private boolean reachedInCoreLimit() throws IOException { if (blocks.size() * Block.SZ < inCoreLimit) { return false; } onDiskFile = File.createTempFile("jgit_", ".buffer"); diskOut = new FileOutputStream(onDiskFile); final Block last = blocks.remove(blocks.size() - 1); for (final Block b : blocks) { diskOut.write(b.buffer, 0, b.count); } blocks = null; diskOut = new BufferedOutputStream(diskOut, Block.SZ); diskOut.write(last.buffer, 0, last.count); return true; } @Override public void close() throws IOException { if (diskOut != null) { try { diskOut.close(); } finally { diskOut = null; } } } /** * Obtain the length (in bytes) of the buffer. *

* The length is only accurate after {@link #close()} has been invoked. * * @return total length of the buffer, in bytes. */ public long length() { if (onDiskFile != null) { return onDiskFile.length(); } final Block last = last(); return ((long) blocks.size()) * Block.SZ - (Block.SZ - last.count); } /** * Send this buffer to an output stream. *

* This method may only be invoked after {@link #close()} has completed * normally, to ensure all data is completely transferred. * * @param os * stream to send this buffer's complete content to. * @throws IOException * an error occurred reading from a temporary file on the local * system, or writing to the output stream. */ public void writeTo(final OutputStream os) throws IOException { if (blocks != null) { // Everything is in core so we can stream directly to the output. // for (final Block b : blocks) { os.write(b.buffer, 0, b.count); } } else { // Reopen the temporary file and copy the contents. // final FileInputStream in = new FileInputStream(onDiskFile); try { int cnt; final byte[] buf = new byte[Block.SZ]; while ((cnt = in.read(buf)) >= 0) { os.write(buf, 0, cnt); } } finally { in.close(); } } } /** Clear this buffer so it has no data, and cannot be used again. */ public void destroy() { blocks = null; if (diskOut != null) { try { diskOut.close(); } catch (IOException err) { // We shouldn't encounter an error closing the file. } finally { diskOut = null; } } if (onDiskFile != null) { if (!onDiskFile.delete()) { onDiskFile.deleteOnExit(); } onDiskFile = null; } } private static class Block { static final int SZ = 8 * 1024 * 1024; private final byte[] buffer = new byte[SZ]; private int count; boolean isFull() { return count == SZ; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy