1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 package org.apache.hadoop.hbase.util; 20 21 22 23 24 /** 25 * Lightweight, reusable class for specifying ranges of byte[]'s. CompareTo and equals methods are 26 * lexicographic, which is native to HBase. 27 * <p/> 28 * This class differs from ByteBuffer: 29 * <li/>On-heap bytes only 30 * <li/>Implements equals, hashCode, and compareTo so that it can be used in standard java 31 * Collections, similar to String. 32 * <li/>Does not maintain mark/position iterator state inside the class. Doing so leads to many bugs 33 * in complex applications. 34 * <li/>Allows the addition of simple core methods like this.copyTo(that, offset). 35 * <li/>Can be reused in tight loops like a major compaction which can save significant amounts of 36 * garbage. 37 * <li/>(Without reuse, we throw off garbage like this thing: 38 * http://www.youtube.com/watch?v=lkmBH-MjZF4 39 * <p/> 40 * Mutable, and always evaluates equals, hashCode, and compareTo based on the current contents. 41 * <p/> 42 * Can contain convenience methods for comparing, printing, cloning, spawning new arrays, copying to 43 * other arrays, etc. Please place non-core methods into {@link ByteRangeTool}. 44 * <p/> 45 * We may consider converting this to an interface and creating separate implementations for a 46 * single byte[], a paged byte[] (growable byte[][]), a ByteBuffer, etc 47 */ 48 public class ByteRange implements Comparable<ByteRange> { 49 50 private static final int UNSET_HASH_VALUE = -1; 51 52 53 /********************** fields *****************************/ 54 55 // Do not make these final, as the intention is to reuse objects of this class 56 57 /** 58 * The array containing the bytes in this range. It will be >= length. 59 */ 60 private byte[] bytes; 61 62 /** 63 * The index of the first byte in this range. ByteRange.get(0) will return bytes[offset]. 64 */ 65 private int offset; 66 67 /** 68 * The number of bytes in the range. Offset + length must be <= bytes.length 69 */ 70 private int length; 71 72 /** 73 * Variable for lazy-caching the hashCode of this range. Useful for frequently used ranges, 74 * long-lived ranges, or long ranges. 75 */ 76 private int hash = UNSET_HASH_VALUE; 77 78 79 /********************** construct ***********************/ 80 81 public ByteRange() { 82 set(new byte[0]);//Could probably get away with a null array if the need arises. 83 } 84 85 public ByteRange(byte[] bytes) { 86 set(bytes); 87 } 88 89 public ByteRange(byte[] bytes, int offset, int length) { 90 set(bytes, offset, length); 91 } 92 93 94 /********************** write methods *************************/ 95 96 public ByteRange clear() { 97 clearHashCache(); 98 bytes = null; 99 offset = 0; 100 length = 0; 101 return this; 102 } 103 104 public ByteRange set(byte[] bytes) { 105 clearHashCache(); 106 this.bytes = bytes; 107 this.offset = 0; 108 this.length = ArrayUtils.length(bytes); 109 return this; 110 } 111 112 public ByteRange set(byte[] bytes, int offset, int length) { 113 clearHashCache(); 114 this.bytes = bytes; 115 this.offset = offset; 116 this.length = length; 117 return this; 118 } 119 120 public void setLength(int length) { 121 clearHashCache(); 122 this.length = length; 123 } 124 125 126 /*********** read methods (add non-core methods to ByteRangeUtils) *************/ 127 128 /** 129 * @param index zero-based index 130 * @return single byte at index 131 */ 132 public byte get(int index) { 133 return bytes[offset + index]; 134 } 135 136 /** 137 * Instantiate a new byte[] with exact length, which is at least 24 bytes + length. Copy the 138 * contents of this range into it. 139 * @return The newly cloned byte[]. 140 */ 141 public byte[] deepCopyToNewArray() { 142 byte[] result = new byte[length]; 143 System.arraycopy(bytes, offset, result, 0, length); 144 return result; 145 } 146 147 /** 148 * Create a new ByteRange with new backing byte[] and copy the state of this range into the new 149 * range. Copy the hash over if it is already calculated. 150 * @return Deep copy 151 */ 152 public ByteRange deepCopy() { 153 ByteRange clone = new ByteRange(deepCopyToNewArray()); 154 if (isHashCached()) { 155 clone.hash = hash; 156 } 157 return clone; 158 } 159 160 /** 161 * Wrapper for System.arraycopy. Copy the contents of this range into the provided array. 162 * @param destination Copy to this array 163 * @param destinationOffset First index in the destination array. 164 */ 165 public void deepCopyTo(byte[] destination, int destinationOffset) { 166 System.arraycopy(bytes, offset, destination, destinationOffset, length); 167 } 168 169 /** 170 * Wrapper for System.arraycopy. Copy the contents of this range into the provided array. 171 * @param innerOffset Start copying from this index in this source ByteRange. First byte copied is 172 * bytes[offset + innerOffset] 173 * @param copyLength Copy this many bytes 174 * @param destination Copy to this array 175 * @param destinationOffset First index in the destination array. 176 */ 177 public void deepCopySubRangeTo(int innerOffset, int copyLength, byte[] destination, 178 int destinationOffset) { 179 System.arraycopy(bytes, offset + innerOffset, destination, destinationOffset, copyLength); 180 } 181 182 /** 183 * Create a new ByteRange that points at this range's byte[]. The new range can have different 184 * values for offset and length, but modifying the shallowCopy will modify the bytes in this 185 * range's array. Pass over the hash code if it is already cached. 186 * @param innerOffset First byte of clone will be this.offset + copyOffset. 187 * @param copyLength Number of bytes in the clone. 188 * @return new ByteRange object referencing this range's byte[]. 189 */ 190 public ByteRange shallowCopySubRange(int innerOffset, int copyLength) { 191 ByteRange clone = new ByteRange(bytes, offset + innerOffset, copyLength); 192 if (isHashCached()) { 193 clone.hash = hash; 194 } 195 return clone; 196 } 197 198 //TODO move to ByteRangeUtils because it is non-core method 199 public int numEqualPrefixBytes(ByteRange that, int thatInnerOffset) { 200 int maxCompares = Math.min(length, that.length - thatInnerOffset); 201 for (int i = 0; i < maxCompares; ++i) { 202 if (bytes[offset + i] != that.bytes[that.offset + thatInnerOffset + i]) { 203 return i; 204 } 205 } 206 return maxCompares; 207 } 208 209 public byte[] getBytes() { 210 return bytes; 211 } 212 213 public int getOffset() { 214 return offset; 215 } 216 217 public int getLength() { 218 return length; 219 } 220 221 public boolean isEmpty(){ 222 return isEmpty(this); 223 } 224 225 public boolean notEmpty(){ 226 return notEmpty(this); 227 } 228 229 230 /******************* static methods ************************/ 231 232 public static boolean isEmpty(ByteRange range){ 233 return range == null || range.length == 0; 234 } 235 236 public static boolean notEmpty(ByteRange range){ 237 return range != null && range.length > 0; 238 } 239 240 /******************* standard methods *********************/ 241 242 @Override 243 public boolean equals(Object thatObject) { 244 if (thatObject == null){ 245 return false; 246 } 247 if (this == thatObject) { 248 return true; 249 } 250 if (hashCode() != thatObject.hashCode()) { 251 return false; 252 } 253 if (!(thatObject instanceof ByteRange)) { 254 return false; 255 } 256 ByteRange that = (ByteRange) thatObject; 257 return Bytes.equals(bytes, offset, length, that.bytes, that.offset, that.length); 258 } 259 260 @Override 261 public int hashCode() { 262 if (isHashCached()) {// hash is already calculated and cached 263 return hash; 264 } 265 if (this.isEmpty()) {// return 0 for empty ByteRange 266 hash = 0; 267 return hash; 268 } 269 int off = offset; 270 hash = 0; 271 for (int i = 0; i < length; i++) { 272 hash = 31 * hash + bytes[off++]; 273 } 274 return hash; 275 } 276 277 private boolean isHashCached() { 278 return hash != UNSET_HASH_VALUE; 279 } 280 281 private void clearHashCache() { 282 hash = UNSET_HASH_VALUE; 283 } 284 285 /** 286 * Bitwise comparison of each byte in the array. Unsigned comparison, not paying attention to 287 * java's signed bytes. 288 */ 289 @Override 290 public int compareTo(ByteRange other) { 291 return Bytes.compareTo(bytes, offset, length, other.bytes, other.offset, other.length); 292 } 293 294 @Override 295 public String toString() { 296 return Bytes.toStringBinary(bytes, offset, length); 297 } 298 299 }