Skip to content

Commit e4fdafe

Browse files
committed
Avoid unnecessary allocations by reusing a buffer
This change introduces the TusUploader#setChunkSize and TusUploader#uploadChunk methods which replace and deprecate TusUploader#uploadChunk(int). Latter method did not reuse the buffer resulting in a high number of memory allocations.
1 parent 7b0e5e2 commit e4fdafe

4 files changed

Lines changed: 83 additions & 10 deletions

File tree

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,12 @@ TusUpload upload = new TusUpload(file);
3434
// a connection to the remote server and doing the uploading.
3535
TusUploader uploader = client.resumeOrCreateUpload(upload);
3636

37-
// Upload the file in chunks of 1MB as long as data is available. Once the
37+
// Upload the file in chunks of 1MB sizes.
38+
uploader.setChunkSize(1024 * 1024);
39+
40+
// Upload the file as long as data is available. Once the
3841
// file has been fully uploaded the method will return -1
39-
while(uploader.uploadChunk(1024 * 1024) > -1) {
42+
while(uploader.uploadChunk() > -1) {
4043
// Calculate the progress using the total size of the uploading file and
4144
// the current offset.
4245
long totalBytes = upload.getSize();

example/src/main/java/io/tus/java/example/Main.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,12 @@ public static void main(String[] args) {
3434

3535
System.out.println("Starting upload...");
3636

37-
// Upload the file in chunks of 1KB as long as data is available. Once the
37+
// Upload the file in chunks of 1KB sizes.
38+
uploader.setChunkSize(1024);
39+
40+
// Upload the file as long as data is available. Once the
3841
// file has been fully uploaded the method will return -1
39-
while (uploader.uploadChunk(1024) > -1) {
42+
while(uploader.uploadChunk() > -1) {
4043
// Calculate the progress using the total size of the uploading file and
4144
// the current offset.
4245
long totalBytes = upload.getSize();

src/main/java/io/tus/java/client/TusUploader.java

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public class TusUploader {
2525
private URL uploadURL;
2626
private InputStream input;
2727
private long offset;
28+
private byte[] buffer;
2829

2930
private HttpURLConnection connection;
3031
private OutputStream output;
@@ -46,6 +47,8 @@ public TusUploader(TusClient client, URL uploadURL, InputStream input, long offs
4647

4748
input.skip(offset);
4849

50+
setChunkSize(2 * 1024 * 1024);
51+
4952
connection = (HttpURLConnection) uploadURL.openConnection();
5053
client.prepareConnection(connection);
5154
connection.setRequestProperty("Upload-Offset", Long.toString(offset));
@@ -63,6 +66,60 @@ public TusUploader(TusClient client, URL uploadURL, InputStream input, long offs
6366
output = connection.getOutputStream();
6467
}
6568

69+
/**
70+
* Sets the used chunk size. This number is used by {@link #uploadChunk()} to indicate how
71+
* much data is uploaded in a single take. When choosing a value for this parameter you need to
72+
* consider that uploadChunk() will only return once the specified number of bytes has been
73+
* sent. For slow internet connections this may take a long time. In addition, a buffer with
74+
* the chunk size is allocated and kept in memory.
75+
*
76+
* @param size The new chunk size
77+
*/
78+
public void setChunkSize(int size) {
79+
buffer = new byte[size];
80+
}
81+
82+
/**
83+
* Returns the current chunk size set using {@link #setChunkSize(int)}.
84+
*
85+
* @return Current chunk size
86+
*/
87+
public int getChunkSize() {
88+
return buffer.length;
89+
}
90+
91+
/**
92+
* Upload a part of the file by reading a chunk from the InputStream and writing
93+
* it to the HTTP request's body. If the number of available bytes is lower than the chunk's
94+
* size, all available bytes will be uploaded and nothing more.
95+
* No new connection will be established when calling this method, instead the connection opened
96+
* in the constructor will be used.
97+
* The size of the read chunk can be obtained using {@link #getChunkSize()} and changed
98+
* using {@link #setChunkSize(int)}.
99+
* In order to obtain the new offset, use {@link #getOffset()} after this method returns.
100+
*
101+
* @return Number of bytes read and written.
102+
* @throws IOException Thrown if an exception occurs while reading from the source or writing
103+
* to the HTTP request.
104+
*/
105+
public int uploadChunk() throws IOException {
106+
int bytesRead = input.read(buffer);
107+
if(bytesRead == -1) {
108+
// No bytes were read since the input stream is empty
109+
return -1;
110+
}
111+
112+
// Do not write the entire buffer to the stream since the array will
113+
// be filled up with 0x00s if the number of read bytes is lower then
114+
// the chunk's size.
115+
output.write(buffer, 0, bytesRead);
116+
output.flush();
117+
118+
offset += bytesRead;
119+
120+
return bytesRead;
121+
}
122+
66123
/**
67124
* Upload a part of the file by read a chunks specified size from the InputStream and writing
68125
* it to the HTTP request's body. If the number of available bytes is lower than the chunk's
@@ -71,6 +128,12 @@ public TusUploader(TusClient client, URL uploadURL, InputStream input, long offs
71128
* in the constructor will be used.
72129
* In order to obtain the new offset, use {@link #getOffset()} after this method returns.
73130
*
131+
* @deprecated This method is inefficient and has been replaced by {@link #setChunkSize(int)}
132+
* and {@link #uploadChunk()} and should not be used anymore. The reason is, that
133+
* this method allocates a new buffer with the supplied chunk size for each time
134+
* it's called without reusing it. This results in a high number of memory
135+
* allocations and should be avoided. The new methods do not have this issue.
136+
*
74137
* @param chunkSize Maximum number of bytes which will be uploaded. When choosing a value
75138
* for this parameter you need to consider that the method call will only
76139
* return once the specified number of bytes have been sent. For slow
@@ -79,9 +142,9 @@ public TusUploader(TusClient client, URL uploadURL, InputStream input, long offs
79142
* @throws IOException Thrown if an exception occurs while reading from the source or writing
80143
* to the HTTP request.
81144
*/
82-
public int uploadChunk(int chunkSize) throws IOException {
83-
byte[] buffer = new byte[chunkSize];
84-
int bytesRead = input.read(buffer);
145+
@Deprecated public int uploadChunk(int chunkSize) throws IOException {
146+
byte[] buf = new byte[chunkSize];
147+
int bytesRead = input.read(buf);
85148
if(bytesRead == -1) {
86149
// No bytes were read since the input stream is empty
87150
return -1;
@@ -90,7 +153,7 @@ public int uploadChunk(int chunkSize) throws IOException {
90153
// Do not write the entire buffer to the stream since the array will
91154
// be filled up with 0x00s if the number of read bytes is lower then
92155
// the chunk's size.
93-
output.write(buffer, 0, bytesRead);
156+
output.write(buf, 0, bytesRead);
94157
output.flush();
95158

96159
offset += bytesRead;

src/test/java/io/tus/java/client/TestTusUploader.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,13 @@ public void testTusUploader() throws IOException, ProtocolException {
5353
long offset = 3;
5454

5555
TusUploader uploader = new TusUploader(client, uploadUrl, input, offset);
56-
assertEquals(5, uploader.uploadChunk(5));
56+
57+
uploader.setChunkSize(5);
58+
assertEquals(uploader.getChunkSize(), 5);
59+
60+
assertEquals(5, uploader.uploadChunk());
5761
assertEquals(3, uploader.uploadChunk(5));
58-
assertEquals(-1, uploader.uploadChunk(5));
62+
assertEquals(-1, uploader.uploadChunk());
5963
assertEquals(11, uploader.getOffset());
6064
uploader.finish();
6165
}

0 commit comments

Comments
 (0)