Guava Hasher有时会为同一个对象提供不同的结果

时间:2015-02-06 15:53:20

标签: java guava

我需要使用Key(自定义对象)和Value As Set自定义对象创建一个哈希代码,我使用Guava 18.0

@Getter
   public final class StockKey {

    @ValidIsin
    private final String isin;

    @ValidExchangeId
    private final Integer exchangeId;

    @ValidCurrency
    private final String currency
   }


   @EqualsAndHashCode
    public final class ClientAssetPosition {

    public static final double EPSILON = 0.0001;

    @NotNull
    private final PositionType type;

    @NotNull
    private final Double quantity;

    @Nullable
    @Getter
    private Double coveredOptions;

    @Nullable
    @Getter
    private Double blockedCoveringUnderlyings;

    @Getter
    @Setter
    private Boolean excluded;
    }

所以我有一个创建HashCode的函数

 public static HashCode getHashCodeWithSha256(Map<StockKey, Set<ClientAssetPosition>> positions) {
        final Hasher hasher = Hashing.sha256().newHasher();
        for (Map.Entry<StockKey, Set<ClientAssetPosition>> positionsEntry : positions.entrySet()) {
            hasher.putObject(positionsEntry.getKey(), STOCK_KEY_FUNNEL);
            for (ClientAssetPosition asset : positionsEntry.getValue()) {
                hasher.putObject(asset, CLIENT_ASSET_POSITION_FUNNEL);
            }
        }
        return hasher.hash();
    }

我使用这样的漏斗

public static final Funnel<StockKey> STOCK_KEY_FUNNEL = new Funnel<StockKey>() {
    @Override
    public void funnel(StockKey from, PrimitiveSink into) {
        into.putString(from.getIsin()).putString(from.getCurrency()).putInt(from.getExchangeId());
    }
};
public static final Funnel<ClientAssetPosition> CLIENT_ASSET_POSITION_FUNNEL = new Funnel<ClientAssetPosition>() {
    @Override
    public void funnel(ClientAssetPosition from, PrimitiveSink into) {
        into.putDouble(from.getQuantity()).putString(from.getType().name());
    }
};

对于相同的Map,这个函数有时会返回不同的HashCode 我通过这个单元测试找到它。如果从maven运行它,则此测试失败,但不是每次都运行。

@Test
    public void testSamePortfolioSameHAshCodeOrdersASC(){
        Map<StockKey, Set<ClientAssetPosition>> positions = new HashMap<>();
        positions.put(PredefinedStockKeys.UBS, Sets.newHashSet(PredefinedAssetPosition.OWN_1, PredefinedAssetPosition.ORD_B101));
        positions.put(PredefinedStockKeys.UBS_FEB_12_17_C, Sets.newHashSet(PredefinedAssetPosition.OWN_1, PredefinedAssetPosition.ORD_B101));
        positions.put(PredefinedStockKeys.UBSN_MAR12_12_5_C, Sets.newHashSet(PredefinedAssetPosition.OWN_1, PredefinedAssetPosition.ORD_M10));
        positions.put(PredefinedStockKeys.UBSN_MAR12_12_5_P, Sets.newHashSet(PredefinedAssetPosition.OWN_1, PredefinedAssetPosition.ORD_B101));
        positions.put(PredefinedStockKeys.UBSN_MAR12_13_C, Sets.newHashSet(PredefinedAssetPosition.OWN_1, PredefinedAssetPosition.ORD_B101));
        positions.put(PredefinedStockKeys.UBSN_MAY12_13_C, Sets.newHashSet(PredefinedAssetPosition.C_ORD_M1, PredefinedAssetPosition.ORD_M10));
        positions.put(PredefinedStockKeys.UBSN_MAR12_13_P, Sets.newHashSet(PredefinedAssetPosition.OWN_1, PredefinedAssetPosition.ORD_B101));
        positions.put(PredefinedStockKeys.UBS_JAN_12_17_C, Sets.newHashSet(PredefinedAssetPosition.OWN_1, PredefinedAssetPosition.C_ORD_M1));
        positions.put(PredefinedStockKeys.UBS_JAN_12_17_P, Sets.newHashSet(PredefinedAssetPosition.OWN_1, PredefinedAssetPosition.ORD_B101));
        positions.put(PredefinedStockKeys.UBSH_APR12, Sets.newHashSet(PredefinedAssetPosition.C_ORD_M1, PredefinedAssetPosition.ORD_B101));
        positions.put(PredefinedStockKeys.UBSH_MAR12, Sets.newHashSet(PredefinedAssetPosition.C_ORD_M1, PredefinedAssetPosition.ORD_B101));
        positions.put(PredefinedStockKeys.UBSH_MAY12, Sets.newHashSet(PredefinedAssetPosition.C_ORD_M1, PredefinedAssetPosition.ORD_B101));

        Map<StockKey, Set<ClientAssetPosition>> positionsv2 = new HashMap<>();
        positionsv2.put(PredefinedStockKeys.UBS, Sets.newHashSet(PredefinedAssetPosition.OWN_1, PredefinedAssetPosition.ORD_B101));
        positionsv2.put(PredefinedStockKeys.UBSN_MAR12_12_5_C, Sets.newHashSet(PredefinedAssetPosition.OWN_1, PredefinedAssetPosition.ORD_M10));
        positionsv2.put(PredefinedStockKeys.UBSN_MAR12_12_5_P, Sets.newHashSet(PredefinedAssetPosition.OWN_1, PredefinedAssetPosition.ORD_B101));
        positionsv2.put(PredefinedStockKeys.UBSH_APR12, Sets.newHashSet(PredefinedAssetPosition.C_ORD_M1, PredefinedAssetPosition.ORD_B101));
        positionsv2.put(PredefinedStockKeys.UBSN_MAY12_13_C, Sets.newHashSet(PredefinedAssetPosition.C_ORD_M1, PredefinedAssetPosition.ORD_M10));
        positionsv2.put(PredefinedStockKeys.UBSN_MAR12_13_P, Sets.newHashSet(PredefinedAssetPosition.OWN_1, PredefinedAssetPosition.ORD_B101));
        positionsv2.put(PredefinedStockKeys.UBS_JAN_12_17_C, Sets.newHashSet(PredefinedAssetPosition.OWN_1, PredefinedAssetPosition.C_ORD_M1));
        positionsv2.put(PredefinedStockKeys.UBS_JAN_12_17_P, Sets.newHashSet(PredefinedAssetPosition.OWN_1, PredefinedAssetPosition.ORD_B101));
        positionsv2.put(PredefinedStockKeys.UBS_FEB_12_17_C, Sets.newHashSet(PredefinedAssetPosition.OWN_1, PredefinedAssetPosition.ORD_B101));
        positionsv2.put(PredefinedStockKeys.UBSH_MAR12, Sets.newHashSet(PredefinedAssetPosition.C_ORD_M1, PredefinedAssetPosition.ORD_B101));
        positionsv2.put(PredefinedStockKeys.UBSN_MAR12_13_C, Sets.newHashSet(PredefinedAssetPosition.ORD_B101, PredefinedAssetPosition.OWN_1));
        positionsv2.put(PredefinedStockKeys.UBSH_MAY12, Sets.newHashSet(PredefinedAssetPosition.C_ORD_M1, PredefinedAssetPosition.ORD_B101));
        HashCode hashCodeWithSha256Expected = HashHelper.getHashCodeWithSha256(positions);
        HashCode hashCodeWithSha256Exist = HashHelper.getHashCodeWithSha256(positionsv2);
        Assert.assertArrayEquals(hashCodeWithSha256Expected.asBytes(), hashCodeWithSha256Exist.asBytes());
    }

有人能解释我的错误吗?

2 个答案:

答案 0 :(得分:3)

我认为问题与订购有关。即使从一次调用到另一次调用,如果分别在HashMapHashSet中放置相同的键/值对或值,也无法保证两个调用之间的条目顺序保持不变。当然,JVM运行的情况要差得多。

因此,您需要重写哈希计算方法,以便在计算哈希值之前强制执行顺序...

其中,因为您使用Guava,很容易:使用Ordering.sortedCopy()

应该这样做;但是,请注意,这假设您的StockKeyClientAssetPosition类实施Comparable

public static HashCode getHashCodeWithSha256(Map<StockKey, Set<ClientAssetPosition>> positions) 
{
    final Hasher hasher = Hashing.sha256().newHasher();

    final Iterable<StockKey> orderedKeys
        = Ordering.sortedCopy(positions.keySet());

    Iterable<ClientAssetPosition> orderedAssets;

    for (final StockKey key: orderedKeys) {
        hasher.putObject(key, STOCK_KEY_FUNNEL);

        orderedAssets = Ordering.sortedCopy(positions.get(key));

        for (final ClientAssetPosition asset: orderedAssets)
            hasher.putObject(asset, CLIENT_ASSET_POSITION_FUNNEL);
    }
    return hasher.hash();
}

HOWEVER :真的考虑切换到Multimap。并且回想一下,如果您在某些时候需要向后兼容性,它也有.asMap()方法。

答案 1 :(得分:1)

如果您查看Hasher documentation,您会发现:

  

警告:放入Hasher的数据块不是   分隔。生成的HashCode仅依赖于字节   插入,以及它们的插入顺序......

顺序问题,具有不同键/值顺序的哈希映射会产生不同的哈希值。

您可以通过在getHashCodeWithSha256中使用有序/排序结构或散列预处理来解决此问题。