【问题标题】:Guava Hasher some times give diff result for same objectGuava Hasher 有时会为同一个对象给出不同的结果
【发布时间】:2015-02-06 15:53:20
【问题描述】:

我需要从带有键(自定义对象)和值作为自定义对象集的 Map 创建一个哈希码,我使用 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());
    }

谁能解释一下我做错了什么?

【问题讨论】:

  • 只有一件事......你使用番石榴,那你为什么不使用Multimap
  • 除此之外,疯狂猜测:订购!请记住,没有任何东西可以保证HashMap 的键/值的顺序,也不能保证HashSet 的值的顺序。我相信这是这里的问题。
  • 这部分代码真的太老了,重写包含这个映射的域对象太贵了。我们没有时间。
  • 是的,可以订购,我也在考虑。我试图做出这样的改进 Collections.sort(positions.entrySet()) 没有帮助
  • 这不是您遇到的唯一问题。您的套装也需要订购。

标签: java guava


【解决方案1】:

我认为问题与订购有关。即使从一个调用到另一个调用,如果您将相同的键/值对或值分别放在 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();
}

但是:真的考虑切换到Multimap。请记住,如果您在某些时候需要向后兼容,它还有一个 .asMap() 方法。

【讨论】:

  • 如此简单...我什至开始在 jvm 内部进行挖掘,以发现我的 jenkins 机器上的哈希创建与本地创建之间的任何差异。因为在本地它总是有效的。
  • 嗯,当然,这也要求您的键和值确实实现Comparable。否则你需要写一个...
【解决方案2】:

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

警告:放入哈希器的数据块不是 划定的。生成的 HashCode 仅取决于字节 插入,以及它们的插入顺序...

就顺序而言,具有不同键/值顺序的哈希图会产生不同的哈希。

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

【讨论】:

    猜你喜欢
    • 2016-06-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-09
    • 2021-04-02
    • 2017-05-07
    • 2014-02-01
    • 2021-08-26
    相关资源
    最近更新 更多