Cause
Today, I encountered a strange problem in the project. The general situation is as follows: Android terminal and server (Spring), the same string key-value pairs are put into HashMap in different order, this directly results in different Digest values encrypted by the HmacSHA256 algorithm on the server and Android terminal, and thus the server cannot perform correct data verification.
Then, with a depressing mood, I found that the servers and terminals in HashMap had different storage orders for the same key!
In the case of HashCode conflicts, different keys should be stored in the HashMap in the same location. Even if the hashCode is written in, if the key-value put order is the same, the storage location should also be the same.
Find and solve
So the problem should be solved on HashMap. I can only view the source code of HashMap in Java and Android. I found that the hashCode () method of the two is different. I'm so excited that I can take a closer look, it is found that Android only optimizes the hashCode () method in Java to make it easier to read. However, the principle used is the same, which is true =. =.
The code is compared as follows:
The code is as follows: |
Copy code |
<! -- Android --> @ Override public int hashCode (){ Int hash = hashCode; If (hash = 0 ){ If (count = 0 ){ Return 0; } Final int end = count + offset; Final char [] chars = value; For (int I = offset; I <end; ++ I ){ Hash = 31 * hash + chars [I]; } HashCode = hash; } Return hash; } <! -- Java --> Public int hashCode (){ Int h = hash; Int len = count; If (h = 0 & len> 0 ){ Int off = offset; Char val [] = value; For (int I = 0; I <len; I ++ ){ H = 31 * h + val [off ++]; } Hash = h; } Return h; } |
However, I can only view and compare the data in the source code, and finally find that the default constructor of the two is different. In essence, the table sizes of the two are different, in Java, the default table size is 16*0.75 = 12 (capacity * load factor), while in Android, the default table size is 2, therefore, even strings are placed in the HashMap in the same order and their key values are stored in different order.
The code is as follows: |
Copy code |
<! -- Android --> Private static final Entry [] EMPTY_TABLE = New HashMapEntry [MINIMUM_CAPACITY >>> 1]; // Default constructor Public HashMap (){ Table = (HashMapEntry <K, V> []) EMPTY_TABLE; Threshold =-1; // Forces first put invocation to replace EMPTY_TABLE } <! -- Java --> Static final int DEFAULT_INITIAL_CAPACITY = 16; Static final float DEFAULT_LOAD_FACTOR = 0.75f; // Default constructor Public HashMap (){ This (DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR ); } |
In fact, if you carefully read the source code, you will find that the HashMap class implemented in Android has different settings on the "threshold" and Java. For details, see the intercepted source code:
The code is as follows: |
Copy code |
<! -- Android --> // Set the threshold value to 3/4 of the table size. Threshold = (newCapacity> 1) + (newCapacity> 2 ); <! -- Java --> // The threshold value is a small value between the capacity * load factor or the maximum capacity + 1. Threshold = (int) Math. min (capacity * loadFactor, MAXIMUM_CAPACITY + 1 ); |
Summary
So in summary, the implementation details of HashMap in different platforms or languages are different. Take a break, grow wisdom, and remember later that HashMap is really not suitable when order is involved!