在查看hibernate源码,学习脏检查的操作时,我发现到这么一段代码,他出现在AbstractEntityEntry类中,这种编写方式,在我们平时的编程中几乎不曾使用过,也只有在源码中可以看到类似的写法。
package org.hibernate.engine.internal
***
public abstract class AbstractEntityEntry implements Serializable, EntityEntry {
***
/**
* Gets the current value of the given enum property.
*
* @param state
* identifies the value to store
* @return the current value of the specified property
*/
protected <E extends Enum<E>> E getCompressedValue(EnumState<E> state) {
// restore the numeric value from the bits at the right offset and return the corresponding enum constant
final int index = ( ( compressedState & state.getMask() ) >> state.getOffset() ) - 1;
return index == - 1 ? null : state.getEnumConstants()[index];
}
}
该方法接受一个EnumState类型的参数(其中E是一个枚举类型),并返回一个和E相同类型的枚举类常量,我们看下EnumState是如何定义的,同样是在AbstractEntityEntry 类里,是一个静态的内部类,代码如下:
protected static class EnumState<E extends Enum<E>> {
protected static final EnumState<LockMode> LOCK_MODE = new EnumState<LockMode>( 0, LockMode.class );
protected static final EnumState<Status> STATUS = new EnumState<Status>( 4, Status.class );
protected static final EnumState<Status> PREVIOUS_STATUS = new EnumState<Status>( 8, Status.class );
protected final int offset;
protected final E[] enumConstants;
protected final int mask;
protected final int unsetMask;
private EnumState(int offset, Class<E> enumType) {
final E[] enumConstants = enumType.getEnumConstants();
// In case any of the enums cannot be stored in 4 bits anymore, we'd have to re-structure the compressed
// state int
if ( enumConstants.length > 15 ) {
throw new AssertionFailure( "Cannot store enum type " + enumType.getName() + " in compressed state as"
+ " it has too many values." );
}
this.offset = offset;
this.enumConstants = enumConstants;
// a mask for reading the four bits, starting at the right offset
this.mask = 0xF << offset;
// a mask for setting the four bits at the right offset to 0
this.unsetMask = 0xFFFF & ~mask;
}
/**
* Returns the numeric value to be stored for the given enum value.
*/
private int getValue(E value) {
return value != null ? value.ordinal() + 1 : 0;
}
/**
* Returns the offset within the number value at which this enum value is stored.
*/
private int getOffset() {
return offset;
}
/**
* Returns the bit mask for reading this enum value from the number value storing it.
*/
private int getMask() {
return mask;
}
/**
* Returns the bit mask for resetting this enum value from the number value storing it.
*/
private int getUnsetMask() {
return unsetMask;
}
/**
* Returns the constants of the represented enum which is cached for performance reasons.
*/
private E[] getEnumConstants() {
return enumConstants;
}
}
LOCK_MODE使用整型数值的第0到第3位(共4位)来存储其状态。
STATUS使用整型数值的第4到第7位(共4位)来存储其状态。
PREVIOUS_STATUS使用整型数值的第8到第11位(共4位)来存储其状态。
LockMode的枚举个数为12个,Status的枚举个数为6个,所以用4位长度来存储是肯定足够的。这种方法的缺点是代码可读性和易用性可能会受到一定影响,但在需要优化内存使用的场景中,这是一个很好的权衡。
this.mask = 0xF << offset;: mask 用于从整型数值中读取位偏移量处的 4 个位。0xF 是一个十六进制数字,它在二进制表示中等于 1111。通过将 0xF 左移 offset 位,创建了一个掩码,可以用来访问整型数值中位偏移量处的 4 个位。例如,如果 offset 为 4,则 mask 的二进制表示为 11110000。
this.unsetMask = 0xFFFF & ~mask;: unsetMask 用于将整型数值中位偏移量处的 4 个位设置为 0。首先,~mask 操作符计算 mask 的按位取反,将位偏移量处的 4 个位变为 0,其他位变为 1。然后,与 0xFFFF(16 位整数的最大值,二进制表示为 16 个 1)进行按位与,确保结果仍然是一个 16 位数值。这样,unsetMask 可以用来将整型数值中位偏移量处的 4 个位设置为 0,而保持其他位不变。
简而言之,mask 和 unsetMask 是两个掩码,分别用于访问和清除整型数值中指定位偏移量处的 4 个位。这在处理二进制数据时非常有用,例如在网络通信、数据压缩或内存优化等场景。
OK,再回头看getCompressedValue方法
protected <E extends Enum<E>> E getCompressedValue(EnumState<E> state) {
// restore the numeric value from the bits at the right offset and return the corresponding enum constant
final int index = ( ( compressedState & state.getMask() ) >> state.getOffset() ) - 1;
return index == - 1 ? null : state.getEnumConstants()[index];
}
&(位与运算符):对两个整数进行位与运算。只有当两个对应的位都为1时,结果的相应位才为1。例如,1101 & 1010 = 1000。
>>(带符号右移运算符):将整数的二进制表示向右移动指定的位数,左边空出的位用原数最高位的值填充(即正数补0,负数补1)。例如,1101 >> 2 = 0011。
-
定义一个泛型方法<E extends Enum> E getCompressedValue(EnumState state),其中E是一个枚举类型。EnumState是一个泛型类,用于维护枚举类型的状态信息。
-
使用位运算符&和>>从compressedState中提取特定的位。compressedState & state.getMask()表示将compressedState与state.getMask()进行位与运算,这会将compressedState中的某些位屏蔽掉,只保留我们关心的那些位。接下来的>> state.getOffset()表示将结果向右移state.getOffset()位,这样我们关心的位就移到了最低位。将结果保存到一个名为index的整数变量。
-
final int index = ( ( compressedState & state.getMask() ) >> state.getOffset() ) - 1;:根据提取到的位计算索引值。请注意,我们还需要减去1,因为在存储时,所有的枚举值的索引都加了1(这样做的原因是为了区分没有设置枚举值的情况,即index为-1)。
-
return index == -1 ? null : state.getEnumConstants()[index];:根据计算得到的index值返回相应的枚举常量。如果index等于-1,表示没有设置枚举值,因此返回null。否则,从state.getEnumConstants()数组中返回index位置的枚举常量。
总结:
在这段代码中,作者将枚举值的索引存储在一个整数(compressedState)中的特定位上。这意味着多个枚举状态可以共享一个整数,从而节省内存。同时,使用位操作(& 和 >>)可以在不使用额外空间的情况下高效地检索特定枚举状态。虽然这样的优化可能在小规模应用中看起来不明显,但在处理大量数据和对象时,这种节省会变得非常重要。编程有时候就是一种艺术!
本文部分内容由作者借助chatgpt生成引用,如果有误,还请指正。