Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 patch class StringBuffer { | 5 patch class StringBuffer { |
| 6 /** Backing store for collected UTF-16 code units. */ | 6 static const int _BUFFER_SIZE = 64; |
| 7 Uint16List _buffer; | 7 static const int _PARTS_TO_COMPACT = 128; |
| 8 /** Number of code units collected. */ | 8 static const int _PARTS_TO_COMPACT_SIZE_LIMIT = _PARTS_TO_COMPACT * 8; |
| 9 int _length = 0; | 9 |
| 10 /** | 10 /** |
| 11 * Collects the approximate maximal magnitude of the added code units. | 11 * When strings are written to the string buffer, we add them to a |
| 12 * list of string parts. | |
| 13 */ | |
| 14 List _parts = null; | |
| 15 | |
| 16 /** | |
| 17 * Total number of code units in the string parts. Does not include | |
| 18 * the code units added to the buffer. | |
| 19 */ | |
| 20 int _partsCodeUnits = 0; | |
| 21 | |
| 22 /** | |
| 23 * To preserve memory, we sometimes compact the parts. This combines | |
| 24 * several smaller parts into a single larger part to cut down on the | |
| 25 * cost that comes from the per-object memory overhead. We keep track | |
| 26 * of the last index where we ended our compaction and the number of | |
| 27 * code units added since the last compaction. | |
| 28 */ | |
| 29 int _partsCompactionIndex = 0; | |
| 30 int _partsCodeUnitsSinceCompaction = 0; | |
| 31 _ObjectArray _partsCompactionArray = null; | |
| 32 | |
| 33 /** | |
| 34 * The buffer is used to build up a string from code units. It is | |
| 35 * used when writing short strings or individul char codes to the | |
| 36 * buffer. The buffer is allocated on demand. | |
| 37 */ | |
| 38 Uint16List _buffer = null; | |
| 39 int _bufferPosition = 0; | |
| 40 | |
| 41 /** | |
| 42 * Collects the approximate maximal magnitude of the code units added | |
| 43 * to the buffer. | |
| 12 * | 44 * |
| 13 * The value of each added code unit is or'ed with this variable, so the | 45 * The value of each added code unit is or'ed with this variable, so the |
| 14 * most significant bit set in any code unit is also set in this value. | 46 * most significant bit set in any code unit is also set in this value. |
| 15 * If below 256, the string is a Latin-1 string. | 47 * If below 256, the string in the buffer is a Latin-1 string. |
| 16 */ | 48 */ |
| 17 int _codeUnitMagnitude = 0; | 49 int _bufferCodeUnitMagnitude = 0; |
| 18 | 50 |
| 19 /// Creates the string buffer with an initial content. | 51 /// Creates the string buffer with an initial content. |
| 20 /* patch */ StringBuffer([Object content = ""]) | 52 /* patch */ StringBuffer([Object content = ""]) { |
| 21 : _buffer = new Uint16List(16) { | |
| 22 write(content); | 53 write(content); |
| 23 } | 54 } |
| 24 | 55 |
| 25 /* patch */ int get length => _length; | 56 /* patch */ int get length => _partsCodeUnits + _bufferPosition; |
| 26 | 57 |
| 27 /// Adds [obj] to the buffer. | |
| 28 /* patch */ void write(Object obj) { | 58 /* patch */ void write(Object obj) { |
| 29 String str; | 59 String str; |
| 30 if (obj is String) { | 60 if (obj is String) { |
| 31 str = obj; | 61 str = obj; |
| 32 } else { | 62 } else { |
| 33 // TODO(srdjan): The following four lines could be replaced by | 63 // TODO(srdjan): The following four lines could be replaced by |
| 34 // '$obj', but apparently this is too slow on the Dart VM. | 64 // '$obj', but apparently this is too slow on the Dart VM. |
|
Lasse Reichstein Nielsen
2013/09/20 11:40:17
Is this still true?
| |
| 35 str = obj.toString(); | 65 str = obj.toString(); |
| 36 if (str is! String) { | 66 if (str is! String) { |
| 37 throw new ArgumentError('toString() did not return a string'); | 67 throw new ArgumentError('toString() did not return a string'); |
| 38 } | 68 } |
| 39 } | 69 } |
| 40 if (str.isEmpty) return; | 70 if (str.isEmpty) return; |
| 41 _ensureCapacity(str.length); | 71 _consumeBuffer(); |
| 42 for (int i = 0; i < str.length; i++) { | 72 _addPart(str); |
| 43 int unit = str.codeUnitAt(i); | |
| 44 _buffer[_length + i] = unit; | |
| 45 _codeUnitMagnitude |= unit; | |
| 46 } | |
| 47 _length += str.length; | |
| 48 } | 73 } |
| 49 | 74 |
| 50 /* patch */ void writeCharCode(int charCode) { | 75 /* patch */ void writeCharCode(int charCode) { |
| 51 if (charCode <= 0xFFFF) { | 76 if (charCode <= 0xFFFF) { |
| 52 if (charCode < 0) { | 77 if (charCode < 0) { |
| 53 throw new RangeError.range(charCode, 0, 0x10FFFF); | 78 throw new RangeError.range(charCode, 0, 0x10FFFF); |
| 54 } | 79 } |
| 55 _ensureCapacity(1); | 80 _ensureCapacity(1); |
| 56 _buffer[_length++] = charCode; | 81 _buffer[_bufferPosition++] = charCode; |
| 57 _codeUnitMagnitude |= charCode; | 82 _bufferCodeUnitMagnitude |= charCode; |
| 58 } else { | 83 } else { |
| 59 if (charCode > 0x10FFFF) { | 84 if (charCode > 0x10FFFF) { |
| 60 throw new RangeError.range(charCode, 0, 0x10FFFF); | 85 throw new RangeError.range(charCode, 0, 0x10FFFF); |
| 61 } | 86 } |
| 62 _ensureCapacity(2); | 87 _ensureCapacity(2); |
| 63 int bits = charCode - 0x10000; | 88 int bits = charCode - 0x10000; |
| 64 _buffer[_length++] = 0xD800 | (bits >> 10); | 89 _buffer[_bufferPosition++] = 0xD800 | (bits >> 10); |
| 65 _buffer[_length++] = 0xDC00 | (bits & 0x3FF); | 90 _buffer[_bufferPosition++] = 0xDC00 | (bits & 0x3FF); |
| 66 _codeUnitMagnitude |= 0xFFFF; | 91 _bufferCodeUnitMagnitude |= 0xFFFF; |
| 67 } | 92 } |
| 68 } | 93 } |
| 69 | 94 |
| 70 /** Makes the buffer empty. */ | 95 /** Makes the buffer empty. */ |
| 71 /* patch */ void clear() { | 96 /* patch */ void clear() { |
| 72 _length = 0; | 97 _parts = null; |
| 73 _codeUnitMagnitude = 0; | 98 _partsCodeUnits = _bufferPosition = _bufferCodeUnitMagnitude = 0; |
| 74 } | 99 } |
| 75 | 100 |
| 76 /** Returns the contents of buffer as a string. */ | 101 /** Returns the contents of buffer as a string. */ |
| 77 /* patch */ String toString() { | 102 /* patch */ String toString() { |
| 78 if (_length == 0) return ""; | 103 _consumeBuffer(); |
| 79 bool isLatin1 = _codeUnitMagnitude <= 0xFF; | 104 if (_partsCodeUnits == 0) return ""; |
| 80 return _create(_buffer, _length, isLatin1); | 105 |
| 106 // TODO(kasperl): It would be nice if concatAllNative would | |
| 107 // allow me to pass in a grownable array directly, but for | |
|
Lasse Reichstein Nielsen
2013/09/20 11:40:17
Should be fairly simple to do.
kasperl
2013/09/20 12:04:27
I guess I really want something like a _concatAllN
srdjan
2013/09/25 20:56:40
s/grownable/growable/
kasperl
2013/09/26 05:30:29
:-)
Maybe you could help me fix the TODO? If conc
srdjan
2013/09/26 13:22:45
Yes, I will do that.
| |
| 108 // now we have to copy the contents to a non-growable array. | |
| 109 int length = _parts.length; | |
| 110 _ObjectArray array = new _ObjectArray(length); | |
| 111 for (int i = 0; i < length; i++) array[i] = _parts[i]; | |
| 112 return _StringBase._concatAllNative(array); | |
| 81 } | 113 } |
| 82 | 114 |
| 83 /** Ensures that the buffer has enough capacity to add n code units. */ | 115 /** Ensures that the buffer has enough capacity to add n code units. */ |
| 84 void _ensureCapacity(int n) { | 116 void _ensureCapacity(int n) { |
| 85 int requiredCapacity = _length + n; | 117 if (_buffer == null) { |
| 86 if (requiredCapacity > _buffer.length) { | 118 _buffer = new Uint16List(_BUFFER_SIZE); |
| 87 _grow(requiredCapacity); | 119 } else if (_bufferPosition + n > _buffer.length) { |
| 120 _consumeBuffer(); | |
| 88 } | 121 } |
| 89 } | 122 } |
| 90 | 123 |
| 91 /** Grows the buffer until it can contain [requiredCapacity] entries. */ | 124 /** |
| 92 void _grow(int requiredCapacity) { | 125 * Consumes the content of the buffer by turning it into a string |
| 93 int newCapacity = _buffer.length; | 126 * and adding it as a part. After calling this the buffer position |
| 94 do { | 127 * will be reset to zero. |
| 95 newCapacity *= 2; | 128 */ |
| 96 } while (newCapacity < requiredCapacity); | 129 void _consumeBuffer() { |
| 97 List<int> newBuffer = new Uint16List(newCapacity); | 130 if (_bufferPosition == 0) return; |
| 98 newBuffer.setRange(0, _length, _buffer); | 131 bool isLatin1 = _bufferCodeUnitMagnitude <= 0xFF; |
| 99 _buffer = newBuffer; | 132 String str = _create(_buffer, _bufferPosition, isLatin1); |
| 133 _bufferPosition = _bufferCodeUnitMagnitude = 0; | |
|
Lasse Reichstein Nielsen
2013/09/20 11:40:17
Consider growing the buffer eventually?
| |
| 134 _addPart(str); | |
| 100 } | 135 } |
| 101 | 136 |
| 102 /** | 137 /** |
| 138 * Adds a new part to this string buffer and keeps track of how | |
| 139 * many code units are contained in the parts. | |
| 140 */ | |
| 141 void _addPart(String str) { | |
| 142 int length = str.length; | |
| 143 _partsCodeUnits += length; | |
| 144 _partsCodeUnitsSinceCompaction += length; | |
| 145 | |
| 146 if (_parts == null) { | |
| 147 _parts = [ str ]; | |
| 148 } else { | |
| 149 _parts.add(str); | |
| 150 int partsSinceCompaction = _parts.length - _partsCompactionIndex; | |
| 151 if (partsSinceCompaction == _PARTS_TO_COMPACT) { | |
| 152 _compact(); | |
| 153 } | |
| 154 } | |
| 155 } | |
| 156 | |
| 157 /** | |
| 158 * Compacts the last N parts if their average size allows us to save a | |
| 159 * lot of memory by turning them all into a single part. | |
| 160 */ | |
| 161 void _compact() { | |
| 162 if (_partsCodeUnitsSinceCompaction < _PARTS_TO_COMPACT_SIZE_LIMIT) { | |
| 163 if (_partsCompactionArray == null) { | |
| 164 _partsCompactionArray = new _ObjectArray(_PARTS_TO_COMPACT); | |
|
Lasse Reichstein Nielsen
2013/09/20 11:40:17
Consider whether reusing the array is worth it - i
kasperl
2013/09/20 12:04:27
I've tried both and reusing the array seems prefer
| |
| 165 } | |
| 166 for (int i = 0; i < _PARTS_TO_COMPACT; i++) { | |
| 167 _partsCompactionArray[i] = _parts[i + _partsCompactionIndex]; | |
| 168 } | |
| 169 String compacted = _StringBase._concatAllNative(_partsCompactionArray); | |
| 170 _parts.length = _parts.length - _PARTS_TO_COMPACT; | |
| 171 _parts.add(compacted); | |
| 172 for (int i = 0; i < _PARTS_TO_COMPACT; i++) { | |
| 173 _partsCompactionArray[i] = null; | |
| 174 } | |
| 175 } | |
| 176 _partsCodeUnitsSinceCompaction = 0; | |
| 177 _partsCompactionIndex = _parts.length; | |
| 178 } | |
| 179 | |
| 180 /** | |
| 103 * Create a [String] from the UFT-16 code units in buffer. | 181 * Create a [String] from the UFT-16 code units in buffer. |
| 104 */ | 182 */ |
| 105 static String _create(Uint16List buffer, int length, bool isLatin1) | 183 static String _create(Uint16List buffer, int length, bool isLatin1) |
| 106 native "StringBuffer_createStringFromUint16Array"; | 184 native "StringBuffer_createStringFromUint16Array"; |
| 107 } | 185 } |
| OLD | NEW |