ConcurrentSkipListMap

ConcurrentSkipListMap 能解决什么问题?什么时候使用 ConcurrentSkipListMap?

  1. 1ConcurrentSkipListMap 是基于跳表实现的并发排序哈希表,映射可以根据键的自然顺序进行排序,
  2. 也可以根据创建映射时所提供的 Comparator 进行排序,具体取决于使用的构造方法。
  3. 2ConcurrentSkipListMap containsKeygetputremove 操作及其变体提供预期平均 log(n) 时间开销。
  4. 3ConcurrentSkipListMap 的功能类似于 TreeMap 的线程安全版本。
  5. 4ConcurrentSkipListMap 提供排序功能的同时,由于其插入、删除时需要维护跳表结构,性能低于 ConcurrentHashMap

如何使用 ConcurrentSkipListMap?

  1. 1)需要使用 ConcurrentNavigableMap 的一些特性时使用 ConcurrentSkipListMap

使用 ConcurrentSkipListMap 有什么风险?

  1. 1ConcurrentSkipListMap 由于底层是通过跳跃表实现的,比较耗内存。
  2. 2)新增、删除元素时需要维护跳表结构,存在一定的性能损耗。

ConcurrentSkipListMap 核心操作的实现原理?

创建实例

  1. /**
  2. * 执行键排序的比较器,如果以自然顺序排序则为 null
  3. */
  4. final Comparator<? super K> comparator;
  5. /** 延迟初始化的顶层索引 */
  6. private transient Index<K,V> head;
  7. /** 延迟初始化的元素计数 */
  8. private transient LongAdder adder;
  9. /** 延迟初始化的键集合 */
  10. private transient KeySet<K,V> keySet;
  11. /** 延迟初始化的值集合 */
  12. private transient Values<K,V> values;
  13. /** 延迟初始化的映射集合 */
  14. private transient EntrySet<K,V> entrySet;
  15. /** 延迟初始化的降序映射 */
  16. private transient SubMap<K,V> descendingMap;
  17. /**
  18. * 头结点和标记节点的 key 为 null,在节点被删除时将 val 置为null
  19. */
  20. static final class Node<K,V> {
  21. final K key; // currently, never detached
  22. V val;
  23. Node<K,V> next;
  24. Node(K key, V value, Node<K,V> next) {
  25. this.key = key;
  26. this.val = value;
  27. this.next = next;
  28. }
  29. }
  30. /**
  31. * 索引节点以表示跳表的层级
  32. */
  33. static final class Index<K,V> {
  34. // 驻留节点值
  35. final Node<K,V> node; // currently, never detached
  36. // 下节点
  37. final Index<K,V> down;
  38. // 右节点
  39. Index<K,V> right;
  40. Index(Node<K,V> node, Index<K,V> down, Index<K,V> right) {
  41. this.node = node;
  42. this.down = down;
  43. this.right = right;
  44. }
  45. }
  46. /**
  47. * 创建一个使用自然顺序进行排序的空 ConcurrentSkipListMap 实例
  48. */
  49. public ConcurrentSkipListMap() {
  50. this.comparator = null;
  51. }
  52. /**
  53. * 创建一个使用比较器 comparator 进行排序的空 ConcurrentSkipListMap 实例
  54. */
  55. public ConcurrentSkipListMap(Comparator<? super K> comparator) {
  56. this.comparator = comparator;
  57. }

添加元素

  1. /**
  2. * 1)如果目标 key 不存在,则写入新的键值对,并返回 null。
  3. * 2)如果目标 key 存在,则替换其关联的值,并返回旧值。
  4. */
  5. @Override
  6. public V put(K key, V value) {
  7. if (value == null) {
  8. throw new NullPointerException();
  9. }
  10. return doPut(key, value, false);
  11. }
  12. private V doPut(K key, V value, boolean onlyIfAbsent) {
  13. if (key == null) {
  14. throw new NullPointerException();
  15. }
  16. final Comparator<? super K> cmp = comparator;
  17. for (;;) {
  18. /**
  19. * h:head 头结点
  20. * b:predecessor 前置节点
  21. */
  22. Index<K,V> h; Node<K,V> b;
  23. VarHandle.acquireFence();
  24. // 节点所在的层次
  25. int levels = 0;
  26. // 1)head==null 表示第一次插入元素
  27. if ((h = head) == null) { // try to initialize
  28. // 创建一个标记节点
  29. final Node<K,V> base = new Node<>(null, null, null);
  30. // 创建索引节点
  31. h = new Index<>(base, null, null);
  32. // 更新头节点
  33. b = ConcurrentSkipListMap.HEAD.compareAndSet(this, null, h) ? base : null;
  34. }
  35. // 2)跳表已经有元素存在
  36. else {
  37. /**
  38. * q:index node
  39. * r:right node
  40. * d:down node
  41. */
  42. for (Index<K,V> q = h, r, d;;) { // count while descending
  43. // 索引节点的右节点不为 null
  44. while ((r = q.right) != null) {
  45. Node<K,V> p; K k;
  46. /**
  47. * 右索引节点的驻留节点为 null ||
  48. * 节点的键为 null ||
  49. * 节点的值为 null
  50. */
  51. if ((p = r.node) == null || (k = p.key) == null ||
  52. p.val == null) {
  53. // 删除接地啊 q 的右侧节点
  54. ConcurrentSkipListMap.RIGHT.compareAndSet(q, r, r.right);
  55. // 2)查找键大于当前节点键,则继续往右侧查找
  56. } else if (ConcurrentSkipListMap.cpr(cmp, key, k) > 0) {
  57. q = r;
  58. // 3)查找键小于等于当前键,则当层节点已经查找完毕,往下层查找
  59. } else {
  60. break;
  61. }
  62. }
  63. // 1)当前索引节点存在下层节点,则往下层查找
  64. if ((d = q.down) != null) {
  65. // 递增层级
  66. ++levels;
  67. // 读取下层节点,重新进入循环并尝试往右侧查找
  68. q = d;
  69. }
  70. else {
  71. /**
  72. * 2)已经到达最后一层
  73. * 则读取驻留其上的节点值,开始玩右侧遍历
  74. */
  75. b = q.node;
  76. break;
  77. }
  78. }
  79. }
  80. // 前置节点存在
  81. if (b != null) {
  82. Node<K,V> z = null; // new node, if inserted
  83. for (;;) {
  84. /**
  85. * n:node
  86. * k:key
  87. * v:value
  88. * c:Comparisons 比较值
  89. */
  90. Node<K,V> n, p; K k; V v; int c;
  91. // 1)前置节点的 next 为 null,则表示当前节点是最后一个节点
  92. if ((n = b.next) == null) {
  93. if (b.key == null) {
  94. ConcurrentSkipListMap.cpr(cmp, key, key);
  95. }
  96. c = -1;
  97. }
  98. // 2)节点键为 null
  99. else if ((k = n.key) == null) {
  100. break; // can't append; restart
  101. // 3)节点值为 null,该节点已经被删除,需要从链表中踢除
  102. } else if ((v = n.val) == null) {
  103. ConcurrentSkipListMap.unlinkNode(b, n);
  104. c = 1;
  105. }
  106. // 4)比较查找键和当前键,并且查找键比较大
  107. else if ((c = ConcurrentSkipListMap.cpr(cmp, key, k)) > 0) {
  108. // 查找下一个节点
  109. b = n;
  110. /**
  111. * 5)如果键相等 && 只有不存在才插入元素,则直接返回;否则尝试更新节点值
  112. */
  113. } else if (c == 0 &&
  114. (onlyIfAbsent || ConcurrentSkipListMap.VAL.compareAndSet(n, v, value))) {
  115. // 更新成功则返回旧值
  116. return v;
  117. }
  118. /**
  119. * 目标键大于 predecessor.key 小于 predecessor.next.key,
  120. * 将目标键值对插入到他们中间。
  121. */
  122. if (c < 0 &&
  123. ConcurrentSkipListMap.NEXT.compareAndSet(b, n,
  124. p = new Node<>(key, value, n))) {
  125. // 读取新增节点
  126. z = p;
  127. break;
  128. }
  129. }
  130. if (z != null) {
  131. // 读取随机数
  132. final int lr = ThreadLocalRandom.nextSecondarySeed();
  133. // 有 1/4 的机会基于新增节点生成索引节点
  134. if ((lr & 0x3) == 0) { // add indices with 1/4 prob
  135. final int hr = ThreadLocalRandom.nextSecondarySeed();
  136. long rnd = (long)hr << 32 | lr & 0xffffffffL;
  137. // 新增节点所在的层级,层级从 0 开始
  138. int skips = levels; // levels to descend before add
  139. Index<K,V> x = null;
  140. for (;;) { // create at most 62 indices
  141. // 基于新增节点生成顶层索引节点
  142. x = new Index<>(z, x, null);
  143. if (rnd >= 0L || --skips < 0) {
  144. break;
  145. } else {
  146. rnd <<= 1;
  147. }
  148. }
  149. /**
  150. * 新增索引节点成功 &&
  151. * 如果是新增顶层索引节点 &&
  152. * 增加新的一层
  153. */
  154. if (ConcurrentSkipListMap.addIndices(h, skips, x, cmp) && skips < 0 &&
  155. head == h) { // try to add new level
  156. // 将新增节点加到顶层
  157. final Index<K,V> hx = new Index<>(z, x, null);
  158. // 创建新的头节点
  159. final Index<K,V> nh = new Index<>(h.node, h, hx);
  160. // 更新头节点
  161. ConcurrentSkipListMap.HEAD.compareAndSet(this, h, nh);
  162. }
  163. if (z.val == null)
  164. {
  165. findPredecessor(key, cmp); // clean
  166. }
  167. }
  168. // 增加计数值
  169. addCount(1L);
  170. return null;
  171. }
  172. }
  173. }
  174. }
  175. /**
  176. * 在插入元素之后新增索引节点,从高层向低层递归插入,之后建立和前置节点的链接
  177. * Recursion depths are exponentially less probable.
  178. *
  179. * @param q 当前层级的起始索引
  180. * @param skips 插入索引时,需要跳过的层级数
  181. * @param x 插入的目标索引
  182. * @param cmp comparator
  183. */
  184. static <K,V> boolean addIndices(Index<K,V> q, int skips, Index<K,V> x,
  185. Comparator<? super K> cmp) {
  186. Node<K,V> z; K key;
  187. /**
  188. * 1)新增索引节点不为 null &&
  189. * 2)驻留数据节点不为 null &&
  190. * 3)数据节点的键不为 null &&
  191. * 4)起始索引节点不为 null
  192. */
  193. if (x != null && (z = x.node) != null && (key = z.key) != null &&
  194. q != null) { // hoist checks
  195. boolean retrying = false;
  196. for (;;) { // find splice point
  197. /**
  198. * r:right
  199. * d:down
  200. * c:Comparisons
  201. */
  202. Index<K,V> r, d; int c;
  203. /**
  204. * 当前节点的右侧节点不为 null
  205. */
  206. if ((r = q.right) != null) {
  207. Node<K,V> p; K k;
  208. // 1)尝试删除索引节点
  209. if ((p = r.node) == null || (k = p.key) == null ||
  210. p.val == null) {
  211. ConcurrentSkipListMap.RIGHT.compareAndSet(q, r, r.right);
  212. c = 0;
  213. }
  214. // 2)目标键比当前节点键大
  215. else if ((c = ConcurrentSkipListMap.cpr(cmp, key, k)) > 0) {
  216. // 往右侧查找
  217. q = r;
  218. // 3)目标键和当前键相等
  219. } else if (c == 0) {
  220. break; // stale
  221. }
  222. // 4)目标键比当前节点键小?
  223. } else {
  224. // 已经不存在右侧节点
  225. c = -1;
  226. }
  227. if (c < 0) {
  228. // 下节点不为 null && 层级数 > 0
  229. if ((d = q.down) != null && skips > 0) {
  230. // 递减层级后,往下查找
  231. --skips;
  232. q = d;
  233. }
  234. /**
  235. * 1)下节点不为 null && skip <=0 && 未出现索引添加失败 &&
  236. * 尝试在当前层级添加索引
  237. */
  238. else if (d != null && !retrying &&
  239. !ConcurrentSkipListMap.addIndices(d, 0, x.down, cmp)) {
  240. // 索引添加失败则退出
  241. break;
  242. } else {
  243. x.right = r;
  244. // 插入新增索引节点
  245. if (ConcurrentSkipListMap.RIGHT.compareAndSet(q, r, x)) {
  246. // 执行成功则退出
  247. return true;
  248. }
  249. else {
  250. retrying = true; // re-find splice point
  251. }
  252. }
  253. }
  254. }
  255. }
  256. return false;
  257. }

读取元素

  1. /**
  2. * 返回指定 key 映射的值,如果 key 不存在,则返回 null
  3. */
  4. @Override
  5. public V get(Object key) {
  6. return doGet(key);
  7. }
  8. /**
  9. * Gets value for key. Same idea as findNode, except skips over
  10. * deletions and markers, and returns first encountered value to
  11. * avoid possibly inconsistent rereads.
  12. */
  13. private V doGet(Object key) {
  14. Index<K,V> q;
  15. VarHandle.acquireFence();
  16. if (key == null) {
  17. throw new NullPointerException();
  18. }
  19. final Comparator<? super K> cmp = comparator;
  20. V result = null;
  21. if ((q = head) != null) {
  22. outer: for (Index<K,V> r, d;;) {
  23. /**
  24. * 首先向右侧查找,之后向下查找
  25. */
  26. while ((r = q.right) != null) {
  27. Node<K,V> p; K k; V v; int c;
  28. // 节点已经被删除
  29. if ((p = r.node) == null || (k = p.key) == null ||
  30. (v = p.val) == null) {
  31. ConcurrentSkipListMap.RIGHT.compareAndSet(q, r, r.right);
  32. // 目标键 > 节点键,则向右侧查找
  33. } else if ((c = ConcurrentSkipListMap.cpr(cmp, key, k)) > 0) {
  34. q = r;
  35. // 找到目标键
  36. } else if (c == 0) {
  37. // 读取值后返回
  38. result = v;
  39. break outer;
  40. } else {
  41. break;
  42. }
  43. }
  44. // 尝试向下层查找
  45. if ((d = q.down) != null) {
  46. q = d;
  47. } else {
  48. // 已经到达底层
  49. Node<K,V> b, n;
  50. // 读取索引节点驻留的数据节点之后,往右侧遍历查找
  51. if ((b = q.node) != null) {
  52. while ((n = b.next) != null) {
  53. V v; int c;
  54. final K k = n.key;
  55. // 跳过被删除节点和标记节点,如果目标键 > 节点键,也向右侧查找
  56. if ((v = n.val) == null || k == null ||
  57. (c = ConcurrentSkipListMap.cpr(cmp, key, k)) > 0) {
  58. b = n;
  59. // 目标键 <= 节点键
  60. } else {
  61. // 如果相等,则直接返回其值
  62. if (c == 0) {
  63. result = v;
  64. }
  65. // 不存在相等的键,则返回 null
  66. break;
  67. }
  68. }
  69. }
  70. break;
  71. }
  72. }
  73. }
  74. return result;
  75. }
  76. /**
  77. * 返回目标 key 关联的值,如果键不存在,则返回 defaultValue
  78. */
  79. @Override
  80. public V getOrDefault(Object key, V defaultValue) {
  81. V v;
  82. return (v = doGet(key)) == null ? defaultValue : v;
  83. }

替换值

  1. /**
  2. * 如果目标 key 存在,则替换其值为 value,并返回旧值;否则返回 null。
  3. */
  4. @Override
  5. public V replace(K key, V value) {
  6. // 键和值都非 null
  7. if (key == null || value == null) {
  8. throw new NullPointerException();
  9. }
  10. for (;;) {
  11. Node<K,V> n; V v;
  12. // 没找到目标键
  13. if ((n = findNode(key)) == null) {
  14. // 则返回 null
  15. return null;
  16. }
  17. // 尝试原子设置值,并返回旧值
  18. if ((v = n.val) != null && ConcurrentSkipListMap.VAL.compareAndSet(n, v, value)) {
  19. return v;
  20. }
  21. }
  22. }
  23. /**
  24. * 返回指定 key 映射的节点,如果键不存在,则返回 null。
  25. * 查找过程中会踢除已经被删除键值对的索引节点,并且重新通过 findPredecessor 方法
  26. * 查找基础节点进行再次遍历【如果节点的 key 为 null,则表示它是一个标记节点,
  27. * 它的前置节点将被删除。】
  28. * The traversal loops in doPut, doRemove, and findNear all include the same checks.
  29. */
  30. private Node<K,V> findNode(Object key) {
  31. if (key == null)
  32. {
  33. throw new NullPointerException(); // don't postpone errors
  34. }
  35. final Comparator<? super K> cmp = comparator;
  36. Node<K,V> b;
  37. // 读取底层有效的前置节点
  38. outer: while ((b = findPredecessor(key, cmp)) != null) {
  39. for (;;) {
  40. Node<K,V> n; K k; V v; int c;
  41. // 1)已经到达尾部,则直接退出
  42. if ((n = b.next) == null) {
  43. break outer; // empty
  44. // 2)当前节点是标记节点
  45. } else if ((k = n.key) == null) {
  46. break; // b is deleted
  47. // 3)当前索引节点驻留的数据节点关联的键值对已经被删除,则踢除当前节点
  48. } else if ((v = n.val) == null) {
  49. ConcurrentSkipListMap.unlinkNode(b, n); // n is deleted
  50. // 4)目标键 > 节点键,则往右侧查找
  51. } else if ((c = ConcurrentSkipListMap.cpr(cmp, key, k)) > 0) {
  52. b = n;
  53. // 5)找到目标节点,则直接返回
  54. } else if (c == 0) {
  55. return n;
  56. } else {
  57. break outer;
  58. }
  59. }
  60. }
  61. return null;
  62. }
  63. /**
  64. * 删除节点 n
  65. */
  66. static <K,V> void unlinkNode(Node<K,V> b, Node<K,V> n) {
  67. if (b != null && n != null) {
  68. Node<K,V> f, p;
  69. for (;;) {
  70. if ((f = n.next) != null && f.key == null) {
  71. p = f.next; // already marked
  72. break;
  73. }
  74. // 被删除节点是尾节点
  75. else if (ConcurrentSkipListMap.NEXT.compareAndSet(n, f,
  76. new Node<>(null, null, f))) {
  77. p = f; // add marker
  78. break;
  79. }
  80. }
  81. ConcurrentSkipListMap.NEXT.compareAndSet(b, n, p);
  82. }
  83. }
  84. /**
  85. * 如果目标 key 存在 && 旧值为 oldValue,则替换其值为 value,替换成功返回 true
  86. */
  87. @Override
  88. public boolean replace(K key, V oldValue, V newValue) {
  89. if (key == null || oldValue == null || newValue == null) {
  90. throw new NullPointerException();
  91. }
  92. for (;;) {
  93. Node<K,V> n; V v;
  94. // 节点不存在
  95. if ((n = findNode(key)) == null) {
  96. return false;
  97. }
  98. if ((v = n.val) != null) {
  99. // 节点值和 oldValue 不相等
  100. if (!oldValue.equals(v)) {
  101. return false;
  102. }
  103. // 原子更新旧值
  104. if (ConcurrentSkipListMap.VAL.compareAndSet(n, v, newValue)) {
  105. return true;
  106. }
  107. }
  108. }
  109. }

删除键值对

  1. /**
  2. * 如果指定的 key 存在,则删除键值对,并返回旧值;否则返回 null
  3. */
  4. @Override
  5. public V remove(Object key) {
  6. return doRemove(key, null);
  7. }
  8. /**
  9. * 定位节点,将其值置为 null,在其后添加一个删除标记节点,断开前置节点,
  10. * 移除关联的索引节点,并尝试递减层级。
  11. */
  12. final V doRemove(Object key, Object value) {
  13. if (key == null) {
  14. throw new NullPointerException();
  15. }
  16. final Comparator<? super K> cmp = comparator;
  17. V result = null;
  18. Node<K,V> b;
  19. outer: while ((b = findPredecessor(key, cmp)) != null &&
  20. result == null) {
  21. for (;;) {
  22. Node<K,V> n; K k; V v; int c;
  23. // 1)无后继节点
  24. if ((n = b.next) == null) {
  25. break outer;
  26. // 2)当前节点是一个删除标记节点
  27. } else if ((k = n.key) == null) {
  28. break;
  29. // 3)当前节点的数据节点被删除,则将其踢除
  30. } else if ((v = n.val) == null) {
  31. ConcurrentSkipListMap.unlinkNode(b, n);
  32. // 4)目标键 > 节点键,则往右侧查找
  33. } else if ((c = ConcurrentSkipListMap.cpr(cmp, key, k)) > 0) {
  34. b = n;
  35. // 5)无匹配键,则直接返回
  36. } else if (c < 0) {
  37. break outer;
  38. // 6)如果值匹配,则删除的场景
  39. } else if (value != null && !value.equals(v)) {
  40. break outer;
  41. // 7)将节点值置为 null
  42. } else if (ConcurrentSkipListMap.VAL.compareAndSet(n, v, null)) {
  43. // 读取旧值
  44. result = v;
  45. // 踢除节点
  46. ConcurrentSkipListMap.unlinkNode(b, n);
  47. break; // loop to clean up
  48. }
  49. }
  50. }
  51. if (result != null) {
  52. // 尝试递减层级
  53. tryReduceLevel();
  54. addCount(-1L);
  55. }
  56. return result;
  57. }
  58. /**
  59. * 如果指定的 key 存在 && 键关联的值和 value 相等,则删除键值对,并返回 true。
  60. */
  61. @Override
  62. public boolean remove(Object key, Object value) {
  63. if (key == null) {
  64. throw new NullPointerException();
  65. }
  66. return value != null && doRemove(key, value) != null;
  67. }

ConcurrentSkipListMap 源码分析的更多相关文章

  1. 【死磕 Java 集合】— ConcurrentSkipListMap源码分析

    转自:http://cmsblogs.com/?p=4773 [隐藏目录] 前情提要 简介 存储结构 源码分析 主要内部类 构造方法 添加元素 添加元素举例 删除元素 删除元素举例 查找元素 查找元素 ...

  2. 死磕 java集合之ConcurrentSkipListMap源码分析——发现个bug

    前情提要 点击链接查看"跳表"详细介绍. 拜托,面试别再问我跳表了! 简介 跳表是一个随机化的数据结构,实质就是一种可以进行二分查找的有序链表. 跳表在原有的有序链表上面增加了多级 ...

  3. 【JUC】JDK1.8源码分析之ConcurrentSkipListMap(二)

    一.前言 最近在做项目的同时也在修复之前项目的一些Bug,所以忙得没有时间看源代码,今天都完成得差不多了,所以又开始源码分析之路,也着笔记录下ConcurrentSkipListMap的源码的分析过程 ...

  4. 【JUC】JDK1.8源码分析之ConcurrentSkipListSet(八)

    一.前言 分析完了CopyOnWriteArraySet后,继续分析Set集合在JUC框架下的另一个集合,ConcurrentSkipListSet,ConcurrentSkipListSet一个基于 ...

  5. 死磕 java集合之ConcurrentSkipListSet源码分析——Set大汇总

    问题 (1)ConcurrentSkipListSet的底层是ConcurrentSkipListMap吗? (2)ConcurrentSkipListSet是线程安全的吗? (3)Concurren ...

  6. 死磕 java集合之CopyOnWriteArraySet源码分析——内含巧妙设计

    问题 (1)CopyOnWriteArraySet是用Map实现的吗? (2)CopyOnWriteArraySet是有序的吗? (3)CopyOnWriteArraySet是并发安全的吗? (4)C ...

  7. 并发-ConcurrentHashMap源码分析

    ConcurrentHashMap 参考: http://www.cnblogs.com/chengxiao/p/6842045.html https://my.oschina.net/hosee/b ...

  8. 4. SOFAJRaft源码分析— RheaKV初始化做了什么?

    前言 由于RheaKV要讲起来篇幅比较长,所以这里分成几个章节来讲,这一章讲一讲RheaKV初始化做了什么? 我们先来给个例子,我们从例子来讲: public static void main(fin ...

  9. JUC源码分析-集合篇:并发类容器介绍

    JUC源码分析-集合篇:并发类容器介绍 同步类容器是 线程安全 的,如 Vector.HashTable 等容器的同步功能都是由 Collections.synchronizedMap 等工厂方法去创 ...

随机推荐

  1. Spring如何读取xml配置文件的

    我们通过一个小案例来看xml解析过程. 1. 导包 <dependencies> <!-- xml解析工具 --> <dependency> <groupId ...

  2. Scala学习笔记(6)对象

    1.单例对象.Scala没有静态方法或字段,可以使用object这个语法结构来达到同样的目的.对象定义了单个实例,包含了你想要的特性. object Accounts{ def newUniqueNu ...

  3. myeclipse2014下卸载,安装maven插件。

    转自:https://blog.csdn.net/gaoshang10/article/details/21177893 一.卸载方法: 点击Help->About Myeclipse Ente ...

  4. vue-cli proxyTable中跨域中pathRewrite 解释

      问:proxyTable 里面的pathRewrite里面的‘^/iclient’:'' 什么意思? 答:用代理, 首先你得有一个标识, 告诉他你这个连接要用代理. 不然的话, 可能你的 html ...

  5. Ubuntu16.04 php7.1安装redis扩展

    sudo apt install php7.1-redis //修改php配置 vi /etc/php.ini 添加extension=redis.so

  6. springboot之学习搭建

    什么是**SpringBoot?** Spring Boot 是由 Pivotal 团队提供的全新框架,其设计目的是用来简化新 Spring 应用的初始搭建以及开发过程.该框架使用了特定的方式来进行配 ...

  7. Python3 获取当前文件名

    #__author: mac#date: 2018/12/16 import osimport sys print(__file__)print(sys.argv[0])print(os.path.d ...

  8. Codeforces 957 水位标记思维题

    A #include <bits/stdc++.h> #define PI acos(-1.0) #define mem(a,b) memset((a),b,sizeof(a)) #def ...

  9. hdu 1059 Dividing bitset 多重背包

    bitset做法 #include <bits/stdc++.h> #define PI acos(-1.0) #define mem(a,b) memset((a),b,sizeof(a ...

  10. 10年前文章_eclipse下perl环境搭建

    eclipse下perl环境搭建1.Eclipse下安装perl插件Help -Software Updates…- Available .- Add Site… :http://e-p-i-c ...