import org.springframework.stereotype.Service;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Base64;
@Service
public class Base64Encoder {
private static final int BUFFER_SIZE = 3 * 1024;
public String encode(final InputStream input) throws IOException {
try (BufferedInputStream in = new BufferedInputStream(input, BUFFER_SIZE)) {
final Base64.Encoder encoder = Base64.getEncoder();
final StringBuilder result = new StringBuilder();
byte[] chunk = new byte[BUFFER_SIZE];
ByteRingBuffer byteBuffer = new ByteRingBuffer(BUFFER_SIZE * 2);
int len;
while ((len = in.read(chunk)) != -1) {
byteBuffer.put(Arrays.copyOf(chunk, len));
if (byteBuffer.available() >= BUFFER_SIZE) {
final byte[] workingChunk = new byte[BUFFER_SIZE];
byteBuffer.get(workingChunk);
result.append(encoder.encodeToString(workingChunk));
}
}
if (byteBuffer.available() > 0) {
final byte[] workingChunk = new byte[byteBuffer.available()];
byteBuffer.get(workingChunk);
result.append(encoder.encodeToString(workingChunk));
}
return result.toString();
}
}
}
public class ByteRingBuffer {
private final byte[] buffer;
private final int capacity;
private int available;
private int idxGet;
private int idxPut;
public ByteRingBuffer(int capacity) {
this.capacity = capacity;
buffer = new byte[this.capacity];
}
/**
* Gets as many of the requested bytes as available from this buffer.
*
* @return number of bytes actually got from this buffer (0 if no bytes are available)
*/
public int get(byte[] dst) {
return get(dst, 0, dst.length);
}
/**
* Gets as many of the requested bytes as available from this buffer.
*
* @return number of bytes actually got from this buffer (0 if no bytes are available)
*/
public int get(byte[] dst, int off, int len) {
if (available == 0) {
return 0;
}
// limit is last index to read + 1
int limit = idxGet < idxPut ? idxPut : capacity;
int count = Math.min(limit - idxGet, len);
System.arraycopy(buffer, idxGet, dst, off, count);
idxGet += count;
if (idxGet == capacity) {
// Array end reached, check if we have more
int count2 = Math.min(len - count, idxPut);
if (count2 > 0) {
System.arraycopy(buffer, 0, dst, off + count, count2);
idxGet = count2;
count += count2;
} else {
idxGet = 0;
}
}
available -= count;
return count;
}
/**
* Puts as many of the given bytes as possible into this buffer.
*
* @return number of bytes actually put into this buffer (0 if the buffer is full)
*/
public int put(byte[] src) {
return put(src, 0, src.length);
}
/**
* Puts as many of the given bytes as possible into this buffer.
*
* @return number of bytes actually put into this buffer (0 if the buffer is full)
*/
public int put(byte[] src, int off, int len) {
if (available == capacity) {
return 0;
}
// limit is last index to put + 1
int limit = idxPut < idxGet ? idxGet : capacity;
int count = Math.min(limit - idxPut, len);
System.arraycopy(src, off, buffer, idxPut, count);
idxPut += count;
if (idxPut == capacity) {
// Array end reached, check if we have more
int count2 = Math.min(len - count, idxGet);
if (count2 > 0) {
System.arraycopy(src, off + count, buffer, 0, count2);
idxPut = count2;
count += count2;
} else {
idxPut = 0;
}
}
available += count;
return count;
}
/**
* Returns the number of bytes available and can be get without additional puts.
*/
public int available() {
return available;
}
}
The ringbuffer is necessary because the BufferedInputStream doesn't always return the required bytes in the same batch (sometimes you read 1000 bytes, other times 2000 etc).