View Javadoc

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 }