📄 arraybytelist.java
字号:
/*
* Copyright (c) 2003, The Regents of the University of California, through
* Lawrence Berkeley National Laboratory (subject to receipt of any required
* approvals from the U.S. Dept. of Energy). All rights reserved.
*/
package gov.lbl.dsd.sea.nio.util;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.Charset;
import java.util.BitSet;
/**
Efficient resizable auto-expanding list holding <code>byte</code> elements; implemented with arrays.
The API is intended for easy non-trivial high-throughput processing,
and (in an elegant and compact yet different form) provides all list and set functionality
of the java.util collections package, as well as a little more.
This API fills the gap between raw arrays (non-resizable), nio ByteBuffers (non-resizable) and
java.util.List and java.util.Set (resizable but not particularly useful for
<i>non-trivial high-throughput</i> processing on primitive types).
For example, this class is convenient to parse and/or assemble
variable-sized network messages if message lengths are a priori unknown.
<p>
Indexed element access is zero based: valid indexes range from
index <code>0</code> (inclusive) to index <code>list.size()</code> (exclusive).
Attempts to access out-of-range indexes will throw an {@link IndexOutOfBoundsException}.
<p>
<strong>Note that this implementation is not synchronized, hence not inherently thread safe.</strong>
<p>
Example usage:
<pre>
System.out.println(new ArrayByteList(new byte[] {0,1,2}));
// insert and replace
ArrayByteList demo = new ArrayByteList(new byte[] {0,1,2});
demo.replace(0,0, new byte[] {4,5}); // insert
System.out.println(demo); // yields [4,5,0,1,2]
demo.replace(0,2, new byte[] {6,7,8,9});
System.out.println(demo); // yields [6,7,8,9,0,1,2]
// sort, search and remove
System.out.println(demo.subList(1,3)); // yields [7,8]
demo.sort(true);
System.out.println(demo);
System.out.println(demo.binarySearch((byte) 7));
demo.remove(4, 4+1); // remove elem at index 4
System.out.println(demo);
System.out.println(demo.binarySearch((byte) 7));
// efficient file I/O
System.out.println(new ArrayByteList(0).add(new java.io.FileInputStream("/etc/passwd")).toString(null));
new java.io.FileOutputStream("/tmp/test").write(demo.asArray(), 0, demo.size());
System.out.println(new ArrayByteList(0).add(new java.io.FileInputStream("/tmp/test")));
System.out.println(new ArrayByteList(0).add(new java.io.FileInputStream("/tmp/test").getChannel()));
// network I/O via stream
java.nio.charset.Charset charset = java.nio.charset.Charset.forName("ISO-8859-1");
System.out.println(new ArrayByteList(0).add(new java.net.URL("http://www.google.com").openStream()).toString(charset));
// simple HTTP via raw socket channel
java.nio.channels.SocketChannel channel = java.nio.channels.SocketChannel.open();
channel.connect(new java.net.InetSocketAddress("www.google.com", 80));
channel.write(new ArrayByteList("GET / HTTP/1.0" + "\r\n\r\n", charset).asByteBuffer());
System.out.println(new ArrayByteList(0).add(channel).toString(charset));
</pre>
<p>
Manipulating primitive values other than bytes is not directly
supported. However, this can be done via <code>asByteBuffer()</code>
along the following lines:
<pre>
// get and set 4 byte integer value at end of list:
list = ...
int val = list.asByteBuffer().getInt(list.size() - 4);
list.asByteBuffer().setInt(list.size() - 4, val * 10);
// append 8 byte double value:
list = ...
double elemToAdd = 1234.0;
list.replace(list.size(), list.size(), (byte)0, 8); // add 8 bytes at end
list.asByteBuffer().putDouble(list.size() - 8, elemToAdd);
// insert 8 byte double value at beginning:
list = ...
double elemToInsert = 1234.0;
list.replace(0, 0, 0, 8); // insert 8 bytes at beginning
list.asByteBuffer().putDouble(0, elemToInsert);
</pre>
This class requires JDK 1.4 or higher; otherwise it has zero dependencies.
Hence you can simply copy the file into your own project if minimal dependencies are desired.
<p>
Also note that the compiler can (and will) easily inline all methods, then optimize away.
In fact with the Sun jdk-1.4.2 server VM it is hard to measure any difference
to raw array manipulations at abstraction level zero.
@author whoschek@lbl.gov
@author $Author: hoschek3 $
@version $Revision: 1.52 $, $Date: 2004/12/01 21:00:24 $
*/
public final class ArrayByteList implements java.io.Serializable {
/**
* The array into which the elements of the list are stored. The
* capacity of the list is the length of this array.
*/
private transient byte[] elements;
/**
* The current number of elements contained in this list.
*/
private int size;
/**
* For compatibility across versions
*/
private static final long serialVersionUID = -6250350905005960078L;
/**
* The default charset is UTF-8, and fixed for interoperability.
* Note that the first 128 character codes of UTF-8, ISO-8859-1 and US-ASCII are identical.
* Hence these charsets are equivalent for most practical purposes.
* This charset is used on most operating systems (e.g. for system files, config files, log files, scripts, source code, etc.)
* Note that in Java UTF-8 and ISO-8859-1 always works since JDK support for it is required by the JDK spec.
*/
private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
/**
* Constructs an empty list.
*/
public ArrayByteList() {
this(64);
}
/**
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity
* the number of elements the receiver can hold without
* auto-expanding itself by allocating new internal memory.
*/
public ArrayByteList(int initialCapacity) {
elements = new byte[initialCapacity];
size = 0;
}
/**
* Constructs a list SHARING the specified elements. The initial size and
* capacity of the list is the length of the backing array.
* <p>
* <b>WARNING: </b> For efficiency reasons and to keep memory usage low,
* <b>the array is SHARED, not copied </b>. So if subsequently you modify
* the specified array directly via the [] operator, be sure you know what
* you're doing.
* <p>
* If you rather need copying behaviour, use
* <code>copy = new ArrayByteList(byte[] elems).copy()</code> or similar.
* <p>
* If you need a list containing a copy of <code>elems[from..to)</code>, use
* <code>list = new ArrayByteList(to-from).add(elems, from, to-from)</code>
* or <code>list = new ArrayByteList(ByteBuffer.wrap(elems, from, to-from))</code>
* or similar.
*
* @param elems
* the array backing the constructed list
*/
public ArrayByteList(byte[] elems) {
elements = elems;
size = elems.length;
}
/**
* Constructs a list containing a copy of the remaining buffer elements. The
* initial size and capacity of the list is <code>elems.remaining()</code>.
*
* @param elems
* the elements initially to be added to the list
*/
public ArrayByteList(ByteBuffer elems) {
this(elems.remaining());
add(elems);
}
/**
* Constructs a list containing a copy of the encoded form of the given
* char sequence (String, StringBuffer, CharBuffer, etc).
*
* @param str
* the string to convert.
* @param charset
* the charset to convert with (e.g. <code>Charset.forName("US-ASCII")</code>,
* <code>Charset.forName("ISO-8859-1")</code>).
* If <code>null</code> uses <code>Charset.forName("UTF-8")</code> as the default charset.
*/
public ArrayByteList(CharSequence str, Charset charset) {
this(getCharset(charset).encode(CharBuffer.wrap(str)));
}
/**
* Appends the specified element to the end of this list.
*
* @param elem
* element to be appended to this list.
* @return <code>this</code> (for chaining convenience only)
*/
public ArrayByteList add(byte elem) {
if (size == elements.length) ensureCapacity(size + 1);
elements[size++] = elem;
return this;
// equally correct alternative impl: insert(size, elem);
}
/**
* Appends the elements in the range <code>[offset..offset+length)</code> to the end of this list.
*
* @param elems the elements to be appended
* @param offset the offset of the first element to add (inclusive)
* @param length the number of elements to add
* @return <code>this</code> (for chaining convenience only)
* @throws IndexOutOfBoundsException if indexes are out of range.
*/
public ArrayByteList add(byte[] elems, int offset, int length) {
if (offset < 0 || length < 0 || offset + length > elems.length)
throw new IndexOutOfBoundsException("offset: " + offset + ", length: " + length + ", elems.length: " + elems.length);
ensureCapacity(size + length);
System.arraycopy(elems, offset, this.elements, size, length);
size += length;
return this;
// equally correct alternative impl: replace(size, size, elems, offset, length);
}
/**
* Appends the specified elements to the end of this list.
*
* @param elems
* elements to be appended.
* @return <code>this</code> (for chaining convenience only)
*/
public ArrayByteList add(ArrayByteList elems) {
replace(size, size, elems);
return this;
}
/**
* Appends the remaining buffer elements to the end of this list.
*
* @param elems
* elements to be appended.
* @return <code>this</code> (for chaining convenience only)
*/
public ArrayByteList add(ByteBuffer elems) {
int length = elems.remaining();
ensureCapacity(size + length);
elems.get(this.elements, size, length);
size += length;
return this;
// equally correct alternative impl: replace(size, size, elems, elems.remaining());
}
/**
* Appends the encoded form of the given char sequence (String, StringBuffer, CharBuffer, etc).
*
* @param str
* the string to convert.
* @param charset
* the charset to convert with (e.g. <code>Charset.forName("US-ASCII")</code>,
* <code>Charset.forName("ISO-8859-1")</code>).
* If <code>null</code> uses <code>Charset.forName("UTF-8")</code> as the default charset.
* @return <code>this</code> (for chaining convenience only)
*/
public ArrayByteList add(CharSequence str, Charset charset) {
return add(getCharset(charset).encode(CharBuffer.wrap(str)));
}
/**
* Appends the remaining elements of the stream to the end of this list,
* reading until end-of-stream. Finally closes the stream. Note that the
* implementation is efficient even if the input stream is not a buffered
* stream.
*
* @param elems
* the input stream to read from.
* @return <code>this</code> (for chaining convenience only)
* @throws IOException
* if an I/O error occurs.
*/
public ArrayByteList add(InputStream elems) throws IOException {
// Note that our algo is correct and efficient even if
// the input stream implements available() in weird or buggy ways.
try {
ensureCapacity(size + 1 + Math.max(0, elems.available()));
int n;
while ((n = elems.read(elements, size, elements.length - size)) >= 0) {
size += n;
// increasingly make room for next read (and defensively
// ensure we don't spin loop, attempting to read zero bytes per iteration)
ensureCapacity(size + Math.max(1, elems.available()));
}
}
finally {
if (elems != null) elems.close();
}
return this;
}
/**
* Appends the remaining elements of the channel to the end of this list,
* reading until end-of-stream. Finally closes the channel.
*
* @param elems
* the channel to read from.
* @return <code>this</code> (for chaining convenience only)
* @throws IOException
* if an I/O error occurs.
*/
public ArrayByteList add(ReadableByteChannel elems) throws IOException {
try {
int remaining = 8192;
if (elems instanceof FileChannel) { // we can be more efficient
long rem = ((FileChannel) elems).size() - ((FileChannel) elems).position();
if (size + 1 + rem > Integer.MAX_VALUE) throw new IllegalArgumentException("File channel too large (2 GB limit exceeded)");
remaining = (int) rem;
}
ensureCapacity(size + 1 + remaining);
int n;
while ((n = elems.read(ByteBuffer.wrap(elements, size, elements.length - size))) >= 0) {
size += n;
// increasingly make room for next read (and defensively
// ensure we don't spin loop, attempting to read zero bytes per iteration)
ensureCapacity(size + 1);
}
}
finally {
if (elems != null) elems.close();
}
return this;
}
/**
* Returns the elements currently stored, including invalid elements between
* size and capacity, if any.
* <p>
* <b>WARNING: </b> For efficiency reasons and to keep memory usage low,
* <b>the array is SHARED, not copied </b>. So if subsequently you modify the
* returned array directly via the [] operator, be sure you know what you're
* doing.
*
* @return the elements currently stored.
*/
public byte[] asArray() {
return elements;
}
/**
* Returns a buffer SHARING elements with the receiver. The buffer will
* have the default NIO byte order, which is ByteOrder.BIG_ENDIAN.
* <p>
* <b>WARNING: </b> For efficiency reasons and to keep memory usage low,
* <b>the array is SHARED, not copied </b>. So if subsequently you modify the
* returned buffer, be sure you know what you're doing.
*/
public ByteBuffer asByteBuffer() {
return ByteBuffer.wrap(elements, 0, size);
}
/**
* Creates and returns an unsynchronized output stream that appends to this
* SHARED backing byte list. Useful if legacy code requires adapting to a
* stream based interface. Note: This is more efficient and
* straighforward than using a {@link java.io.ByteArrayOutputStream}.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -