集合

框架关系图:

补充:HashTable父类是Dictionary,不是AbstractMap。

Map:

Map(接口)和Collection都属于集合,但是Map不是Collection的子类或者子接口。而且Map比较特殊:它是以<key,value>键值对的方式来存储数据,其中key不能重复,且一个key最多只对应一个value。

Map在面试中常问的三个子类:HashMap、HashTable、TreeMap。

HashMap:

基于哈希表的实现的Map接口, 该实现提供了所有可选的映射操作,并允许null的key和null的value。

在JDK1.8以前,HashMap底层是:"链表+数组"结构;在JDK1.8时,对HashMap底层进行优化,成了:数组+链表+红黑树。

JDK1.6:初始容量为16,加载因子为0.75,底层是Entry数组加链表结构:

  1. //初始容量
  2. static final int DEFAULT_INITIAL_CAPACITY = 16;
  3. //加载因子
  4. static final float DEFAULT_LOAD_FACTOR = 0.75f;
  5. //存储容器-数组
  6. transient Entry[] table;
  7. //静态内部类
  8. static class Entry<K,V> implements Map.Entry<K,V> {
  9. final K key;
  10. V value;
  11. Entry<K,V> next;//把key和value转换成数组中的Entry对象
  12. final int hash;
  13.  
  14. /**
  15. * Creates new entry.
  16. */
  17. Entry(int h, K k, V v, Entry<K,V> n) {
  18. value = v;
  19. next = n;
  20. key = k;
  21. hash = h;
  22. }
  23.  
  24. public final K getKey() {
  25. return key;
  26. }
  27.  
  28. public final V getValue() {
  29. return value;
  30. }
  31.  
  32. public final V setValue(V newValue) {
  33. V oldValue = value;
  34. value = newValue;
  35. return oldValue;
  36. }
  37.  
  38. public final boolean equals(Object o) {
  39. if (!(o instanceof Map.Entry))
  40. return false;
  41. Map.Entry e = (Map.Entry)o;
  42. Object k1 = getKey();
  43. Object k2 = e.getKey();
  44. if (k1 == k2 || (k1 != null && k1.equals(k2))) {
  45. Object v1 = getValue();
  46. Object v2 = e.getValue();
  47. if (v1 == v2 || (v1 != null && v1.equals(v2)))
  48. return true;
  49. }
  50. return false;
  51. }
  52.  
  53. public final int hashCode() {
  54. return (key==null ? 0 : key.hashCode()) ^
  55. (value==null ? 0 : value.hashCode());
  56. }
  57.  
  58. public final String toString() {
  59. return getKey() + "=" + getValue();
  60. }
  61.  
  62. void recordAccess(HashMap<K,V> m) {
  63. }
  64.  
  65. void recordRemoval(HashMap<K,V> m) {
  66. }
  67. }
  68. //Put方法
  69. public V put(K key, V value) {
  70. if (key == null)
  71. return putForNullKey(value);//hashmap可以存key==null
  72. int hash = hash(key.hashCode());//根据key的hashcode计算一个哈希数
  73. int i = indexFor(hash, table.length);//根据计算的哈希数获取数组的坐标
  74. for (Entry<K,V> e = table[i]; e != null; e = e.next) {//hashmap不允许重复,如果key重复了,把新的value替换老的value
  75. Object k;
  76. if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {//
  77. V oldValue = e.value;
  78. e.value = value;
  79. e.recordAccess(this);
  80. return oldValue;
  81. }
  82. }
  83.  
  84. modCount++;
  85. addEntry(hash, key, value, i);//处理好以后把hash,key,value,index添加到数组中去
  86. return null;
  87. }
  88. //针对key=null的方法
  89. private V putForNullKey(V value) {
  90. for (Entry<K,V> e = table[0]; e != null; e = e.next) {
  91. if (e.key == null) {
  92. V oldValue = e.value;
  93. e.value = value;
  94. e.recordAccess(this);
  95. return oldValue;
  96. }
  97. }
  98. modCount++;
  99. addEntry(0, null, value, 0);
  100. return null;
  101. }
  102. //获取哈希数
  103. static int hash(int h) {
  104. // This function ensures that hashCodes that differ only by
  105. // constant multiples at each bit position have a bounded
  106. // number of collisions (approximately 8 at default load factor).
  107. h ^= (h >>> 20) ^ (h >>> 12);
  108. return h ^ (h >>> 7) ^ (h >>> 4);
  109. }
  110. //获取数组坐标
  111. static int indexFor(int h, int length) {
  112. return h & (length-1);
  113. }
  114. //添加元素的方法
  115. void addEntry(int hash, K key, V value, int bucketIndex) {
  116. Entry<K,V> e = table[bucketIndex];//如果index相同了(哈希碰撞),把原来的值给一个新的Entry对象
  117. //然后把新来的值存在数组中,老值作为新值的下标(next对象),在此也体现了hashmap的“链式结构”
  118. table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
  119. if (size++ >= threshold)
  120. resize(2 * table.length);//如果数值超容了,进行扩容:数组的长度*2(默认初始容量16,所以为16*2=32)
  121. }
  122. //扩容方法
  123. void resize(int newCapacity) {//2倍长度作为参数传入
  124. Entry[] oldTable = table;
  125. int oldCapacity = oldTable.length;
  126. if (oldCapacity == MAXIMUM_CAPACITY) {
  127. threshold = Integer.MAX_VALUE;
  128. return;
  129. }
  130.  
  131. Entry[] newTable = new Entry[newCapacity];//新建数组
  132. transfer(newTable);//数组值转换的方法
  133. table = newTable;
  134. threshold = (int)(newCapacity * loadFactor);
  135. }
  136. //数组值转换的方法
  137. void transfer(Entry[] newTable) {
  138. Entry[] src = table;
  139. int newCapacity = newTable.length;
  140. for (int j = 0; j < src.length; j++) {
  141. Entry<K,V> e = src[j];
  142. if (e != null) {
  143. src[j] = null;
  144. do {//此处为复制的过程
  145. Entry<K,V> next = e.next;
  146. int i = indexFor(e.hash, newCapacity);
  147. e.next = newTable[i];
  148. newTable[i] = e;
  149. e = next;
  150. } while (e != null);
  151. }
  152. }
  153. }
  154. //Get方法
  155. public V get(Object key) {
  156. if (key == null)
  157. return getForNullKey();//遍历,获取到key==null的值
  158. int hash = hash(key.hashCode());//然后根据key获取哈希值
  159. for (Entry<K,V> e = table[indexFor(hash, table.length)];//然后获取坐标得到Entry对象
  160. e != null;
  161. e = e.next) {//从第一个开始遍历,如果不是,把Entry对象的下标给Entry然后比较
  162. Object k;
  163. if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
  164. return e.value;//当哈希值和key都相等的时候,把value返回去
  165. }
  166. return null;
  167. }
  168. //key等于null获取值
  169. private V getForNullKey() {
  170. for (Entry<K,V> e = table[0]; e != null; e = e.next) {
  171. if (e.key == null)
  172. return e.value;
  173. }
  174. return null;
  175. }
  176. //Clear方法
  177. public void clear() {
  178. modCount++;
  179. Entry[] tab = table;
  180. for (int i = 0; i < tab.length; i++)//遍历数组,把值清空,把size置为0
  181. tab[i] = null;
  182. size = 0;
  183. }
  184. //entrySet​方法:获取到数组中Entry对象的Set集合
  185. private transient Set<Map.Entry<K,V>> entrySet = null;
  186.  
  187. public Set<Map.Entry<K,V>> entrySet() {
  188. return entrySet0();
  189. }
  190. private Set<Map.Entry<K,V>> entrySet0() {
  191. Set<Map.Entry<K,V>> es = entrySet;
  192. return es != null ? es : (entrySet = new EntrySet());
  193. }
  194. //原理就是迭代map中的Entry对象
  195. private final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
  196. public Iterator<Map.Entry<K,V>> iterator() {
  197. return newEntryIterator();
  198. }
  199. public boolean contains(Object o) {
  200. if (!(o instanceof Map.Entry))
  201. return false;
  202. Map.Entry<K,V> e = (Map.Entry<K,V>) o;
  203. Entry<K,V> candidate = getEntry(e.getKey());
  204. return candidate != null && candidate.equals(e);
  205. }
  206. public boolean remove(Object o) {
  207. return removeMapping(o) != null;
  208. }
  209. public int size() {
  210. return size;
  211. }
  212. public void clear() {
  213. HashMap.this.clear();
  214. }
  215. }
  216. final Entry<K,V> nextEntry() {
  217. if (modCount != expectedModCount)
  218. throw new ConcurrentModificationException();
  219. Entry<K,V> e = next;
  220. if (e == null)
  221. throw new NoSuchElementException();
  222.  
  223. if ((next = e.next) == null) {
  224. Entry[] t = table;
  225. while (index < t.length && (next = t[index++]) == null) ;
  226. }
  227. current = e;
  228. return e;
  229. }
  230. //keySet​方法
  231. public Set<K> keySet() {
  232. Set<K> ks = keySet;
  233. return (ks != null ? ks : (keySet = new KeySet()));
  234. }
  235. //原理也是通过迭代器,迭代Entry对象,然后获取key
  236. private final class KeySet extends AbstractSet<K> {
  237. public Iterator<K> iterator() {
  238. return newKeyIterator();
  239. }
  240. public int size() {
  241. return size;
  242. }
  243. public boolean contains(Object o) {
  244. return containsKey(o);
  245. }
  246. public boolean remove(Object o) {
  247. return HashMap.this.removeEntryForKey(o) != null;
  248. }
  249. public void clear() {
  250. HashMap.this.clear();
  251. }
  252. }
  253.  
  254. private final class KeyIterator extends HashIterator<K> {
  255. public K next() {
  256. return nextEntry().getKey();
  257. }
  258. }
  259. //remove方法
  260. public V remove(Object key) {
  261. Entry<K,V> e = removeEntryForKey(key);
  262. return (e == null ? null : e.value);
  263. }
  264. final Entry<K,V> removeEntryForKey(Object key) {
  265. int hash = (key == null) ? 0 : hash(key.hashCode());
  266. int i = indexFor(hash, table.length);
  267. Entry<K,V> prev = table[i];
  268. Entry<K,V> e = prev;
  269.  
  270. while (e != null) {//用while循环,当哈希值和key都相等的时候把值得next赋值给现在位置的值
  271. Entry<K,V> next = e.next;
  272. Object k;
  273. if (e.hash == hash &&
  274. ((k = e.key) == key || (key != null && key.equals(k)))) {
  275. modCount++;
  276. size--;
  277. if (prev == e)
  278. table[i] = next;
  279. else
  280. prev.next = next;
  281. e.recordRemoval(this);
  282. return e;
  283. }
  284. prev = e;
  285. e = next;
  286. }
  287.  
  288. return e;
  289. }
  290. //size方法和isEmpty方法
  291. transient int size;//和put以及remove方法有关系,增加就++,删除就--
  292. public int size() {
  293. return size;
  294. }
  295. public boolean isEmpty() {
  296. return size == 0;
  297. }
  298. //contains方法
  299. public boolean contains(Object o) {
  300. return containsKey(o);
  301. }
  302. public boolean containsKey(Object key) {
  303. return getEntry(key) != null;
  304. }
  305. final Entry<K,V> getEntry(Object key) {
  306. if (size == 0) {
  307. return null;
  308. }
  309.  
  310. int hash = (key == null) ? 0 : hash(key);
  311. for (Entry<K,V> e = table[indexFor(hash, table.length)];
  312. e != null;
  313. e = e.next) {
  314. Object k;
  315. if (e.hash == hash &&
  316. ((k = e.key) == key || (key != null && key.equals(k))))
  317. return e;
  318. }
  319. return null;
  320. }

补充1:

哈希表(Hash table,采用散列技术将记录存储在一块连续的存储空间中,这块连续存储空间称为散列表或哈希表(Hash table)),是根据关键码值(Key value)而直接进行访问的数据结构,也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。
记录的存储位置=f(关键字),被称为散列函数,又称为哈希(Hash函数),

补充二:

两个对象相等,hashCode一定相同,但是两个对象的HashCode相同,这两个对象不一定相等。

JDK1.7源码(和1.6的对比):

  1. //对比一:1.7默认初始容量用了位移计算
  2. static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
  3. //加载因子
  4. static final float DEFAULT_LOAD_FACTOR = 0.75f;
  5. //存储容器-数组
  6. static final Entry<?,?>[] EMPTY_TABLE = {};
  7.  
  8. transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
  9. //静态内部类
  10. static class Entry<K,V> implements Map.Entry<K,V> {
  11. final K key;
  12. V value;
  13. Entry<K,V> next;
  14. int hash;
  15.  
  16. /**
  17. * Creates new entry.
  18. */
  19. Entry(int h, K k, V v, Entry<K,V> n) {
  20. value = v;
  21. next = n;
  22. key = k;
  23. hash = h;
  24. }
  25.  
  26. public final K getKey() {
  27. return key;
  28. }
  29.  
  30. public final V getValue() {
  31. return value;
  32. }
  33.  
  34. public final V setValue(V newValue) {
  35. V oldValue = value;
  36. value = newValue;
  37. return oldValue;
  38. }
  39.  
  40. public final boolean equals(Object o) {
  41. if (!(o instanceof Map.Entry))
  42. return false;
  43. Map.Entry e = (Map.Entry)o;
  44. Object k1 = getKey();
  45. Object k2 = e.getKey();
  46. if (k1 == k2 || (k1 != null && k1.equals(k2))) {
  47. Object v1 = getValue();
  48. Object v2 = e.getValue();
  49. if (v1 == v2 || (v1 != null && v1.equals(v2)))
  50. return true;
  51. }
  52. return false;
  53. }
  54.  
  55. public final int hashCode() {
  56. return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
  57. }
  58.  
  59. public final String toString() {
  60. return getKey() + "=" + getValue();
  61. }
  62.  
  63. /**
  64. * This method is invoked whenever the value in an entry is
  65. * overwritten by an invocation of put(k,v) for a key k that's already
  66. * in the HashMap.
  67. */
  68. void recordAccess(HashMap<K,V> m) {
  69. }
  70.  
  71. /**
  72. * This method is invoked whenever the entry is
  73. * removed from the table.
  74. */
  75. void recordRemoval(HashMap<K,V> m) {
  76. }
  77. }
  78. //Put方法
  79. public V put(K key, V value) {
  80. if (table == EMPTY_TABLE) {//此出多了一个数组初始化的方法
  81. inflateTable(threshold);
  82. }
  83. if (key == null)
  84. return putForNullKey(value);
  85. int hash = hash(key);
  86. int i = indexFor(hash, table.length);
  87. for (Entry<K,V> e = table[i]; e != null; e = e.next) {
  88. Object k;
  89. if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
  90. V oldValue = e.value;
  91. e.value = value;
  92. e.recordAccess(this);
  93. return oldValue;
  94. }
  95. }
  96.  
  97. modCount++;
  98. addEntry(hash, key, value, i);
  99. return null;
  100. }
  101. //初始化数组
  102. private void inflateTable(int toSize) {
  103.  
  104. int capacity = roundUpToPowerOf2(toSize); // 此处把传入的数组容量向上转换为2的n次幂的数值
  105.  
  106. threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
  107. table = new Entry[capacity];
  108. initHashSeedAsNeeded(capacity);
  109. }
  110. final boolean initHashSeedAsNeeded(int capacity) {
  111. boolean currentAltHashing = hashSeed != 0;
  112. boolean useAltHashing = sun.misc.VM.isBooted() &&
  113. (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
  114. boolean switching = currentAltHashing ^ useAltHashing;
  115. if (switching) {
  116. hashSeed = useAltHashing
  117. ? sun.misc.Hashing.randomHashSeed(this)
  118. : 0;
  119. }
  120. return switching;
  121. }
  122. //针对key=null的方法
  123. private V putForNullKey(V value) {
  124. for (Entry<K,V> e = table[0]; e != null; e = e.next) {
  125. if (e.key == null) {
  126. V oldValue = e.value;
  127. e.value = value;
  128. e.recordAccess(this);
  129. return oldValue;
  130. }
  131. }
  132. modCount++;
  133. addEntry(0, null, value, 0);
  134. return null;
  135. }
  136. //获取哈希数
  137. final int hash(Object k) {
  138. int h = hashSeed;
  139. if (0 != h && k instanceof String) {
  140. return sun.misc.Hashing.stringHash32((String) k);
  141. }
  142.  
  143. h ^= k.hashCode();
  144.  
  145. h ^= (h >>> 20) ^ (h >>> 12);
  146. return h ^ (h >>> 7) ^ (h >>> 4);
  147. }
  148. //获取数组坐标
  149. static int indexFor(int h, int length) {
  150. // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
  151. return h & (length-1);
  152. }
  153. //添加元素的方法
  154. void addEntry(int hash, K key, V value, int bucketIndex) {
  155. if ((size >= threshold) && (null != table[bucketIndex])) {//1.7这里先进行是否需要扩容的判断
  156. resize(2 * table.length);
  157. hash = (null != key) ? hash(key) : 0;
  158. bucketIndex = indexFor(hash, table.length);
  159. }
  160.  
  161. createEntry(hash, key, value, bucketIndex);
  162. }
  163. //扩容方法
  164. void resize(int newCapacity) {
  165. Entry[] oldTable = table;
  166. int oldCapacity = oldTable.length;
  167. if (oldCapacity == MAXIMUM_CAPACITY) {
  168. threshold = Integer.MAX_VALUE;
  169. return;
  170. }
  171.  
  172. Entry[] newTable = new Entry[newCapacity];
  173. transfer(newTable, initHashSeedAsNeeded(newCapacity));
  174. table = newTable;
  175. threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
  176. }
  177. //数组值转换的方法
  178. void transfer(Entry[] newTable, boolean rehash) {
  179. int newCapacity = newTable.length;
  180. for (Entry<K,V> e : table) {
  181. while(null != e) {
  182. Entry<K,V> next = e.next;
  183. if (rehash) {
  184. e.hash = null == e.key ? 0 : hash(e.key);
  185. }
  186. int i = indexFor(e.hash, newCapacity);
  187. e.next = newTable[i];
  188. newTable[i] = e;
  189. e = next;
  190. }
  191. }
  192. }
  193. //添加元素的方法
  194. void createEntry(int hash, K key, V value, int bucketIndex) {
  195. Entry<K,V> e = table[bucketIndex];
  196. table[bucketIndex] = new Entry<>(hash, key, value, e);
  197. size++;
  198. }
  199. //Get方法
  200. public V get(Object key) {
  201. if (key == null)
  202. return getForNullKey();
  203. Entry<K,V> entry = getEntry(key);
  204.  
  205. return null == entry ? null : entry.getValue();
  206. }
  207. //key等于null获取值
  208. private V getForNullKey() {
  209. if (size == 0) {//1.7在此处又多了个判断数组是否为空的情况
  210. return null;
  211. }
  212. for (Entry<K,V> e = table[0]; e != null; e = e.next) {
  213. if (e.key == null)
  214. return e.value;
  215. }
  216. return null;
  217. }
  218. //Clear方法
  219. public void clear() {
  220. modCount++;
  221. Arrays.fill(table, null);//1,7此处调用了Arrays中的方法,不过原理还是一样的
  222. size = 0;
  223. }
  224.  
  225. public static void fill(Object[] a, Object val) {
  226. for (int i = 0, len = a.length; i < len; i++)
  227. a[i] = val;
  228. }
  229. //entrySet​方法:获取到数组中Entry对象的Set集合
  230. private transient Set<Map.Entry<K,V>> entrySet = null;
  231.  
  232. public Set<Map.Entry<K,V>> entrySet() {
  233. return entrySet0();
  234. }
  235.  
  236. private Set<Map.Entry<K,V>> entrySet0() {
  237. Set<Map.Entry<K,V>> es = entrySet;
  238. return es != null ? es : (entrySet = new EntrySet());
  239. }
  240. //原理就是迭代map中的Entry对象
  241. private final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
  242. public Iterator<Map.Entry<K,V>> iterator() {
  243. return newEntryIterator();
  244. }
  245. public boolean contains(Object o) {
  246. if (!(o instanceof Map.Entry))
  247. return false;
  248. Map.Entry<K,V> e = (Map.Entry<K,V>) o;
  249. Entry<K,V> candidate = getEntry(e.getKey());
  250. return candidate != null && candidate.equals(e);
  251. }
  252. public boolean remove(Object o) {
  253. return removeMapping(o) != null;
  254. }
  255. public int size() {
  256. return size;
  257. }
  258. public void clear() {
  259. HashMap.this.clear();
  260. }
  261. }
  262. //keySet​方法
  263. public Set<K> keySet() {
  264. Set<K> ks = keySet;
  265. return (ks != null ? ks : (keySet = new KeySet()));
  266. }
  267. //原理也是通过迭代器,迭代Entry对象,然后获取key
  268. private final class KeySet extends AbstractSet<K> {
  269. public Iterator<K> iterator() {
  270. return newKeyIterator();
  271. }
  272. public int size() {
  273. return size;
  274. }
  275. public boolean contains(Object o) {
  276. return containsKey(o);
  277. }
  278. public boolean remove(Object o) {
  279. return HashMap.this.removeEntryForKey(o) != null;
  280. }
  281. public void clear() {
  282. HashMap.this.clear();
  283. }
  284. }
  285. //remove方法
  286. public V remove(Object key) {
  287. Entry<K,V> e = removeEntryForKey(key);
  288. return (e == null ? null : e.value);
  289. }
  290.  
  291. final Entry<K,V> removeEntryForKey(Object key) {
  292. if (size == 0) {
  293. return null;
  294. }
  295. int hash = (key == null) ? 0 : hash(key);
  296. int i = indexFor(hash, table.length);
  297. Entry<K,V> prev = table[i];
  298. Entry<K,V> e = prev;
  299.  
  300. while (e != null) {
  301. Entry<K,V> next = e.next;
  302. Object k;
  303. if (e.hash == hash &&
  304. ((k = e.key) == key || (key != null && key.equals(k)))) {
  305. modCount++;
  306. size--;
  307. if (prev == e)
  308. table[i] = next;
  309. else
  310. prev.next = next;
  311. e.recordRemoval(this);
  312. return e;
  313. }
  314. prev = e;
  315. e = next;
  316. }
  317.  
  318. return e;
  319. }
  320. //size方法、isEmpty方法、contains方法
  321. public int size() {
  322. return size;
  323. }
  324. public boolean isEmpty() {
  325. return size == 0;
  326. }
  327.  
  328. public boolean contains(Object o) {
  329. return containsKey(o);
  330. }
  331. public boolean containsKey(Object key) {
  332. return getEntry(key) != null;
  333. }
  334. final Entry<K,V> getEntry(Object key) {
  335. if (size == 0) {
  336. return null;
  337. }
  338.  
  339. int hash = (key == null) ? 0 : hash(key);
  340. for (Entry<K,V> e = table[indexFor(hash, table.length)];
  341. e != null;
  342. e = e.next) {
  343. Object k;
  344. if (e.hash == hash &&
  345. ((k = e.key) == key || (key != null && key.equals(k))))
  346. return e;
  347. }
  348. return null;
  349. }

对比可以看出,1.6和1.7重要方法的原理基本没变,1.7在部分方法上做了一点小的优化,还有就是从1.7开始,创建无参hashmap时不直接生成数组了,采用懒加载的方式,在用的时候再初始化。

1.6无参构造函数:

  1. public HashMap() {
  2. this.loadFactor = DEFAULT_LOAD_FACTOR;
  3. threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
  4. table = new Entry[DEFAULT_INITIAL_CAPACITY];
  5. init();
  6. }

1.7无参构造函数(在Put方法中,判断table等于空的时候再创建):

  1. public HashMap() {
  2. this.loadFactor = DEFAULT_LOAD_FACTOR;
  3. }

HashMap1.8:初始容量为16,加载因子为0.75,底层是Entry数组+链表+红黑树:

  1. //初始容量
  2. static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
  3. //加载因子
  4. static final float DEFAULT_LOAD_FACTOR = 0.75f;
  5. //转为红黑树的阈(yu)值
  6. static final int TREEIFY_THRESHOLD = 8;
  7. //从红黑树转换为链表的阈值(扩容会重新计算一次数据长度)
  8. static final int UNTREEIFY_THRESHOLD = 6;
  9. //默认红黑树的容量
  10. static final int MIN_TREEIFY_CAPACITY = 64;
  11. //存储容器-数组,由1.7的Entry<K,V>[]数组变为了Node<K,V>[]
  12.  
  13. transient Node<K,V>[] table;
  14.  
  15. static class Node<K,V> implements Map.Entry<K,V> {
  16. final int hash;
  17. final K key;
  18. V value;
  19. Node<K,V> next;
  20.  
  21. Node(int hash, K key, V value, Node<K,V> next) {
  22. this.hash = hash;
  23. this.key = key;
  24. this.value = value;
  25. this.next = next;
  26. }
  27.  
  28. public final K getKey() { return key; }
  29. public final V getValue() { return value; }
  30. public final String toString() { return key + "=" + value; }
  31.  
  32. public final int hashCode() {
  33. return Objects.hashCode(key) ^ Objects.hashCode(value);
  34. }
  35.  
  36. public final V setValue(V newValue) {
  37. V oldValue = value;
  38. value = newValue;
  39. return oldValue;
  40. }
  41.  
  42. public final boolean equals(Object o) {
  43. if (o == this)
  44. return true;
  45. if (o instanceof Map.Entry) {
  46. Map.Entry<?,?> e = (Map.Entry<?,?>)o;
  47. if (Objects.equals(key, e.getKey()) &&
  48. Objects.equals(value, e.getValue()))
  49. return true;
  50. }
  51. return false;
  52. }
  53. }
  54. //Put方法
  55. public V put(K key, V value) {
  56. return putVal(hash(key), key, value, false, true);
  57. }
  58.  
  59. final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
  60. boolean evict) {
  61. Node<K,V>[] tab; Node<K,V> p; int n, i;
  62. if ((tab = table) == null || (n = tab.length) == 0)
  63. n = (tab = resize()).length;//首先还是先判断数组是否为空的,是的话进行初始化
  64. if ((p = tab[i = (n - 1) & hash]) == null)//如果数组该位置为空,则把key,value传入该位置
  65. tab[i] = newNode(hash, key, value, null);
  66. else {//走到这说明发生了哈希碰撞,计算的key的哈希值相同
  67. Node<K,V> e; K k;
  68. if (p.hash == hash &&
  69. ((k = p.key) == key || (key != null && key.equals(k))))//先比较数据是否重复,重复的话就新值替换旧值
  70. e = p;
  71. else if (p instanceof TreeNode)//判断该对象是不是红黑树的元素,是的话直接存入红黑树
  72. e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
  73. else {
  74. for (int binCount = 0; ; ++binCount) {//遍历链表上的元素
  75. if ((e = p.next) == null) {
  76. p.next = newNode(hash, key, value, null);
  77. if (binCount >= TREEIFY_THRESHOLD - 1) // 如果数量达到临界点,则转换为红黑树
  78. treeifyBin(tab, hash);//转换方法
  79. break;
  80. }
  81. if (e.hash == hash &&
  82. ((k = e.key) == key || (key != null && key.equals(k))))
  83. break;
  84. p = e;//e其实是p.next,此处体现1.8的尾插法
  85. }
  86. }
  87. if (e != null) { // 此处作用为,当key相同时,新value替换老的value,并将oleValue返回出去
  88. V oldValue = e.value;
  89. if (!onlyIfAbsent || oldValue == null)
  90. e.value = value;
  91. afterNodeAccess(e);
  92. return oldValue;
  93. }
  94. }
  95. ++modCount;
  96. if (++size > threshold)//如果数组大于初始量*加载因子,则进行扩容
  97. resize();
  98. afterNodeInsertion(evict);
  99. return null;
  100. }
  101. //空数组初始化方法
  102. final Node<K,V>[] resize() {
  103. Node<K,V>[] oldTab = table;
  104. int oldCap = (oldTab == null) ? 0 : oldTab.length;
  105. int oldThr = threshold;
  106. int newCap, newThr = 0;
  107. if (oldCap > 0) {
  108. if (oldCap >= MAXIMUM_CAPACITY) {
  109. threshold = Integer.MAX_VALUE;
  110. return oldTab;
  111. }
  112. else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
  113. oldCap >= DEFAULT_INITIAL_CAPACITY)
  114. newThr = oldThr << 1; // double threshold
  115. }
  116. else if (oldThr > 0) // initial capacity was placed in threshold
  117. newCap = oldThr;
  118. else { // zero initial threshold signifies using defaults
  119. newCap = DEFAULT_INITIAL_CAPACITY;
  120. newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
  121. }
  122. if (newThr == 0) {
  123. float ft = (float)newCap * loadFactor;
  124. newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
  125. (int)ft : Integer.MAX_VALUE);
  126. }
  127. threshold = newThr;
  128. @SuppressWarnings({"rawtypes","unchecked"})
  129. Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
  130. table = newTab;
  131. if (oldTab != null) {
  132. for (int j = 0; j < oldCap; ++j) {
  133. Node<K,V> e;
  134. if ((e = oldTab[j]) != null) {
  135. oldTab[j] = null;
  136. if (e.next == null)
  137. newTab[e.hash & (newCap - 1)] = e;
  138. else if (e instanceof TreeNode)
  139. ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
  140. else { // preserve order
  141. Node<K,V> loHead = null, loTail = null;
  142. Node<K,V> hiHead = null, hiTail = null;
  143. Node<K,V> next;
  144. do {
  145. next = e.next;
  146. if ((e.hash & oldCap) == 0) {
  147. if (loTail == null)
  148. loHead = e;
  149. else
  150. loTail.next = e;
  151. loTail = e;
  152. }
  153. else {
  154. if (hiTail == null)
  155. hiHead = e;
  156. else
  157. hiTail.next = e;
  158. hiTail = e;
  159. }
  160. } while ((e = next) != null);
  161. if (loTail != null) {
  162. loTail.next = null;
  163. newTab[j] = loHead;
  164. }
  165. if (hiTail != null) {
  166. hiTail.next = null;
  167. newTab[j + oldCap] = hiHead;
  168. }
  169. }
  170. }
  171. }
  172. }
  173. return newTab;
  174. }
  175. //存入红黑树
  176. final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,
  177. int h, K k, V v) {
  178. Class<?> kc = null;
  179. boolean searched = false;
  180. TreeNode<K,V> root = (parent != null) ? root() : this;
  181. for (TreeNode<K,V> p = root;;) {
  182. int dir, ph; K pk;
  183. if ((ph = p.hash) > h)
  184. dir = -1;
  185. else if (ph < h)
  186. dir = 1;
  187. else if ((pk = p.key) == k || (k != null && k.equals(pk)))
  188. return p;
  189. else if ((kc == null &&
  190. (kc = comparableClassFor(k)) == null) ||
  191. (dir = compareComparables(kc, k, pk)) == 0) {
  192. if (!searched) {
  193. TreeNode<K,V> q, ch;
  194. searched = true;
  195. if (((ch = p.left) != null &&
  196. (q = ch.find(h, k, kc)) != null) ||
  197. ((ch = p.right) != null &&
  198. (q = ch.find(h, k, kc)) != null))
  199. return q;
  200. }
  201. dir = tieBreakOrder(k, pk);
  202. }
  203.  
  204. TreeNode<K,V> xp = p;
  205. if ((p = (dir <= 0) ? p.left : p.right) == null) {
  206. Node<K,V> xpn = xp.next;
  207. TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);
  208. if (dir <= 0)
  209. xp.left = x;
  210. else
  211. xp.right = x;
  212. xp.next = x;
  213. x.parent = x.prev = xp;
  214. if (xpn != null)
  215. ((TreeNode<K,V>)xpn).prev = x;
  216. moveRootToFront(tab, balanceInsertion(root, x));
  217. return null;
  218. }
  219. }
  220. }
  221. //转换为红黑树
  222. final void treeifyBin(Node<K,V>[] tab, int hash) {
  223. int n, index; Node<K,V> e;
  224. if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
  225. resize();
  226. else if ((e = tab[index = (n - 1) & hash]) != null) {
  227. TreeNode<K,V> hd = null, tl = null;
  228. do {
  229. TreeNode<K,V> p = replacementTreeNode(e, null);
  230. if (tl == null)
  231. hd = p;
  232. else {
  233. p.prev = tl;
  234. tl.next = p;
  235. }
  236. tl = p;
  237. } while ((e = e.next) != null);
  238. if ((tab[index] = hd) != null)
  239. hd.treeify(tab);
  240. }
  241. }
  242. //扩容方法
  243. final Node<K,V>[] resize() {
  244. Node<K,V>[] oldTab = table;
  245. int oldCap = (oldTab == null) ? 0 : oldTab.length;
  246. int oldThr = threshold;
  247. int newCap, newThr = 0;
  248. if (oldCap > 0) {
  249. if (oldCap >= MAXIMUM_CAPACITY) {
  250. threshold = Integer.MAX_VALUE;
  251. return oldTab;
  252. }
  253. else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
  254. oldCap >= DEFAULT_INITIAL_CAPACITY)
  255. newThr = oldThr << 1; // double threshold
  256. }
  257. else if (oldThr > 0) // initial capacity was placed in threshold
  258. newCap = oldThr;
  259. else { // zero initial threshold signifies using defaults
  260. newCap = DEFAULT_INITIAL_CAPACITY;
  261. newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
  262. }
  263. if (newThr == 0) {
  264. float ft = (float)newCap * loadFactor;
  265. newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
  266. (int)ft : Integer.MAX_VALUE);
  267. }
  268. threshold = newThr;
  269. @SuppressWarnings({"rawtypes","unchecked"})
  270. Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
  271. table = newTab;
  272. if (oldTab != null) {
  273. for (int j = 0; j < oldCap; ++j) {
  274. Node<K,V> e;
  275. if ((e = oldTab[j]) != null) {
  276. oldTab[j] = null;
  277. if (e.next == null)
  278. newTab[e.hash & (newCap - 1)] = e;
  279. else if (e instanceof TreeNode)
  280. ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
  281. else { // preserve order
  282. Node<K,V> loHead = null, loTail = null;
  283. Node<K,V> hiHead = null, hiTail = null;
  284. Node<K,V> next;
  285. do {
  286. next = e.next;
  287. if ((e.hash & oldCap) == 0) {
  288. if (loTail == null)
  289. loHead = e;
  290. else
  291. loTail.next = e;
  292. loTail = e;
  293. }
  294. else {
  295. if (hiTail == null)
  296. hiHead = e;
  297. else
  298. hiTail.next = e;
  299. hiTail = e;
  300. }
  301. } while ((e = next) != null);
  302. if (loTail != null) {
  303. loTail.next = null;
  304. newTab[j] = loHead;
  305. }
  306. if (hiTail != null) {
  307. hiTail.next = null;
  308. newTab[j + oldCap] = hiHead;
  309. }
  310. }
  311. }
  312. }
  313. }
  314. return newTab;
  315. }
  316. //Get方法,加入很多的判断,以保证数据的准确性
  317. public V get(Object key) {
  318. Node<K,V> e;
  319. return (e = getNode(hash(key), key)) == null ? null : e.value;
  320. }
  321.  
  322. final Node<K,V> getNode(int hash, Object key) {
  323. Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
  324. if ((tab = table) != null && (n = tab.length) > 0 &&
  325. (first = tab[(n - 1) & hash]) != null) {
  326. if (first.hash == hash && // always check first node
  327. ((k = first.key) == key || (key != null && key.equals(k))))
  328. return first;
  329. if ((e = first.next) != null) {
  330. if (first instanceof TreeNode)
  331. return ((TreeNode<K,V>)first).getTreeNode(hash, key);//如果是红黑树,则用红黑树的get方法
  332. do {
  333. if (e.hash == hash &&
  334. ((k = e.key) == key || (key != null && key.equals(k))))
  335. return e;
  336. } while ((e = e.next) != null);
  337. }
  338. }
  339. return null;
  340. }
  341. //1.8取消了contans方法,用containsKey和ContainsValue
  342. public boolean containsKey(Object key) {
  343. return getNode(hash(key), key) != null;
  344. }
  345.  
  346. public boolean containsValue(Object value) {
  347. Node<K,V>[] tab; V v;
  348. if ((tab = table) != null && size > 0) {
  349. for (int i = 0; i < tab.length; ++i) {
  350. for (Node<K,V> e = tab[i]; e != null; e = e.next) {
  351. if ((v = e.value) == value ||
  352. (value != null && value.equals(v)))
  353. return true;
  354. }
  355. }
  356. }
  357. return false;
  358. }
  359. //entrySet方法
  360. public Set<Map.Entry<K,V>> entrySet() {
  361. Set<Map.Entry<K,V>> es;
  362. return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
  363. }
  364.  
  365. final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
  366. public final int size() { return size; }
  367. public final void clear() { HashMap.this.clear(); }
  368. public final Iterator<Map.Entry<K,V>> iterator() {
  369. return new EntryIterator();
  370. }
  371. public final boolean contains(Object o) {
  372. if (!(o instanceof Map.Entry))
  373. return false;
  374. Map.Entry<?,?> e = (Map.Entry<?,?>) o;
  375. Object key = e.getKey();
  376. Node<K,V> candidate = getNode(hash(key), key);
  377. return candidate != null && candidate.equals(e);
  378. }
  379. public final boolean remove(Object o) {
  380. if (o instanceof Map.Entry) {
  381. Map.Entry<?,?> e = (Map.Entry<?,?>) o;
  382. Object key = e.getKey();
  383. Object value = e.getValue();
  384. return removeNode(hash(key), key, value, true, true) != null;
  385. }
  386. return false;
  387. }
  388. public final Spliterator<Map.Entry<K,V>> spliterator() {
  389. return new EntrySpliterator<>(HashMap.this, 0, -1, 0, 0);
  390. }
  391. public final void forEach(Consumer<? super Map.Entry<K,V>> action) {
  392. Node<K,V>[] tab;
  393. if (action == null)
  394. throw new NullPointerException();
  395. if (size > 0 && (tab = table) != null) {
  396. int mc = modCount;
  397. for (int i = 0; i < tab.length; ++i) {
  398. for (Node<K,V> e = tab[i]; e != null; e = e.next)
  399. action.accept(e);
  400. }
  401. if (modCount != mc)
  402. throw new ConcurrentModificationException();
  403. }
  404. }
  405. }
  406. //keySet
  407. public Set<K> keySet() {
  408. Set<K> ks;
  409. return (ks = keySet) == null ? (keySet = new KeySet()) : ks;
  410. }
  411.  
  412. final class KeySet extends AbstractSet<K> {
  413. public final int size() { return size; }
  414. public final void clear() { HashMap.this.clear(); }
  415. public final Iterator<K> iterator() { return new KeyIterator(); }
  416. public final boolean contains(Object o) { return containsKey(o); }
  417. public final boolean remove(Object key) {
  418. return removeNode(hash(key), key, null, false, true) != null;
  419. }
  420. public final Spliterator<K> spliterator() {
  421. return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);
  422. }
  423. public final void forEach(Consumer<? super K> action) {
  424. Node<K,V>[] tab;
  425. if (action == null)
  426. throw new NullPointerException();
  427. if (size > 0 && (tab = table) != null) {
  428. int mc = modCount;
  429. for (int i = 0; i < tab.length; ++i) {
  430. for (Node<K,V> e = tab[i]; e != null; e = e.next)
  431. action.accept(e.key);
  432. }
  433. if (modCount != mc)
  434. throw new ConcurrentModificationException();
  435. }
  436. }
  437. }
  438. //remove方法
  439. public boolean remove(Object key, Object value) {
  440. return removeNode(hash(key), key, value, true, true) != null;
  441. }
  442.  
  443. final Node<K,V> removeNode(int hash, Object key, Object value,
  444. boolean matchValue, boolean movable) {
  445. Node<K,V>[] tab; Node<K,V> p; int n, index;
  446. if ((tab = table) != null && (n = tab.length) > 0 &&
  447. (p = tab[index = (n - 1) & hash]) != null) {
  448. Node<K,V> node = null, e; K k; V v;
  449. if (p.hash == hash &&
  450. ((k = p.key) == key || (key != null && key.equals(k))))
  451. node = p;
  452. else if ((e = p.next) != null) {
  453. if (p instanceof TreeNode)
  454. node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
  455. else {
  456. do {
  457. if (e.hash == hash &&
  458. ((k = e.key) == key ||
  459. (key != null && key.equals(k)))) {
  460. node = e;
  461. break;
  462. }
  463. p = e;
  464. } while ((e = e.next) != null);
  465. }
  466. }
  467. if (node != null && (!matchValue || (v = node.value) == value ||
  468. (value != null && value.equals(v)))) {
  469. if (node instanceof TreeNode)
  470. ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
  471. else if (node == p)
  472. tab[index] = node.next;
  473. else
  474. p.next = node.next;
  475. ++modCount;
  476. --size;
  477. afterNodeRemoval(node);
  478. return node;
  479. }
  480. }
  481. return null;
  482. }
  483. //size方法和isEmpty方法
  484. transient int size;//和put以及remove方法有关系,增加就++,删除就--
  485.  
  486. public int size() {
  487. return size;
  488. }
  489.  
  490. public boolean isEmpty() {
  491. return size == 0;
  492. }

对比一下可以发现,在JDK1.8的时候,hashmap有了很大的改变,不止加了很多小的优化,而且还添加红黑树用来解决哈希碰撞导致的的查询问题。

补充:

为什么要转换为红黑树?
红黑树具有很高效的查找功能,当数值不多时用链表的形式就可以应对问题,但是当链表很长的时候(发生了哈希碰撞多),hashmap在进行put和get等方法时都需要遍历链表,红黑树可以保证hashmap在发生哈希碰撞时能保证数据元素的高效定位。

为什么转为红黑树的阈(yu)值为8?
因为由链表转换成红黑树时,需要额外的空间和时间,作者根据“泊松分布”算出,出现链表长度为8的情况已经非常小了,大概是:0.00000006,所以在8的时候再转换为红黑树。

数据结构(集合)学习之Map(一)的更多相关文章

  1. 数据结构(集合)学习之Map(二)

    集合 框架关系图 补充:HashTable父类是Dictionary,不是AbstractMap. 一:HashMap中的链循环: 一般来说HashMap中的链循环会发生在多线程操作时(虽然HashM ...

  2. 数据结构(集合)学习之Set

    集合 框架关系图: Collection接口下面有三个子接口:List.Set.Queue.此篇是关于Set<E>的简单学习总结. 补充:HashTable父类是Dictionary,不是 ...

  3. 数据结构(集合)学习之Queue

    集合 框架关系图: Collection接口下面有三个子接口:List.Set.Queue.此篇是关于Queue<E>的简单学习总结. 补充:HashTable父类是Dictionary, ...

  4. 数据结构(集合)学习之List

    集合 框架关系图: Collection接口下面有三个子接口:List.Set.Queue.此篇是关于List<E>的简单学习总结. 补充:HashTable父类是Dictionary,不 ...

  5. 数据结构(集合)学习之Collection和Iterator

    集合 1.集合与数组 数组(可以存储基本数据类型)是用来存现对象的一种容器,但是数组的长度固定,不适合在对象数量未知的情况下使用. 集合(只能存储对象,对象类型可以不一样)的长度可变,可在多数情况下使 ...

  6. 【转】Java学习---Java核心数据结构(List,Map,Set)使用技巧与优化

    [原文]https://www.toutiao.com/i6594587397101453827/ Java核心数据结构(List,Map,Set)使用技巧与优化 JDK提供了一组主要的数据结构实现, ...

  7. Java学习:集合双列Map

    数据结构 数据结构: 数据结构_栈:先进后出 入口和出口在同一侧 数据结构_队列:先进先出 入口和出口在集合的两侧 数据结构_数组: 查询快:数组的地址是连续的,我们通过数组的首地址可以找到数组,通过 ...

  8. java集合学习(2):Map和HashMap

    Map接口 java.util 中的集合类包含 Java 中某些最常用的类.最常用的集合类是 List 和 Map. Map 是一种键-值对(key-value)集合,Map 集合中的每一个元素都包含 ...

  9. 2019/3/4 java集合学习(二)

    java集合学习(二) 在学完ArrayList 和 LinkedList之后,基本已经掌握了最基本的java常用数据结构,但是为了提高程序的效率,还有很多种特点各异的数据结构等着我们去运用,类如可以 ...

随机推荐

  1. Dubbo(二):深入理解Dubbo的服务发现SPI机制

    一.前言 用到微服务就不得不来谈谈服务发现的话题.通俗的来说,就是在提供服务方把服务注册到注册中心,并且告诉服务消费方现在已经存在了这个服务.那么里面的细节到底是怎么通过代码实现的呢,现在我们来看看D ...

  2. 数学建模之优劣解距法(TOPSIS)

    优劣解距法简称TOPSIS,是一种综合评价方法,利用原始数据反映各评价方案之间的差距 优劣解距法的步骤通常为: 先将原始数据针具做正向化处理,得到正向化矩阵 再对正向化矩阵标准化处理以消除各指标纲量的 ...

  3. 剑指offer刷题笔记

    删除链表中重复的结点:较难 在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针. 例如,链表1->2->3->3->4->4- ...

  4. 必须收藏的MySQL高性能优化实战总结!

    MySQL对于很多程序员来说,是一个非常棘手的问题,多数情况都是因为对数据库出现问题的情况和处理思路不清晰.在进行MySQL的优化之前必须要了解的就是MySQL的查询过程,很多的查询优化工作实际上就是 ...

  5. 2 深入分析 Java IO的工作机制(二)

    2.5 I/O调优 下面总结一些磁盘I/O和网络I/O的常用优化技巧. 2.5.1 磁盘I/O优化 1. 性能检测 应用程序通常都需要访问磁盘来读取数据,而磁盘I/O通常都很耗时,要判断I/O是否是一 ...

  6. cmake处理多源文件目录的方法(转)

    cmake处理源代码分布在不同目录中的情况也很简单,现在假设我们的源代码分布情况如下: 源代码的分布情况 其中src目录下的文件要编译成一个链接库 第一步,项目主目录中的CMakelist.txt 在 ...

  7. lwip stats

    lwip统计量分两种,一种是lwip自己的,一种是snmp的. 直接用snmp的 /* ----------------------------------- ---------- Statistic ...

  8. Unable to update index for nexus-publish | http://ip:port/repository/maven-public/

    问题描述:Unable to update index for nexus-publish | http://ip:port/repository/maven-public/ 解决方案:进入工作空间. ...

  9. scanf函数中*修饰符的作用,如:%*d

    在scanf函数中,*修饰符可以跳过所在项的输入.如下: #include <stdio.h> int main() { ; printf("请输入:"); scanf ...

  10. Python—TCP的黏包问题以及UDP的分片问题

    TCP协议与UDP协议 TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务.收发两端(客户端和服务器端)都要有一一成对的socket, ...