1. WeakHashMap简介

  WeakHashMap继承自AbstractMap,实现了Map接口。

  和HashMap一样,WeakHashMap也是一种以key-value键值对的形式进行数据的存储,底层存储结构为数组加链表,并且键值都可以为null。与HashMap不同的是,WeakHashMap实现了弱引用,当一个key不再正常使用时,会从WeakHashMap中自动移除,被GC回收并存入ReferenceQueue中。而这一特点主要是通过ReferenceQueue加WeakReference组合来实现的,ReFerenceQueue主要是配合WeakReference来记录被回收的元素,在WeakReference构造时关联一个ReferenceQueue,当该WeakReference被GC回收后便会将该对象放入ReferenceQueue中,也就是说ReferenceQueue中的key一定是被GC回收过的。所以WeakHashMap在使用中会出现size不断变小,isEmpty会从false变为true等情况。

  一般情况下,除非整个map对象被GC回收,否则里面的元素便会一致存在,不会被GC回收。而WeakHashMap中的Entry实现了WeakReference,是一种弱引用类型,弱引用类型的对象会在GC运行时被回收的,也就是说WeakHashMap的Entry中的key只要没有被其他强引用指向,便会在GC运行时被回收。key在被GC回收后会把Entry存入queue,所以这时候仅仅是key被回收了变为null,但是Entry实体还是存在WeakHashMap中的,所以在每次对WeakHashMap进行操作时,都会主动把queue中的所有Entry移除WeakHashMap,利用弱键的机制从而实现了Entry的弱引用。

  还有就是WeakHashMap中没有红黑树的优化处理,取而代之的是在hash函数里做了处理,保证了再默认加载因子0.75的情况下,链表中元素数量不会超过8个左右。

  使用场景:缓存

2. WeakHashMap实现

1. 核心参数

  和HashMap差不多,底层都是通过数组加链表的方式存储数据,默认的初始容量、加载因子和最大容量等参数都和HashMap一致,只不过增加了ReferenceQueue来存储被GC回收的弱键以及NULL_KEY,此外WeakHashMap内部Entry继承自WeakReference,实现了弱引用。在Entry被创建时,会调用父类WeakReference的构造函数并将ReferenceQueue(queue)与其相关联,当该Entry实体的key被GC回收后,便会将该实体放到queue中。

  1. //默认初始容量16
  2. private static final int DEFAULT_INITIAL_CAPACITY = 16;
  3. //最大容量2^30
  4. private static final int MAXIMUM_CAPACITY = 1 << 30;
  5. //默认加载因子0.75
  6. private static final float DEFAULT_LOAD_FACTOR = 0.75f;
  7. //底层entry数组存储
  8. Entry<K,V>[] table;
  9. //WeakHashMap大小
  10. private int size;
  11. //扩容阈值
  12. private int threshold;
  13. //加载因子
  14. private final float loadFactor;
  15. //ReferenceQueue用来存储已被GC回收的弱键
  16. private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
  17. //修改次数,用来实现fail-fast机制
  18. int modCount;
  19. //当key为null时,用NULL_KEY来代替
  20. private static final Object NULL_KEY = new Object();
  21. //entry实体的set集合,用于foreach遍历
  22. private transient Set<Map.Entry<K,V>> entrySet;
  23. //entry实体,继承自WeakReference,实现了Map.Entry接口
  24. private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
        
  25. V value;
  26. final int hash;
  27. Entry<K,V> next;
  28.  
  29. /**
  30. * Creates new entry.
  31. */
  32. Entry(Object key, V value, ReferenceQueue<Object> queue, int hash, Entry<K,V> next) {
            //调用父类WeakReference的构造函数,把queue作为被GC回收的队列,key没有被引用时会被GC回收,然后将Entry放入queue中
  33. super(key, queue);
  34. this.value = value;
  35. this.hash = hash;
  36. this.next = next;
  37. }
  38.  
  39. @SuppressWarnings("unchecked")
  40. public K getKey() {
  41. return (K) WeakHashMap.unmaskNull(get());
  42. }
  43.  
  44. public V getValue() {
  45. return value;
  46. }
  47.  
  48. public V setValue(V newValue) {
  49. V oldValue = value;
  50. value = newValue;
  51. return oldValue;
  52. }
  53.  
  54. public boolean equals(Object o) {
  55. if (!(o instanceof Map.Entry))
  56. return false;
  57. Map.Entry<?,?> e = (Map.Entry<?,?>)o;
  58. K k1 = getKey();
  59. Object k2 = e.getKey();
  60. if (k1 == k2 || (k1 != null && k1.equals(k2))) {
  61. V v1 = getValue();
  62. Object v2 = e.getValue();
  63. if (v1 == v2 || (v1 != null && v1.equals(v2)))
  64. return true;
  65. }
  66. return false;
  67. }
  68.  
  69. public int hashCode() {
  70. K k = getKey();
  71. V v = getValue();
  72. return Objects.hashCode(k) ^ Objects.hashCode(v);
  73. }
  74.  
  75. public String toString() {
  76. return getKey() + "=" + getValue();
  77. }
  78. }

2. 构造函数

  WeakHashMap的构造函数和HashMap的构造函数也有点类似,围绕着初始容量和加载因子两个核心参数进行构造。不同的是,WeakHashMap在构造时就已经把底层entry数组实例化了,而HashMap在第一次添加元素时才进行底层Entry数组的实例化。

  1. //构造一个指定加载因子的WeakHashMap,把初始容量设为大于等于initialCapacity的最小2次幂
  2. public WeakHashMap(int initialCapacity, float loadFactor) {
  3. //初始容量不能小于0,否则抛出异常
  4. if (initialCapacity < 0)
  5. throw new IllegalArgumentException("Illegal Initial Capacity: "+ initialCapacity);
  6. //初始容量不能大于2^30
  7. if (initialCapacity > MAXIMUM_CAPACITY)
  8. initialCapacity = MAXIMUM_CAPACITY;
  9. //加载因子必须大于0,小于等于1
  10. if (loadFactor <= 0 || Float.isNaN(loadFactor))
  11. throw new IllegalArgumentException("Illegal Load factor: "+ loadFactor);
  12. //把初始容量设为大于等于initialCapacity的最小2次幂
  13. int capacity = 1;
  14. while (capacity < initialCapacity)
  15. capacity <<= 1;
  16. //实例化底层Entry数组
  17. table = newTable(capacity);
  18. //设置加载因子
  19. this.loadFactor = loadFactor;
  20. //扩容阈值设为 容量*加载因子
  21. threshold = (int)(capacity * loadFactor);
  22. }
  23.  
  24. //构造一个默认加载因子0.75的WeakHashMap,把初始容量设为大于等于initialCapacity的最小2次幂
  25. public WeakHashMap(int initialCapacity) {
  26. this(initialCapacity, DEFAULT_LOAD_FACTOR);
  27. }
  28.  
  29. //构造一个默认初始容量16、默认加载因子0.75的WeakHashMap
  30. public WeakHashMap() {
  31. this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
  32. }
  33.  
  34. //构造一个默认加载因子0.75的WeakHashMap,初始容量取能够容纳m中元素的最小2次幂(最小为16),并将m中所有元素放入WeakHashMap
  35. public WeakHashMap(Map<? extends K, ? extends V> m) {
  36. this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
  37. putAll(m);
  38. }

3.  核心方法

  1. //当key为null时,返回一个NULL_KEY对象
  2. private static Object maskNull(Object key) {
  3. return (key == null) ? NULL_KEY : key;
  4. }
  5.  
  6. //当对象时NULL_KEY时,返回null
  7. static Object unmaskNull(Object key) {
  8. return (key == NULL_KEY) ? null : key;
  9. }
  10.  
  11. //equals比较
  12. private static boolean eq(Object x, Object y) {
  13. return x == y || x.equals(y);
  14. }
  15.  
  16. //hash函数
  17. final int hash(Object k) {
  18. int h = k.hashCode();
  19.  
  20. // 这个函数确保在每个位位置上只相差常数倍的哈希码具有有限的冲突数(在默认负载因子下约为8)
  21. h ^= (h >>> 20) ^ (h >>> 12);
  22. return h ^ (h >>> 7) ^ (h >>> 4);
  23. }
  24.  
  25. //根据hashCode和length-1进行与运算得到下标索引
  26. private static int indexFor(int h, int length) {
  27. return h & (length-1);
  28. }
  29.  
  30. //删除所有被GC回收的Entry
  31. private void expungeStaleEntries() {
  32. //将queue中的对象依次取出
  33. for (Object x; (x = queue.poll()) != null; ) {
  34. //锁住queue,保证对queue的操作是线程安全的
  35. synchronized (queue) {
  36. @SuppressWarnings("unchecked")
  37. //将取出的对象转为Entry
  38. Entry<K,V> e = (Entry<K,V>) x;
  39. //获取该Entry的索引i
  40. int i = indexFor(e.hash, table.length);
  41. //取索引i处的Entry prev,用于遍历链表时存放上一节点
  42. Entry<K,V> prev = table[i];
  43. //声明Entry p,用于遍历链表时存放当前节点
  44. Entry<K,V> p = prev;
  45. //遍历链表
  46. while (p != null) {
  47. //取当前节点的下一节点next
  48. Entry<K,V> next = p.next;
  49. //如果当前节点就是被要移除的节点,则进行移除操作
  50. if (p == e) {
  51. //如果上个节点就是要被移除的节点,说明被移除的是链表头节点,则把下个节点设为链表头
  52. if (prev == e)
  53. table[i] = next;
  54. //否则把下个节点和上个节点链接起来
  55. else
  56. prev.next = next;
  57. // Must not null out e.next;
  58. // stale entries may be in use by a HashIterator
  59. e.value = null; // Help GC
  60. size--;
  61. break;
  62. }
  63. prev = p;
  64. p = next;
  65. }
  66. }
  67. }
  68. }
  69.  
  70. //获取Entry数组,获取之前先删除所有需要被移除的Entry
  71. private Entry<K,V>[] getTable() {
  72. expungeStaleEntries();
  73. return table;
  74. }
  75.  
  76. //返回WeakHashMap的大小,获取之前先删除所有需要被移除的Entry
  77. public int size() {
  78. if (size == 0)
  79. return 0;
  80. expungeStaleEntries();
  81. return size;
  82. }
  83.  
  84. //判断WeakHashMap是否为空,获取之前先删除所有需要被移除的Entry
  85. public boolean isEmpty() {
  86. return size() == 0;
  87. }
  88.  
  89. //根据key获取value
  90. public V get(Object key) {
  91. //检查key为null则返回NULL_KEY对象
  92. Object k = maskNull(key);
  93. //取key的hashCode
  94. int h = hash(k);
  95. //通过getTable获取数组,去除所有需要被回收的Entry
  96. Entry<K,V>[] tab = getTable();
  97. //计算出该key的下标索引
  98. int index = indexFor(h, tab.length);
  99. //获取该索引位置的链表头
  100. Entry<K,V> e = tab[index];
  101. //遍历链表,根据hashCode值与equals比较双重判断获取value
  102. while (e != null) {
  103. if (e.hash == h && eq(k, e.get()))
  104. return e.value;
  105. e = e.next;
  106. }
  107. return null;
  108. }
  109.  
  110. //判断WeakHashMap中是否包含key
  111. public boolean containsKey(Object key) {
  112. return getEntry(key) != null;
  113. }
  114.  
  115. //根据key获取Entry实体
  116. Entry<K,V> getEntry(Object key) {
  117. //检查key为null则返回NULL_KEY对象
  118. Object k = maskNull(key);
  119. //取key的hashCode
  120. int h = hash(k);
  121. //通过getTable获取数组,去除所有需要被回收的Entry
  122. Entry<K,V>[] tab = getTable();
  123. //计算出该key的下标索引
  124. int index = indexFor(h, tab.length);
  125. //获取该索引位置的链表头
  126. Entry<K,V> e = tab[index];
  127. //遍历链表,根据hashCode值与equals比较双重判断获取Entry
  128. while (e != null && !(e.hash == h && eq(k, e.get())))
  129. e = e.next;
  130. return e;
  131. }
  132.  
  133. //将key-value存入WeakHashMap,如果key已存在则将value替换并返回旧
  134. public V put(K key, V value) {
  135. //检查key为null则返回NULL_KEY对象
  136. Object k = maskNull(key);
  137. //取key的hashCode
  138. int h = hash(k);
  139. //通过getTable获取数组,去除所有需要被回收的Entry
  140. Entry<K,V>[] tab = getTable();
  141. //计算出该key的下标索引
  142. int i = indexFor(h, tab.length);
  143. //遍历链表,根据hashCode值与equals比较双重判断key是否存在,存在则将value替换,并直接返回旧值
  144. for (Entry<K,V> e = tab[i]; e != null; e = e.next) {
  145. if (h == e.hash && eq(k, e.get())) {
  146. V oldValue = e.value;
  147. if (value != oldValue)
  148. e.value = value;
  149. return oldValue;
  150. }
  151. }
  152. //若key不存在,则将该节点插入链表头,这里和HashMap不太一样,HashMap是尾部追加
  153. modCount++;
  154. Entry<K,V> e = tab[i];
  155. tab[i] = new Entry<>(k, value, queue, h, e);
  156. //添加元素后的WeakHashMap大小如果大于等于扩容阈值,则扩容1倍
  157. if (++size >= threshold)
  158. resize(tab.length * 2);
  159. return null;
  160. }
  161.  
  162. //核心扩容算法
  163. void resize(int newCapacity) {
  164. //获取原Entry数组,获取之前先删除所有需要被移除的Entry
  165. Entry<K,V>[] oldTable = getTable();
  166. //获取原Entry数组的容量
  167. int oldCapacity = oldTable.length;
  168. //如果原数组的容量已经达到最大值2^30,则停止扩容并防止再次扩容
  169. if (oldCapacity == MAXIMUM_CAPACITY) {
  170. threshold = Integer.MAX_VALUE;
  171. return;
  172. }
  173. //实例化新Entry数组
  174. Entry<K,V>[] newTable = newTable(newCapacity);
  175. //将原数组中的元素全部放入新数组
  176. transfer(oldTable, newTable);
  177. //将底层数组替换为新数组
  178. table = newTable;
  179.  
  180. //如果忽略空元素并处理ref队列导致大量收缩,则恢复旧表。这应该是很少见的,但是避免了垃圾填充表的无限制扩展。
  181. if (size >= threshold / 2) {
  182. threshold = (int)(newCapacity * loadFactor);
  183. } else {
  184. expungeStaleEntries();
  185. transfer(newTable, oldTable);
  186. table = oldTable;
  187. }
  188. }
  189.  
  190. //将src数组中的元素全部放入dest数组
  191. private void transfer(Entry<K,V>[] src, Entry<K,V>[] dest) {
  192. //遍历src数组
  193. for (int j = 0; j < src.length; ++j) {
  194. //依次将src数组中的元素取出
  195. Entry<K,V> e = src[j];
  196. //取出后将src中去除引用
  197. src[j] = null;
  198. //遍历链表
  199. while (e != null) {
  200. //取当前节点的下一节点next
  201. Entry<K,V> next = e.next;
  202. //获取弱键
  203. Object key = e.get();
  204. //弱键为空则说明已被GC回收,将该节点清空,方便GC回收该Entry
  205. if (key == null) {
  206. e.next = null; // Help GC
  207. e.value = null; // " "
  208. size--;
  209. } else {
  210. //不为空说明没有被回收,重新计算在dest数组中的索引
  211. int i = indexFor(e.hash, dest.length);
  212. //将该节点插入dest索引i处链表头
  213. e.next = dest[i];
  214. dest[i] = e;
  215. }
  216. e = next;
  217. }
  218. }
  219. }
  220.  
  221. //将一个map中的所有元素全部放入WeakHashMap
  222. public void putAll(Map<? extends K, ? extends V> m) {
  223. //取传入map的大小
  224. int numKeysToBeAdded = m.size();
  225. //若传入的是一个空map,直接return
  226. if (numKeysToBeAdded == 0)
  227. return;
  228.  
  229. //如果要添加映射的数量大于或等于阈值,这是保守的;明显的条件是(m.size() + size) >= 阈值,
  230. //但是这个条件可能导致map的容量是适当容量的两倍,如果要添加的键与此映射中已经存在的键重叠。
  231. //通过使用保守的计算,我们最多可额外调整一次大小。
  232. if (numKeysToBeAdded > threshold) {
  233. int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1);
  234. if (targetCapacity > MAXIMUM_CAPACITY)
  235. targetCapacity = MAXIMUM_CAPACITY;
  236. int newCapacity = table.length;
  237. while (newCapacity < targetCapacity)
  238. newCapacity <<= 1;
  239. if (newCapacity > table.length)
  240. resize(newCapacity);
  241. }
  242.  
  243. //遍历map,依次将元素存入WeakHashMap中
  244. for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
  245. put(e.getKey(), e.getValue());
  246. }
  247.  
  248. //根据key将Entry移除WeakHashMap
  249. public V remove(Object key) {
  250. //检查key为null则返回NULL_KEY对象
  251. Object k = maskNull(key);
  252. //取key的hashCode
  253. int h = hash(k);
  254. //获取Entry数组,获取之前先删除所有需要被移除的Entry
  255. Entry<K,V>[] tab = getTable();
  256. //计算出该key的下标索引
  257. int i = indexFor(h, tab.length);
  258. //取索引i处的Entry,用来遍历链表时存放上个节点
  259. Entry<K,V> prev = tab[i];
  260. //用来遍历链表时存放当前节点
  261. Entry<K,V> e = prev;
  262.  
  263. //遍历链表
  264. while (e != null) {
  265. //取当前节点的下一节点next
  266. Entry<K,V> next = e.next;
  267. //根据hashCode值与equals比较双重判断key是否相同,存在则移除该节点并返回该节点value
  268. if (h == e.hash && eq(k, e.get())) {
  269. modCount++;
  270. size--;
  271. if (prev == e)
  272. tab[i] = next;
  273. else
  274. prev.next = next;
  275. return e.value;
  276. }
  277. //不存在继续遍历
  278. prev = e;
  279. e = next;
  280. }
  281.  
  282. return null;
  283. }
  284.  
  285. //移除WeakHashMap中指定Entry
  286. boolean removeMapping(Object o) {
  287. //判断对象o是否为Entry实例,不是则直接return
  288. if (!(o instanceof Map.Entry))
  289. return false;
  290. //获取Entry数组,获取之前先删除所有需要被移除的Entry
  291. Entry<K,V>[] tab = getTable();
  292. //将对象o强转为Entry实体
  293. Map.Entry<?,?> entry = (Map.Entry<?,?>)o;
  294. //检查key为null则返回NULL_KEY对象
  295. Object k = maskNull(entry.getKey());
  296. //取key的hashCode
  297. int h = hash(k);
  298. //计算出该key的下标索引
  299. int i = indexFor(h, tab.length);
  300. //取索引i处的Entry,用来遍历链表时存放上个节点
  301. Entry<K,V> prev = tab[i];
  302. //用来遍历链表时存放当前节点
  303. Entry<K,V> e = prev;
  304.  
  305. //遍历链表
  306. while (e != null) {
  307. //取当前节点的下一节点next
  308. Entry<K,V> next = e.next;
  309. //根据hashCode值与equals比较双重判断Entry是否为要移除的对象,若是则移除并返回true
  310. if (h == e.hash && e.equals(entry)) {
  311. modCount++;
  312. size--;
  313. if (prev == e)
  314. tab[i] = next;
  315. else
  316. prev.next = next;
  317. return true;
  318. }
  319. //若不是则继续遍历
  320. prev = e;
  321. e = next;
  322. }
  323.  
  324. return false;
  325. }
  326.  
  327. //清空WeakHashMap
  328. public void clear() {
  329. //清空queue,将queue中的元素一个个取出。这是神马骚操作,长见识了!!!
  330. while (queue.poll() != null)
  331. ;
  332.  
  333. modCount++;
  334. //通过Arrays.fill把底层数组所有元素全部清空
  335. Arrays.fill(table, null);
  336. size = 0;
  337.  
  338. //将数组清空后可能引发了GC导致queue中又添加了元素,再次清空
  339. while (queue.poll() != null)
  340. ;
  341. }
  342.  
  343. //判断WeakHashMap中是否包含value
  344. public boolean containsValue(Object value) {
  345. //如果value为空,调用是否包含空value逻辑
  346. if (value==null)
  347. return containsNullValue();
  348. //获取Entry数组,获取之前先删除所有需要被移除的Entry
  349. Entry<K,V>[] tab = getTable();
  350. //遍历数组
  351. for (int i = tab.length; i-- > 0;)
  352. //遍历链表
  353. for (Entry<K,V> e = tab[i]; e != null; e = e.next)
  354. //equals比较value值,相同则返回true
  355. if (value.equals(e.value))
  356. return true;
  357. return false;
  358. }
  359.  
  360. //判断WeakHashMap是否包含空value
  361. private boolean containsNullValue() {
  362. Entry<K,V>[] tab = getTable();
  363. for (int i = tab.length; i-- > 0;)
  364. for (Entry<K,V> e = tab[i]; e != null; e = e.next)
  365. if (e.value==null)
  366. return true;
  367. return false;
  368. }
  369.  
  370. //返回WeakHashMap中的所有key的set集合
  371. public Set<K> keySet() {
  372. Set<K> ks = keySet;
  373. if (ks == null) {
  374. ks = new KeySet();
  375. keySet = ks;
  376. }
  377. return ks;
  378. }
  379.  
  380. //返回WeakHashMap中的所有value的集合
  381. public Collection<V> values() {
  382. Collection<V> vs = values;
  383. if (vs == null) {
  384. vs = new Values();
  385. values = vs;
  386. }
  387. return vs;
  388. }
  389.  
  390. //返回WeakHashMap中的所有Entry的set集合
  391. public Set<Map.Entry<K,V>> entrySet() {
  392. Set<Map.Entry<K,V>> es = entrySet;
  393. return es != null ? es : (entrySet = new EntrySet());
  394. }

学习JDK1.8集合源码之--WeakHashMap的更多相关文章

  1. 学习JDK1.8集合源码之--LinkedHashSet

    1. LinkedHashSet简介 LinkedHashSet继承自HashSet,故拥有HashSet的全部API,LinkedHashSet内部实现简单,核心参数和方法都继承自HashSet,只 ...

  2. 学习JDK1.8集合源码之--ArrayList

    参考文档: https://cloud.tencent.com/developer/article/1145014 https://segmentfault.com/a/119000001857894 ...

  3. 学习JDK1.8集合源码之--HashMap

    1. HashMap简介 HashMap是一种key-value结构存储数据的集合,是map集合的经典哈希实现. HashMap允许存储null键和null值,但null键最多只能有一个(HashSe ...

  4. 学习JDK1.8集合源码之--ArrayDeque

    1. ArrayDeque简介 ArrayDeque是基于数组实现的一种双端队列,既可以当成普通的队列用(先进先出),也可以当成栈来用(后进先出),故ArrayDeque完全可以代替Stack,Arr ...

  5. 学习JDK1.8集合源码之--HashSet

    1. HashSet简介 HashSet是一个不可重复的无序集合,底层由HashMap实现存储,故HashSet是非线程安全的,由于HashSet使用HashMap的Key来存储元素,而HashMap ...

  6. 学习JDK1.8集合源码之--Vector

    1. Vector简介 Vector是JDK1.0版本就推出的一个类,和ArrayList一样,继承自AbstractList,实现了List.RandomAccess.Cloneable.java. ...

  7. 学习JDK1.8集合源码之--TreeMap

    1. TreeMap简介 TreeMap继承自AbstractMap,实现了NavigableMap.Cloneable.java.io.Serializable接口.所以TreeMap也是一个key ...

  8. 学习JDK1.8集合源码之--LinkedHashMap

    1. LinkedHashMap简介 LinkedHashMap继承自HashMap,实现了Map接口. LinkedHashMap是HashMap的一种有序实现(多态,HashMap的有序态),可以 ...

  9. 学习JDK1.8集合源码之--PriorityQueue

    1. PriorityQueue简介 PriorityQueue是一种优先队列,不同于普通队列的先进先出原则,优先队列是按照元素的优先级出列,每次出列都是优先级最高的元素.优先队列的应用很多,最典型的 ...

随机推荐

  1. 洛谷 P3120 [USACO15FEB]牛跳房子(金)Cow Hopscotch (Gold)

    P3120 [USACO15FEB]牛跳房子(金)Cow Hopscotch (Gold) 就像人类喜欢跳格子游戏一样,FJ的奶牛们发明了一种新的跳格子游戏.虽然这种接近一吨的笨拙的动物玩跳格子游戏几 ...

  2. CLOSE_WAIT问题讨论

    1.https://cloud.tencent.com/developer/article/1347610 2.https://blog.huoding.com/2016/01/19/488 3.ht ...

  3. JS-jquery 获取当前点击的对象

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http ...

  4. 转:如何成为Linux高手

    源地址:http://www.douban.com/note/60936243/ 经过几年的发展,公司在互联网公司里面也算是大公司了,线上机器使用的操作系统都是Linux,部门有几个同事,天天都跟Li ...

  5. Django logging 的配置方法

    做开发离不开日志,以下是我在工作中写Django项目常用的logging配置. BASE_LOG_DIR = os.path.join(BASE_DIR, "log") LOGGI ...

  6. mac版pycharm的字体和行间距设置

  7. 原生js封装ajax代码

    方法一:(类似jQuery的封装方法) 1.ajax函数封装: /* *author: Ivan *date: 2014.06.01 *参数说明: *opts: {'可选参数'} **method: ...

  8. tp5异常全局返回处理

    tp5 针对对异常,在debug模式下,会直接以页面返回的形式显示出各类错误.如果debug关机,显示 页面错误!请稍后再试- ThinkPHP V5.1.38 LTS { 十年磨一剑-为API开发设 ...

  9. 计算机程序是怎么通过cpu,内存,硬盘运行起来的?

    虽然以前知道计算机里有CPU,内存,硬盘,显卡这么些东西,我还真不知道这些东西是怎么协作起来完成一段程序的,能写出程序却不懂程序,也不会向别人解释他们的关系,所以特意总结了一下,写的比较浅显,和我一样 ...

  10. 做网站-mysql表字段设计

    https://mp.weixin.qq.com/s/HhdbmQqKmiw9IVnnL0Zyag VARCHAR与CHAR如何选择 使用VARCHAR理由 字段不经常更新 字段比较长,且长度不均(比 ...