Skip to content

Commit ffc28d6

Browse files
committed
Merge pull request square#116 from square/jwilson_0228_segment_sharing
Segment sharing.
2 parents 755bde6 + 7a9dd8c commit ffc28d6

17 files changed

+756
-140
lines changed

okio/src/main/java/okio/Buffer.java

Lines changed: 59 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ public long size() {
8989
return this; // Nowhere to emit to!
9090
}
9191

92-
@Override public BufferedSink emit() throws IOException {
92+
@Override public BufferedSink emit() {
9393
return this; // Nowhere to emit to!
9494
}
9595

@@ -101,7 +101,7 @@ public long size() {
101101
if (size < byteCount) throw new EOFException();
102102
}
103103

104-
@Override public boolean request(long byteCount) throws IOException {
104+
@Override public boolean request(long byteCount) {
105105
return size >= byteCount;
106106
}
107107

@@ -167,30 +167,26 @@ public Buffer copyTo(Buffer out, long offset, long byteCount) {
167167
checkOffsetAndCount(size, offset, byteCount);
168168
if (byteCount == 0) return this;
169169

170-
Segment source = head;
171-
Segment target = out.writableSegment(1);
172170
out.size += byteCount;
173171

174-
while (byteCount > 0) {
175-
// If necessary, advance to a readable source segment. This won't repeat after the first copy.
176-
while (offset >= source.limit - source.pos) {
177-
offset -= (source.limit - source.pos);
178-
source = source.next;
179-
}
172+
// Skip segments that we aren't copying from.
173+
Segment s = head;
174+
for (; offset >= (s.limit - s.pos); s = s.next) {
175+
offset -= (s.limit - s.pos);
176+
}
180177

181-
// If necessary, append another target segment.
182-
if (target.limit == Segment.SIZE) {
183-
target = target.push(SegmentPool.INSTANCE.take());
178+
// Copy one segment at a time.
179+
for (; byteCount > 0; s = s.next) {
180+
Segment copy = new Segment(s);
181+
copy.pos += offset;
182+
copy.limit = Math.min(copy.pos + (int) byteCount, copy.limit);
183+
if (out.head == null) {
184+
out.head = copy.next = copy.prev = copy;
185+
} else {
186+
out.head.prev.push(copy);
184187
}
185-
186-
// Copy bytes from the source segment to the target segment.
187-
long sourceReadable = Math.min(source.limit - (source.pos + offset), byteCount);
188-
long targetWritable = Segment.SIZE - target.limit;
189-
int toCopy = (int) Math.min(sourceReadable, targetWritable);
190-
System.arraycopy(source.data, source.pos + (int) offset, target.data, target.limit, toCopy);
191-
offset += toCopy;
192-
target.limit += toCopy;
193-
byteCount -= toCopy;
188+
byteCount -= copy.limit - copy.pos;
189+
offset = 0;
194190
}
195191

196192
return this;
@@ -218,7 +214,7 @@ public Buffer writeTo(OutputStream out, long byteCount) throws IOException {
218214
if (s.pos == s.limit) {
219215
Segment toRecycle = s;
220216
head = s = toRecycle.pop();
221-
SegmentPool.INSTANCE.recycle(toRecycle);
217+
SegmentPool.recycle(toRecycle);
222218
}
223219
}
224220

@@ -265,7 +261,7 @@ public long completeSegmentByteCount() {
265261

266262
// Omit the tail if it's still writable.
267263
Segment tail = head.prev;
268-
if (tail.limit < Segment.SIZE) {
264+
if (tail.limit < Segment.SIZE && tail.owner) {
269265
result -= tail.limit - tail.pos;
270266
}
271267

@@ -285,7 +281,7 @@ public long completeSegmentByteCount() {
285281

286282
if (pos == limit) {
287283
head = segment.pop();
288-
SegmentPool.INSTANCE.recycle(segment);
284+
SegmentPool.recycle(segment);
289285
} else {
290286
segment.pos = pos;
291287
}
@@ -324,7 +320,7 @@ public byte getByte(long pos) {
324320

325321
if (pos == limit) {
326322
head = segment.pop();
327-
SegmentPool.INSTANCE.recycle(segment);
323+
SegmentPool.recycle(segment);
328324
} else {
329325
segment.pos = pos;
330326
}
@@ -356,7 +352,7 @@ public byte getByte(long pos) {
356352

357353
if (pos == limit) {
358354
head = segment.pop();
359-
SegmentPool.INSTANCE.recycle(segment);
355+
SegmentPool.recycle(segment);
360356
} else {
361357
segment.pos = pos;
362358
}
@@ -390,7 +386,7 @@ public byte getByte(long pos) {
390386

391387
if (pos == limit) {
392388
head = segment.pop();
393-
SegmentPool.INSTANCE.recycle(segment);
389+
SegmentPool.recycle(segment);
394390
} else {
395391
segment.pos = pos;
396392
}
@@ -588,7 +584,7 @@ public byte getByte(long pos) {
588584

589585
if (s.pos == s.limit) {
590586
head = s.pop();
591-
SegmentPool.INSTANCE.recycle(s);
587+
SegmentPool.recycle(s);
592588
}
593589

594590
return result;
@@ -675,7 +671,7 @@ String readUtf8Line(long newline) throws EOFException {
675671

676672
if (s.pos == s.limit) {
677673
head = s.pop();
678-
SegmentPool.INSTANCE.recycle(s);
674+
SegmentPool.recycle(s);
679675
}
680676

681677
return toCopy;
@@ -706,14 +702,15 @@ public void clear() {
706702
if (head.pos == head.limit) {
707703
Segment toRecycle = head;
708704
head = toRecycle.pop();
709-
SegmentPool.INSTANCE.recycle(toRecycle);
705+
SegmentPool.recycle(toRecycle);
710706
}
711707
}
712708
}
713709

714710
@Override public Buffer write(ByteString byteString) {
715711
if (byteString == null) throw new IllegalArgumentException("byteString == null");
716-
return write(byteString.data, 0, byteString.data.length);
712+
byteString.write(this);
713+
return this;
717714
}
718715

719716
@Override public Buffer writeUtf8(String string) {
@@ -977,13 +974,13 @@ Segment writableSegment(int minimumCapacity) {
977974
if (minimumCapacity < 1 || minimumCapacity > Segment.SIZE) throw new IllegalArgumentException();
978975

979976
if (head == null) {
980-
head = SegmentPool.INSTANCE.take(); // Acquire a first segment.
977+
head = SegmentPool.take(); // Acquire a first segment.
981978
return head.next = head.prev = head;
982979
}
983980

984981
Segment tail = head.prev;
985-
if (tail.limit + minimumCapacity > Segment.SIZE) {
986-
tail = tail.push(SegmentPool.INSTANCE.take()); // Append a new empty segment to fill up.
982+
if (tail.limit + minimumCapacity > Segment.SIZE || !tail.owner) {
983+
tail = tail.push(SegmentPool.take()); // Append a new empty segment to fill up.
987984
}
988985
return tail;
989986
}
@@ -1047,16 +1044,17 @@ Segment writableSegment(int minimumCapacity) {
10471044
// Is a prefix of the source's head segment all that we need to move?
10481045
if (byteCount < (source.head.limit - source.head.pos)) {
10491046
Segment tail = head != null ? head.prev : null;
1050-
if (tail == null || byteCount + (tail.limit - tail.pos) > Segment.SIZE) {
1051-
// We're going to need another segment. Split the source's head
1052-
// segment in two, then move the first of those two to this buffer.
1053-
source.head = source.head.split((int) byteCount);
1054-
} else {
1047+
if (tail != null && tail.owner
1048+
&& (byteCount + tail.limit - (tail.shared ? 0 : tail.pos) <= Segment.SIZE)) {
10551049
// Our existing segments are sufficient. Move bytes from source's head to our tail.
10561050
source.head.writeTo(tail, (int) byteCount);
10571051
source.size -= byteCount;
10581052
size += byteCount;
10591053
return;
1054+
} else {
1055+
// We're going to need another segment. Split the source's head
1056+
// segment in two, then move the first of those two to this buffer.
1057+
source.head = source.head.split((int) byteCount);
10601058
}
10611059
}
10621060

@@ -1128,7 +1126,7 @@ Segment writableSegment(int minimumCapacity) {
11281126
Segment s = head;
11291127
if (s == null) return -1L;
11301128
long offset = 0L;
1131-
byte[] toFind = targetBytes.data;
1129+
byte[] toFind = targetBytes.toByteArray();
11321130
do {
11331131
int segmentByteCount = s.limit - s.pos;
11341132
if (fromIndex >= segmentByteCount) {
@@ -1244,11 +1242,28 @@ List<Integer> segmentSizes() {
12441242
Buffer result = new Buffer();
12451243
if (size == 0) return result;
12461244

1247-
result.write(head.data, head.pos, head.limit - head.pos);
1245+
result.head = new Segment(head);
1246+
result.head.next = result.head.prev = result.head;
12481247
for (Segment s = head.next; s != head; s = s.next) {
1249-
result.write(s.data, s.pos, s.limit - s.pos);
1248+
result.head.prev.push(new Segment(s));
12501249
}
1251-
1250+
result.size = size;
12521251
return result;
12531252
}
1253+
1254+
/** Returns an immutable copy of this buffer as a byte string. */
1255+
public ByteString snapshot() {
1256+
if (size > Integer.MAX_VALUE) {
1257+
throw new IllegalArgumentException("size > Integer.MAX_VALUE: " + size);
1258+
}
1259+
return snapshot((int) size);
1260+
}
1261+
1262+
/**
1263+
* Returns an immutable copy of the first {@code byteCount} bytes of this buffer as a byte string.
1264+
*/
1265+
public ByteString snapshot(int byteCount) {
1266+
if (byteCount == 0) return ByteString.EMPTY;
1267+
return new SegmentedByteString(this, byteCount);
1268+
}
12541269
}

okio/src/main/java/okio/ByteString.java

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.security.NoSuchAlgorithmException;
2828
import java.util.Arrays;
2929

30+
import static okio.Util.arrayRangeEquals;
3031
import static okio.Util.checkOffsetAndCount;
3132

3233
/**
@@ -40,17 +41,17 @@
4041
* and other environments that run both trusted and untrusted code in the same
4142
* process.
4243
*/
43-
public final class ByteString implements Serializable {
44-
private static final char[] HEX_DIGITS =
44+
public class ByteString implements Serializable {
45+
static final char[] HEX_DIGITS =
4546
{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
4647
private static final long serialVersionUID = 1L;
4748

4849
/** A singleton empty {@code ByteString}. */
4950
public static final ByteString EMPTY = ByteString.of();
5051

5152
final byte[] data;
52-
private transient int hashCode; // Lazily computed; 0 if unknown.
53-
private transient String utf8; // Lazily computed.
53+
transient int hashCode; // Lazily computed; 0 if unknown.
54+
transient String utf8; // Lazily computed.
5455

5556
ByteString(byte[] data) {
5657
this.data = data; // Trusted internal constructor doesn't clone data.
@@ -293,8 +294,36 @@ public void write(OutputStream out) throws IOException {
293294
out.write(data);
294295
}
295296

297+
/** Writes the contents of this byte string to {@code buffer}. */
298+
void write(Buffer buffer) {
299+
buffer.write(data, 0, data.length);
300+
}
301+
302+
/**
303+
* Returns true if the bytes of this in {@code [offset..offset+byteCount)} equal the bytes of
304+
* {@code other} in {@code [otherOffset..otherOffset+byteCount)}. Returns false if either range is
305+
* out of bounds.
306+
*/
307+
public boolean rangeEquals(int offset, ByteString other, int otherOffset, int byteCount) {
308+
return other.rangeEquals(otherOffset, this.data, offset, byteCount);
309+
}
310+
311+
/**
312+
* Returns true if the bytes of this in {@code [offset..offset+byteCount)} equal the bytes of
313+
* {@code other} in {@code [otherOffset..otherOffset+byteCount)}. Returns false if either range is
314+
* out of bounds.
315+
*/
316+
public boolean rangeEquals(int offset, byte[] other, int otherOffset, int byteCount) {
317+
return offset <= data.length - byteCount
318+
&& otherOffset <= other.length - byteCount
319+
&& arrayRangeEquals(data, offset, other, otherOffset, byteCount);
320+
}
321+
296322
@Override public boolean equals(Object o) {
297-
return o == this || o instanceof ByteString && Arrays.equals(((ByteString) o).data, data);
323+
if (o == this) return true;
324+
return o instanceof ByteString
325+
&& ((ByteString) o).size() == data.length
326+
&& ((ByteString) o).rangeEquals(0, data, 0, data.length);
298327
}
299328

300329
@Override public int hashCode() {

okio/src/main/java/okio/DeflaterSink.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public DeflaterSink(Sink sink, Deflater deflater) {
7373
head.pos += toDeflate;
7474
if (head.pos == head.limit) {
7575
source.head = head.pop();
76-
SegmentPool.INSTANCE.recycle(head);
76+
SegmentPool.recycle(head);
7777
}
7878

7979
byteCount -= toDeflate;
@@ -99,7 +99,7 @@ private void deflate(boolean syncFlush) throws IOException {
9999
buffer.size += deflated;
100100
sink.emitCompleteSegments();
101101
} else if (deflater.needsInput()) {
102-
return;
102+
return; // TODO(jwilson): do we have a dangling empty tail segment here?
103103
}
104104
}
105105
}

okio/src/main/java/okio/GzipSink.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ public GzipSink(Sink sink) {
8383

8484
// This method delegates to the DeflaterSink for finishing the deflate process
8585
// but keeps responsibility for releasing the deflater's resources. This is
86-
// necessary because writeFooter needs to query the proccessed byte count which
87-
// only works when the defalter is still open.
86+
// necessary because writeFooter needs to query the processed byte count which
87+
// only works when the deflater is still open.
8888

8989
Throwable thrown = null;
9090
try {

okio/src/main/java/okio/InflaterSource.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ public InflaterSource(Source source, Inflater inflater) {
7070
sink.size += bytesInflated;
7171
return bytesInflated;
7272
}
73+
// TODO(jwilson): do we have an empty tail?
7374
if (inflater.finished() || inflater.needsDictionary()) {
7475
releaseInflatedBytes();
7576
return -1;

okio/src/main/java/okio/Okio.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ private static Sink sink(final OutputStream out, final Timeout timeout) {
8383

8484
if (head.pos == head.limit) {
8585
source.head = head.pop();
86-
SegmentPool.INSTANCE.recycle(head);
86+
SegmentPool.recycle(head);
8787
}
8888
}
8989
}

0 commit comments

Comments
 (0)