深入学习java源码之ByteBuffer.getDouble()与ByteBuffer.put()
hashCode方法及equals方法
在Java中,hashCode
方法和 equals
方法都是 java.lang.Object 类的方法,
简而言之,equals
是判断两个对象是否等价的方法,而 hashCode
则是为散列数据结构服务的计算散列值的方法。下面分别对这两个方法进行讨论。
equals方法
equals
方法注重 两个对象在逻辑上是否相等。重写 equals
方法看似比较简单,但实际编写的时候还是要考虑具体类的意义。
一般来说,以下几种情况不需要重写 equals
方法:
- 一个类的每个实例在本质上都是独立的、不同的,比如
Thread
类 - 不需要
equals
方法,也就是判断对象相等的逻辑是没有必要的 - 父类已重写
equals
方法,并且子类的判断逻辑与父类相符 - 一个类的访问权限为 private 或 package-private,并且可以确定
equals
方法不会被调用
那么,另外的情况通常需要重写 equals
方法。一般来说,equals 方法遵循离散数学中的 等价关系(equivalence relation)。从 OOP 的角度来说,等价关系三原则如下:
- 自反性(Reflexive):一个对象与自身相等,即 x=xx=x。对任何非空对象 x,
x.equals(x)
必定为 true。 - 对称性(Symmetric):对象之间的等价关系是可交换的,即 a=b⇔b=aa=b⇔b=a。对任何非空对象 x、y,
x.equals(y)
为 true,当且仅当 y.equals(x)
为 true。 - 传递性(Transitive):(a=b)∧(b=c)⇒(a=c)(a=b)∧(b=c)⇒(a=c)。对任何非空对象 x、y、z, 若
x.equals(y)
为 true 且 y.equals(z)
为 true,则 x.equals(z)
为 true。
除了遵循这三原则之外,还要遵循:
- 一致性(Consistent):对任何非空对象 x、y,只要所比较的信息未变,则连续调用
x.equals(y)
总是得到一致的结果。这要求了 equals
所依赖的值必须是可靠的。 - 对任何非空对象 x,
x.equals(null)
必定为 false。
所以,根据上面的原则,equals
函数的一个比较好的实践如下:
- 首先先判断传入的对象与自身是否为同一对象,如果是的话直接返回
true
。这相当于一种性能优化,尤其是在各种比较操作代价高昂的时候,这种优化非常有效。 - 判断对象是否为正确的类型。若此方法接受子类,即子类判断等价的逻辑与父类相同,则可以用
instanceof
操作符;若逻辑不同,即仅接受当前类型,则可以用 getClass
方法获取 Class 对象来判断。注意使用 getClass
方法时必须保证非空,而用 instanceof
操作符则不用非空(null instanceof o
的值为 false)。 - 将对象转换为相应的类型:由于前面已经做过校验,因此这里做类型转换的时候不应当抛出 ClassCastException 异常。
- 进行对应的判断逻辑
几个注意事项:
- 我们无法在扩展一个可实例化的类的同时,即增加新的成员变量,同时又保留原先的
equals
约定 - 注意不要写错
equals
方法的参数类型,标准的应该是 public boolean equals(Object o)
,若写错就变成重载而不是重写了 - 不要让
equals
变得太“智能”而使其性能下降 - 如果重写了
equals
方法,则一定要重写 hashCode
方法(具体见下面)
equals和==
equals
方法用来判断 两个对象在逻辑上是否相等,而 ==
用来判断两个引用对象是否指向同一个对象(是否为同一个对象)。
String str1 = "Fucking Scala";
String str2 = new String("Fucking Scala");
String str3 = new String("Fucking Scala");
String str4 = "Fucking Scala";
System.out.println(str1 == str2); // false
System.out.println(str2 == str3); // false
System.out.println(str2.equals(str3)); // true
System.out.println(str1 == str4); // true
str4 = "Fuck again!";
String str5 = "Fuck again!";
System.out.println(str1 == str4); // false
System.out.println(str4 == str5); // true
hashCode 方法
如果重写了equals方法,则一定要重写hashCode方法。
重写 hashCode
方法的原则如下:
- 在程序执行期间,只要equals方法的比较操作用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法必须始终如一地返回同一个整数
- 如果两个对象通过equals方法比较得到的结果是相等的,那么对这两个对象进行hashCode得到的值应该相同
- 两个不同的对象,hashCode的结果可能是相同的,这就是哈希表中的冲突。为了保证哈希表的效率,哈希算法应尽可能的避免冲突
关于相应的哈希算法,一个简单的算法如下:
- 永远不要让哈希算法返回一个常值,这时哈希表将退化成链表,查找时间复杂度也从 O(1)O(1) 退化到 O(N)O(N)
- 如果参数是boolean型,计算
(f ? 1 : 0)
- 如果参数是byte, char, short或者int型,计算
(int) f
- 如果参数是long型,计算
(int) (f ^ (f >>> 32))
- 如果参数是float型,计算
Float.floatToIntBits(f)
- 如果参数是double型,计算
Double.doubleToLongBits(f)
得到long类型的值,再根据公式计算出相应的hash值 - 如果参数是Object型,那么应计算其有用的成员变量的hash值,并按照下面的公式计算最终的hash值
- 如果参数是个数组,那么把数组中的每个值都当做单独的值,分别按照上面的方法单独计算hash值,最后按照下面的公式计算最终的hash值
组合公式:result = 31 * result + c
比如,String 类的 hashCode
方法如下(JDK 1.8):
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
自定义类的 hashCode
例子:
class Baz {
private int id;
private String name;
private double weight;
private float height;
private String note;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Baz baz = (Baz) o;
if (id != baz.id) return false;
if (Double.compare(baz.weight, weight) != 0) return false;
if (Float.compare(baz.height, height) != 0) return false;
if (name != null ? !name.equals(baz.name) : baz.name != null) return false;
return !(note != null ? !note.equals(baz.note) : baz.note != null);
}
@Override
public int hashCode() {
int result;
long temp;
result = id;
result = 31 * result + (name != null ? name.hashCode() : 0);
temp = Double.doubleToLongBits(weight);
result = 31 * result + (int) (temp ^ (temp >>> 32));
result = 31 * result + (height != +0.0f ? Float.floatToIntBits(height) : 0);
result = 31 * result + (note != null ? note.hashCode() : 0);
return result;
}
}
final修饰的引用变量
public class Demo {
public static void main(String[] args) {
final StringBuilder sb = new StringBuilder("haha");
//同一对象的hashCode值相同
System.out.println("sb中的内容是:" + sb);
System.out.println(sb + "的哈希编码是:" + sb.hashCode());
sb.append("我变了");
System.out.println("sb中的内容是:" + sb);
System.out.println(sb + "的哈希编码是:" + sb.hashCode());
System.out.println("-----------------哈希值-------------------");
Demo test = new Demo();
System.out.println(test.hashCode());
System.out.println(Integer.toHexString(test.hashCode()));
System.out.println(test.getClass().getName() + "@" + Integer.toHexString(test.hashCode()));
//在API中这么定义toString()等同于 getClass().getName() + '@' + Integer.toHexString(hashCode())
//返回值是 a string representation of the object.
System.out.println(test.toString());
}
}
结果:
sb中的内容是:haha
haha的哈希编码是:396873410
sb中的内容是:haha我变了
haha我变了的哈希编码是:396873410
-----------------哈希值-------------------
1706234378
65b3120a
demo1.Demo@65b3120a
demo1.Demo@65b3120a
分析结果,两次的hashCode都是一样的,表示引用变量没变,引用变量所指向的对象中的内容还是可以改变的
总得来说对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
java源码
字节缓冲区是直接 或非直接的 。 给定一个直接字节缓冲区,Java虚拟机将尽力在其上直接执行本地I / O操作。 也就是说,它将尝试避免在每次调用其中一个底层操作系统的本机I / O操作之前(或之后)将缓冲区的内容复制到(或从)中间缓冲区。
可以通过调用此类的allocateDirect工厂方法来创建直接字节缓冲区。 此方法返回的缓冲区通常比非直接缓冲区具有更高的分配和释放成本。 直接缓冲区的内容可能驻留在正常的垃圾回收堆之外,因此它们对应用程序的内存占用的影响可能不明显。 因此,建议直接缓冲区主要用于受基础系统本机I / O操作影响的大型长寿命缓冲区。 一般来说,最好只在产生程序性能可测量的增益时才分配直接缓冲区。
直接字节缓冲区也可以由文件的mapping直接创建到内存区域。 Java平台的实现可以可选地支持通过JNI从本地代码创建直接字节缓冲器。 如果这些缓冲区之一的实例指的是存储器的不可访问的区域,则访问该区域的尝试将不会改变缓冲区的内容,并且将导致在访问时或之后的某个未指定的异常时间。
字节缓冲区是直接还是非直接可以通过调用其isDirect方法来确定 。 提供了这种方法,使得可以在性能关键代码中进行显式缓冲区管理。
访问二进制数据
该类定义了用于读取和写入所有其他原始类型的值的方法,但boolean除外。 原始值根据缓冲区的当前字节顺序转换为(或从)字节序列,可以通过order方法检索和修改。 特定字节顺序由ByteOrder类的实例表示。 字节缓冲区的初始顺序始终为BIG_ENDIAN 。
对于访问异构二进制数据,即不同类型的值序列,该类定义了每个类型的绝对和相对get和put方法族。 例如,对于32位浮点值,此类定义:
float getFloat()
float getFloat(int index)
void putFloat(float f)
void putFloat(int index, float f)
相应的方法被用于类型char,short,int,long和double限定。 绝对get和put方法的索引参数是以字节为单位,而不是读取或写入的类型。
对于访问同构二进制数据,即相同类型的值的序列,此类定义可以创建给定字节缓冲区的视图的方法。 视图缓冲区只是另一个缓冲区,其内容由字节缓冲区支持。 对字节缓冲区内容的更改将在视图缓冲区中可见,反之亦然; 两个缓冲区的位置,极限和标记值是独立的。 例如, asFloatBuffer方法创建了由调用该方法的字节缓冲区支持的FloatBuffer类的实例。 相应的视图创建方法的各类char,short,int,long和double限定。
与上述类型特定的get和put方法相比,查看缓冲区有三个重要的优点:
在创建视图时,视图缓冲区的字节顺序被固定为其字节缓冲区的字节顺序。
调用链接
指定此类中没有值返回值的方法返回调用它们的缓冲区。 这允许方法调用被链接。 语句序列
bb.putInt(0xCAFEBABE);
bb.putShort(3);
bb.putShort(45);
例如,可以用单个语句来替换
bb.putInt(0xCAFEBABE).putShort(3).putShort(45);
视图缓冲区的索引不是以字节为单位,而是根据其值的类型特定大小;
视图缓冲器提供相对的批量获取和放置方法,该方法可以在缓冲区和数组之间传递连续序列值或者相同类型的其他缓冲区; 和
视图缓冲区可能会更有效率,因为只有当它的后备字节缓冲区是直接的时才是直接的。
Modifier and Type
| Method and Description
|
static ByteBuffer | allocate(int capacity) 分配一个新的字节缓冲区。 |
static ByteBuffer | allocateDirect(int capacity) 分配一个新的直接字节缓冲区。 |
byte[] | array() 返回支持此缓冲区的字节数组 (可选操作) 。 |
int | arrayOffset() 返回该缓冲区的缓冲区的第一个元素的背衬数组中的偏移量 (可选操作) 。 |
abstract CharBuffer | asCharBuffer() 创建一个字节缓冲区作为char缓冲区的视图。 |
abstract DoubleBuffer | asDoubleBuffer() 将此字节缓冲区的视图创建为双缓冲区。 |
abstract FloatBuffer | asFloatBuffer() 将此字节缓冲区的视图创建为浮动缓冲区。 |
abstract IntBuffer | asIntBuffer() 将此字节缓冲区的视图创建为int缓冲区。 |
abstract LongBuffer | asLongBuffer() 将此字节缓冲区的视图创建为长缓冲区。 |
abstract ByteBuffer | asReadOnlyBuffer() 创建一个新的只读字节缓冲区,共享此缓冲区的内容。 |
abstract ShortBuffer | asShortBuffer() 将此字节缓冲区的视图创建为短缓冲区。 |
abstract ByteBuffer | compact() 压缩此缓冲区 (可选操作) 。 |
int | compareTo(ByteBuffer 将此缓冲区与另一个缓冲区进行比较。 |
abstract ByteBuffer | duplicate() 创建一个新的字节缓冲区,共享此缓冲区的内容。 |
boolean | equals(Object 告诉这个缓冲区是否等于另一个对象。 |
abstract byte | get() 相对 获取方法。 |
ByteBuffer | get(byte[] dst) 相对批量 获取方法。 |
ByteBuffer | get(byte[] dst, int offset, int length) 相对批量 获取方法。 |
abstract byte | get(int index) 绝对 获取方法。 |
abstract char | getChar() 读取char值的相对 get方法。 |
abstract char | getChar(int index) 绝对 获取方法来读取一个char值。 |
abstract double | getDouble() 读取双重值的相对 get方法。 |
abstract double | getDouble(int index) 绝对 获取读取双重值的方法。 |
abstract float | getFloat() 读取浮点值的相对 get方法。 |
abstract float | getFloat(int index) 用于读取浮点值的绝对 get方法。 |
abstract int | getInt() 用于读取int值的相对 get方法。 |
abstract int | getInt(int index) 用于读取int值的绝对 get方法。 |
abstract long | getLong() 读取长值的相对 get方法。 |
abstract long | getLong(int index) 绝对 获取读取长值的方法。 |
abstract short | getShort() 相对 获取方法读取一个简短的值。 |
abstract short | getShort(int index) 绝对 获取读取一个简短值的方法。 |
boolean | hasArray() 告诉这个缓冲区是否由可访问的字节数组支持。 |
int | hashCode() 返回此缓冲区的当前哈希码。 |
abstract boolean | isDirect() 告诉这个字节缓冲区是否是直接的。 |
ByteOrder | order() 检索此缓冲区的字节顺序。 |
ByteBuffer | order(ByteOrder 修改缓冲区的字节顺序。 |
abstract ByteBuffer | put(byte b) 相对 放置法 (可选操作) 。 |
ByteBuffer | put(byte[] src) 相对大容量 put方法 (可选操作) 。 |
ByteBuffer | put(byte[] src, int offset, int length) 相对大容量 put方法 (可选操作) 。 |
ByteBuffer | put(ByteBuffer 相对大容量 put方法 (可选操作) 。 |
abstract ByteBuffer | put(int index, byte b) 绝对 put方法 (可选操作) 。 |
abstract ByteBuffer | putChar(char value) 写入char值的相对 put方法 (可选操作) 。 |
abstract ByteBuffer | putChar(int index, char value) 用于写入char值的绝对 put方法 (可选操作) 。 |
abstract ByteBuffer | putDouble(double value) 写入double值的相对 put方法 (可选操作) 。 |
abstract ByteBuffer | putDouble(int index, double value) 用于写入双精度值的绝对 put方法 (可选操作) 。 |
abstract ByteBuffer | putFloat(float value) 编写浮点值的相对 put方法 (可选操作) 。 |
abstract ByteBuffer | putFloat(int index, float value) 用于写入浮点值的绝对 put方法 (可选操作) 。 |
abstract ByteBuffer | putInt(int value) 编写int值的相对 put方法 (可选操作) 。 |
abstract ByteBuffer | putInt(int index, int value) 用于写入int值的绝对 put方法 (可选操作) 。 |
abstract ByteBuffer | putLong(int index, long value) 绝对 put方法写入一个长的值 (可选操作) 。 |
abstract ByteBuffer | putLong(long value) 写入长值的相对 put方法 (可选操作) 。 |
abstract ByteBuffer | putShort(int index, short value) 绝对 put方法写入一个简短的值 (可选操作) 。 |
abstract ByteBuffer | putShort(short value) 写入一个短值的相对 放置方法 (可选操作) 。 |
abstract ByteBuffer | slice() 创建一个新的字节缓冲区,其内容是此缓冲区内容的共享子序列。 |
String | toString() 返回一个汇总此缓冲区状态的字符串。 |
static ByteBuffer | wrap(byte[] array) 将一个字节数组包装到缓冲区中。 |
static ByteBuffer | wrap(byte[] array, int offset, int length) 将一个字节数组包装到缓冲区中。 |
package java.nio;
public abstract class ByteBuffer
extends Buffer
implements Comparable<ByteBuffer>
{
// These fields are declared here rather than in Heap-X-Buffer in order to
// reduce the number of virtual method invocations needed to access these
// values, which is especially costly when coding small buffers.
//
final byte[] hb; // Non-null only for heap buffers
final int offset;
boolean isReadOnly; // Valid only for heap buffers
// Creates a new buffer with the given mark, position, limit, capacity,
// backing array, and array offset
//
ByteBuffer(int mark, int pos, int lim, int cap, // package-private
byte[] hb, int offset)
{
super(mark, pos, lim, cap);
this.hb = hb;
this.offset = offset;
}
// Creates a new buffer with the given mark, position, limit, and capacity
//
ByteBuffer(int mark, int pos, int lim, int cap) { // package-private
this(mark, pos, lim, cap, null, 0);
}
public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}
public static ByteBuffer allocate(int capacity) {
if (capacity < 0)
throw new IllegalArgumentException();
return new HeapByteBuffer(capacity, capacity);
}
public static ByteBuffer wrap(byte[] array,
int offset, int length)
{
try {
return new HeapByteBuffer(array, offset, length);
} catch (IllegalArgumentException x) {
throw new IndexOutOfBoundsException();
}
}
public static ByteBuffer wrap(byte[] array) {
return wrap(array, 0, array.length);
}
public abstract ByteBuffer slice();
public abstract ByteBuffer duplicate();
public abstract ByteBuffer asReadOnlyBuffer();
public abstract byte get();
public abstract ByteBuffer put(byte b);
public abstract byte get(int index);
public abstract ByteBuffer put(int index, byte b);
public ByteBuffer get(byte[] dst, int offset, int length) {
checkBounds(offset, length, dst.length);
if (length > remaining())
throw new BufferUnderflowException();
int end = offset + length;
for (int i = offset; i < end; i++)
dst[i] = get();
return this;
}
public ByteBuffer get(byte[] dst) {
return get(dst, 0, dst.length);
}
public ByteBuffer put(ByteBuffer src) {
if (src == this)
throw new IllegalArgumentException();
if (isReadOnly())
throw new ReadOnlyBufferException();
int n = src.remaining();
if (n > remaining())
throw new BufferOverflowException();
for (int i = 0; i < n; i++)
put(src.get());
return this;
}
public ByteBuffer put(byte[] src, int offset, int length) {
checkBounds(offset, length, src.length);
if (length > remaining())
throw new BufferOverflowException();
int end = offset + length;
for (int i = offset; i < end; i++)
this.put(src[i]);
return this;
}
public final ByteBuffer put(byte[] src) {
return put(src, 0, src.length);
}
public final boolean hasArray() {
return (hb != null) && !isReadOnly;
}
public final byte[] array() {
if (hb == null)
throw new UnsupportedOperationException();
if (isReadOnly)
throw new ReadOnlyBufferException();
return hb;
}
public final int arrayOffset() {
if (hb == null)
throw new UnsupportedOperationException();
if (isReadOnly)
throw new ReadOnlyBufferException();
return offset;
}
public abstract ByteBuffer compact();
public abstract boolean isDirect();
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append(getClass().getName());
sb.append("[pos=");
sb.append(position());
sb.append(" lim=");
sb.append(limit());
sb.append(" cap=");
sb.append(capacity());
sb.append("]");
return sb.toString();
}
public int hashCode() {
int h = 1;
int p = position();
for (int i = limit() - 1; i >= p; i--)
h = 31 * h + (int)get(i);
return h;
}
public boolean equals(Object ob) {
if (this == ob)
return true;
if (!(ob instanceof ByteBuffer))
return false;
ByteBuffer that = (ByteBuffer)ob;
if (this.remaining() != that.remaining())
return false;
int p = this.position();
for (int i = this.limit() - 1, j = that.limit() - 1; i >= p; i--, j--)
if (!equals(this.get(i), that.get(j)))
return false;
return true;
}
private static boolean equals(byte x, byte y) {
return x == y;
}
public int compareTo(ByteBuffer that) {
int n = this.position() + Math.min(this.remaining(), that.remaining());
for (int i = this.position(), j = that.position(); i < n; i++, j++) {
int cmp = compare(this.get(i), that.get(j));
if (cmp != 0)
return cmp;
}
return this.remaining() - that.remaining();
}
private static int compare(byte x, byte y) {
return Byte.compare(x, y);
}
boolean bigEndian // package-private
= true;
boolean nativeByteOrder // package-private
= (Bits.byteOrder() == ByteOrder.BIG_ENDIAN);
public final ByteOrder order() {
return bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN;
}
public final ByteBuffer order(ByteOrder bo) {
bigEndian = (bo == ByteOrder.BIG_ENDIAN);
nativeByteOrder =
(bigEndian == (Bits.byteOrder() == ByteOrder.BIG_ENDIAN));
return this;
}
// Unchecked accessors, for use by ByteBufferAs-X-Buffer classes
//
abstract byte _get(int i); // package-private
abstract void _put(int i, byte b); // package-private
public abstract char getChar();
public abstract ByteBuffer putChar(char value);
public abstract char getChar(int index);
public abstract ByteBuffer putChar(int index, char value);
public abstract CharBuffer asCharBuffer();
public abstract short getShort();
public abstract ByteBuffer putShort(short value);
public abstract short getShort(int index);
public abstract ByteBuffer putShort(int index, short value);
public abstract ShortBuffer asShortBuffer();
public abstract int getInt();
public abstract ByteBuffer putInt(int value);
public abstract int getInt(int index);
public abstract ByteBuffer putInt(int index, int value);
public abstract IntBuffer asIntBuffer();
public abstract long getLong();
public abstract ByteBuffer putLong(long value);
public abstract long getLong(int index);
public abstract ByteBuffer putLong(int index, long value);
public abstract LongBuffer asLongBuffer();
public abstract float getFloat();
public abstract ByteBuffer putFloat(float value);
public abstract float getFloat(int index);
public abstract ByteBuffer putFloat(int index, float value);
public abstract FloatBuffer asFloatBuffer();
public abstract double getDouble();
public abstract ByteBuffer putDouble(double value);
public abstract double getDouble(int index);
public abstract ByteBuffer putDouble(int index, double value);
public abstract DoubleBuffer asDoubleBuffer();
}