private static boolean compareUnorderedCollection(Collection col1, Collection col2, Deque stack, Set visited)
    {
        // Same instance check already performed...

        if (col1.size() != col2.size())
        {
            return false;
        }

        Map<Integer, Collection> fastLookup = new HashMap<>();
        for (Object o : col2)
        {
            int hash = deepHashCode(o);
            Collection items = fastLookup.get(hash);
            if (items == null)
            {
                items = new ArrayList();
                fastLookup.put(hash, items);
            }
            items.add(o);
        }

        for (Object o : col1)
        {
            Collection other = fastLookup.get(deepHashCode(o));
            if (other == null || other.isEmpty())
            {   // fail fast: item not even found in other Collection, no need to continue.
                return false;
            }

            if (other.size() == 1)
            {   // no hash collision, items must be equivalent or deepEquals is false
                DualKey dk = new DualKey(o, other.iterator().next());
                if (!visited.contains(dk))
                {   // Place items on 'stack' for future equality comparison.
                    stack.addFirst(dk);
                }
            }
            else
            {   // hash collision: try all collided items against the current item (if 1 equals, we are good - remove it
                // from collision list, making further comparisons faster)
                if (!isContained(o, other))
                {
                    return false;
                }
            }
        }
        return true;
    }
private static boolean compareUnorderedCollection ( Collection col1 , Collection col2 , Deque stack , Set visited ) { if ( col1 . size ( ) != col2 . size ( ) ) { return false ; } Map < Integer , Collection > fastLookup = new HashMap < > ( ) ; for ( Object o : col2 ) { int hash = deepHashCode ( o ) ; Collection items = fastLookup . get ( hash ) ; if ( items == null ) { items = new ArrayList ( ) ; fastLookup . put ( hash , items ) ; } items . add ( o ) ; } for ( Object o : col1 ) { Collection other = fastLookup . get ( deepHashCode ( o ) ) ; if ( other == null || other . isEmpty ( ) ) { return false ; } if ( other . size ( ) == 1 ) { DualKey dk = new DualKey ( o , other . iterator ( ) . next ( ) ) ; if ( ! visited . contains ( dk ) ) { stack . addFirst ( dk ) ; } } else { if ( ! isContained ( o , other ) ) { return false ; } } } return true ; }
Deeply compare the two sets referenced by dualKey.  This method attempts
to quickly determine inequality by length, then if lengths match, it
places one collection into a temporary Map by deepHashCode(), so that it
can walk the other collection and look for each item in the map, which
runs in O(N) time, rather than an O(N^2) lookup that would occur if each
item from collection one was scanned for in collection two.
@param col1 First collection of items to compare
@param col2 Second collection of items to compare
@param stack add items to compare to the Stack (Stack versus recursion)
@param visited Set containing items that have already been compared,
so as to prevent cycles.
@return boolean false if the Collections are for certain not equals. A
value of 'true' indicates that the Collections may be equal, and the sets
items will be added to the Stack for further comparison.