HashMap中红黑树的定义和它内部的方法

     static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {TreeNode<K,V> parent;  //父亲节点 red-black tree linksTreeNode<K,V> left;//左子树TreeNode<K,V> right;//右子树TreeNode<K,V> prev;    // needed, to unlink next upon deletion删除的时候使用boolean red;//标记颜色,默认红色TreeNode(int hash, K key, V val, Node<K,V> next) {super(hash, key, val, next);}final TreeNode<K,V> root(){//返回节点的根节点...}static <K,V> void moveRootToFront(Node<K,V>[] tab, TreeNode<K,V> root){... //把给定节点设为桶中的第一个元素}final TreeNode<K,V> find(int h, Object k, Class<?> kc){... //从当前结点this开始通过给定的hash和key查找结点}final TreeNode<K,V> getTreeNode(int h, Object k) {... // 从根节点开始寻找节点}static int tieBreakOrder(Object a, Object b) {...//用来排序}final void treeify(Node<K,V>[] tab){...//链表树化}final Node<K,V> untreeify(HashMap<K,V> map){...//转化回链表}final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,int h, K k, V v){...//放入树节点}final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,boolean movable) {...//删除节点}final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit){...//Resize()调用}static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,TreeNode<K,V> p){...//左旋                                                  }static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root, TreeNode<K,V> p){...//右旋}static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,TreeNode<K,V> x) {...//插入后保持平衡}static <K,V> TreeNode<K,V> balanceDeletion(TreeNode<K,V> root,TreeNode<K,V> x) {...//删除后保持平衡}static <K,V> boolean checkInvariants(TreeNode<K,V> t) {}

转化为红黑树–treeifyBin 方法

在HashMap中put方法时候,但数组中某个位置的链表长度大于8时,会调用treeifyBin方法将链表转化为红黑树。

     final void treeifyBin(Node<K,V>[] tab, int hash) {int n, index; Node<K,V> e;// 如果桶数组table为空,或者桶数组table的长度小于MIN_TREEIFY_CAPACITY,不符合转化为红黑树的条件if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)// 数组大小小于64的,调用resize将数组大小扩容至2倍大小resize();// 如果符合转化为红黑树的条件,而且hash对应的桶不为null, 则重新计算 hash段位,及table的索引位,第一个节点else if ((e = tab[index = (n - 1) & hash]) != null) {TreeNode<K,V> hd = null, tl = null;do {// 遍历链表,将链表元素转化成TreeNode链TreeNode<K,V> p = replacementTreeNode(e, null);// 确定树头节点if (tl == null)// TreeNode链为空,将元素设置为hd的首个节点hd = p;else {// TreeNode链不为空,向TreeNode链后面添加元素p.prev = tl;tl.next = p;}tl = p;} while ((e = e.next) != null);// 前面仅仅转换为双向链表,treeify才是转换红黑树的处理方法入口 // 让桶的第一个元素指向新建的红黑树头结点,以后这个桶里的元素就是红黑树而不是链表了if ((tab[index] = hd) != null)// TreeNode链表转化为红黑树hd.treeify(tab);}}

构成红黑树–treeify 方法

将Treenode链转化成红黑树

         final void treeify(Node<K,V>[] tab) {// root节点TreeNode<K,V> root = null;// 遍历TreeNode链for (TreeNode<K,V> x = this, next; x != null; x = next) {// next 下一个节点next = (TreeNode<K,V>)x.next;// 设置左右节点为空x.left = x.right = null;// 第一次进入循环 root == null,确定头结点,为黑色if (root == null) {// 将根节点的父节点设置位空x.parent = null;// 将根节点设置为黑色x.red = false;//将x 设置为根节点root = x;}// 后面进入循环走的逻辑,x 指向树中的某个节点。 此处为非根节点else {// 获取当前循环节点keyK k = x.key;// 获取当前节点 hashint h = x.hash;Class<?> kc = null;// 从根节点开始验证,遍历所有节点跟当前节点 x 比较,调整位置,有点像冒泡排序for (TreeNode<K,V> p = root;;) {// 循环查找当前节点插入的位置并添加节点int dir, ph;// 每个节点的 keyK pk = p.key;// hashMap元素的hash值用来表示红黑树中节点数值大小if ((ph = p.hash) > h)// 当前节点值小于根节点,dir = -1 沿左路径查找dir = -1;else if (ph < h)// 当前节点值大于根节点, dir = 1 沿右路径查找dir = 1;// 如果存在比较对象,则根据比较对象定义的comparable进行比较// 比较之后返回查询节点路径(左或右)else if ((kc == null &&(kc = comparableClassFor(k)) == null) ||(dir = compareComparables(kc, k, pk)) == 0)// 当前节点的值等于根节点值。// 如果当前节点实现Comparable接口,调用compareTo比较大小并赋值dir// 如果当前节点没有实现Comparable接口,compareTo结果等于0,则调用tieBreakOrder继续比较大小// tieBreakOrder本质是通过比较k与pk的hashcodedir = tieBreakOrder(k, pk);// 当前“根节点”赋值给xpTreeNode<K,V> xp = p;if ((p = (dir <= 0) ? p.left : p.right) == null) {// 如果当前节点小于根节点且左子节点为空 或者  当前节点大于根节点且右子节点为空,直接添加子节点// 将px设置为x的父节点x.parent = xp;if (dir <= 0)xp.left = x;elsexp.right = x;// 平衡红黑树,将二叉树转换位红黑树-正式转换红黑树root = balanceInsertion(root, x);// 跳出循环,继续向红黑树添加下一个元素break;}}}}// 确保红黑树根节点是数组中该index的第一个节点moveRootToFront(tab, root);}

新增元素后平衡红黑树–balanceInsertion方法

    static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,TreeNode<K,V> x) {// 新增节点默认是红色x.red = true;// xp父节点 xpp祖父节点 xppl祖父左节点 xppr 祖父右节点for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {// xp = x.parent// 如果x存在父节点,则说明目前只有一个节点,即root.根据红黑树的五大特征,根节点只能为黑色节点if ((xp = x.parent) == null) {// x的父节点为空,x应为根节点,应为黑色x.red = false;return x;}// xpp = xp.parent, 直接查询的是根节点else if (!xp.red || (xpp = xp.parent) == null)// 父节点是黑色,祖父节点为空,直接返回return root;// xppl = xpp.left.  x的父节点是左节点时if (xp == (xppl = xpp.left)) {// 验证是否需要旋转// xppr = xpp.right 存在右节点 且 右节点为红色if ((xppr = xpp.right) != null && xppr.red) {// 叔父节点设为黑色xppr.red = false;// 父节点设为黑色xp.red = false;// 祖父节点设置为红色xpp.red = true;// 将祖父节点设置为当前节点,并继续循环操作x = xpp;}else {// 叔父节点为黑色或者空if (x == xp.right) {// x为父节点右节点,则要进行左旋操作root = rotateLeft(root, x = xp);xpp = (xp = x.parent) == null ? null : xp.parent;}// 经过左旋x为左节点if (xp != null) {// 父节点涂成黑色xp.red = false;if (xpp != null) {// 祖父节点不为空// 祖父节点设为红色xpp.red = true;// 以租父节点为支点右旋转root = rotateRight(root, xpp);}}}}else {// 验证是否需要旋转if (xppl != null && xppl.red) {// 将叔父节点设为黑色xppl.red = false;// 父节点设为黑色xp.red = false;// 祖父节点设为红色xpp.red = true;// 循环操作x = xpp;}else {if (x == xp.left) {// 右旋root = rotateRight(root, x = xp);xpp = (xp = x.parent) == null ? null : xp.parent;}// x为右节点if (xp != null) {xp.red = false;if (xpp != null) {xpp.red = true;// 以祖父节点为支点左旋root = rotateLeft(root, xpp);}}}}}}

向红黑树添加元素 – putTreeVal 方法

      final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,int h, K k, V v) {Class<?> kc = null;boolean searched = false;// 获取根节点TreeNode<K,V> root = (parent != null) ? root() : this;for (TreeNode<K,V> p = root;;) {    // 从root节点开始遍历int dir, ph; K pk;// 通过比较hash大小确定添加元素的位置if ((ph = p.hash) > h)dir = -1;else if (ph < h)dir = 1;else if ((pk = p.key) == k || (k != null && k.equals(pk)))// key相同直接返回return p;else if ((kc == null &&(kc = comparableClassFor(k)) == null) ||(dir = compareComparables(kc, k, pk)) == 0) {if (!searched) {TreeNode<K,V> q, ch;searched = true;// 有相同节点直接返回if (((ch = p.left) != null &&(q = ch.find(h, k, kc)) != null) ||((ch = p.right) != null &&(q = ch.find(h, k, kc)) != null))return q;}dir = tieBreakOrder(k, pk);}TreeNode<K,V> xp = p;// 根据dir大小添加元素if ((p = (dir <= 0) ? p.left : p.right) == null) {Node<K,V> xpn = xp.next;// 构建新的treeNode节点TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);if (dir <= 0)xp.left = x;elsexp.right = x;xp.next = x;x.parent = x.prev = xp;if (xpn != null)((TreeNode<K,V>)xpn).prev = x;// 平衡红黑树并保证root是index处首节点moveRootToFront(tab, balanceInsertion(root, x));return null;}}}

获取方法–getTreeNode

    // 获取红黑树的指定节点final TreeNode<K,V> getTreeNode(int h, Object k) {return ((parent != null) ? root() : this).find(h, k, null);// 从根节点开始查询}// 获取红黑树指定节点final TreeNode<K,V> find(int h, Object k, Class<?> kc) {// 此节点p就是根节点,进入循环后p代表当前节点TreeNode<K,V> p = this;do {// 定义当前节点p的hash值ph、相对位置dir、keyint ph, dir; K pk;// 获取当前节点的左子节点、右子节点TreeNode<K,V> pl = p.left, pr = p.right, q// 表明目标节点在当前节点的左子节点if ((ph = p.hash) > h)p = pl;// 表明目标节点在当前节点的右子节点else if (ph < h)p = pr;// 当前节点的hash值与目标节点hash值相等,且当前节点的key与目标key相等(equals)else if ((pk = p.key) == k || (k != null && k.equals(pk)))return p;// 当前节点的hash值与目标节点hash值相等,且当前节点的key与目标key不相等(equals)else if (pl == null)p = pr;else if (pr == null)p = pl;// 当前节点的hash值与目标节点hash值相等,// 且当前节点的key与目标key不相等(equals),// 且左子节点与右子节点均不为null,目标key实现Comparable接口,且与当前节点比较不为0else if ((kc != null ||(kc = comparableClassFor(k)) != null) && (dir = compareComparables(kc, k, pk)) != 0)p = (dir < 0) ? pl : pr;// 当前节点的hash值与目标节点hash值相等,// 且当前节点的key与目标key不相等(equals),// 且左子节点与右子节点均不为null,目标key没有实现Comparable接口,// 则直接在右子树中查询,这个方法并没有在左子树中循环,因为这是一个递归方法,// 先遍历右子树并判断是否查找到,若无则将左子树根节点作为当前节点,不用遍历左子树依然可以覆盖全部情况else if ((q = pr.find(h, k, kc)) != null)return q;elsep = pl;} while (p != null);return null;// 未找到,返回null}

红黑树节点的删除–removeTreeNode方法

         final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,boolean movable) {int n;// 判断是否为空,是,直接返回if (tab == null || (n = tab.length) == 0)return;// 计算下标int index = (n - 1) & hash;TreeNode<K,V> first = (TreeNode<K,V>)tab[index], root = first, rl;// succ指向删除节点的下一个,pred指向前一个TreeNode<K,V> succ = (TreeNode<K,V>)next, pred = prev;if (pred == null)// 前一个为空,tab[index] 和 first 指向后一个节点tab[index] = first = succ;else// 前一个节点不为空,则前一个节点的后一个节点指向后一个,就是删除当前节点pred.next = succ;if (succ != null)// 如果后一个节点不为空,将他的前一个节点,指向当前节点的前一个succ.prev = pred;if (first == null) // 为空直接返回return;// 根节点存在父节点,说明不是根节点,调用root()获取,确保root时根节点if (root.parent != null)root = root.root();// 根据节点及其左右孩子,判断当前红黑树节点的数量,进而转换为链表if (root == null|| (movable&& (root.right == null|| (rl = root.left) == null|| rl.left == null))) {tab[index] = first.untreeify(map);  // too smallreturn;}// p 要删除的节点,replacement删除后替代他的节点// 删除一个中级节点,但是他还有子节点,肯定连接到树上的,此时需要一个节点来顶替他的位置TreeNode<K,V> p = this, pl = left, pr = right, replacement;// 删除节点,左右孩子都不为空时,遍历if (pl != null && pr != null) {TreeNode<K,V> s = pr, sl;// s = pr,是当前节点的右节点开始遍历,找到最后一个左子节点// s是大于当前节点的最小值while ((sl = s.left) != null) // find successors = sl;// 颜色交换,要删除的节点,替换他的节点boolean c = s.red; s.red = p.red; p.red = c; // swap colorsTreeNode<K,V> sr = s.right;TreeNode<K,V> pp = p.parent;// 位置交换// s == pr 意思是大于要删除节点的最小节点,就是他的右节点// 在节点右边都是比它大的,在节点左边都是比它小的// 在当前节点的右边,其右边节点的左边,说明大于当前节点,小于当前节点的右节点,最左边的也就是最靠近当前节点if (s == pr) { // p was s's direct parentp.parent = s;s.right = p;}else {// 当前节点的父节点指向,替换节点的父节点TreeNode<K,V> sp = s.parent;if ((p.parent = sp) != null) {// 判断替换的节点是其父的左右节点,将替换节点的父节点的左或者右节点指向当前节点if (s == sp.left)sp.left = p;elsesp.right = p;}// 替换节点的右节点指向删除节点的右节点,删除节点的右节点的父节点,指向替换节点if ((s.right = pr) != null)pr.parent = s;}// 删除节点的左节点置空// 替换节点肯定不存在左节点,不然就不会成为替换节点p.left = null;// 但不一定没有右节点,进行右节点赋值if ((p.right = sr) != null)sr.parent = p;// 将替换节点的左节点指向删除节点的左节点if ((s.left = pl) != null)pl.parent = s;// 替换节点的父节点,指向删除节点的父节点,如果父节点为空,则替换节点作为根节点if ((s.parent = pp) == null)root = s;// 删除节点有父节点,且为父节点的左节点,父节点的左节点指向替换节点,反之亦然else if (p == pp.left)pp.left = s;elsepp.right = s;// 替换节点存在右节点,左节点是肯定不存在的// replacement 为替换节点右节点,反之为删除节点if (sr != null)replacement = sr;elsereplacement = p;}// 删除节点存在左节点,replacement为左节点else if (pl != null)replacement = pl;// 删除节点存在右节点,replacement为右节点else if (pr != null)replacement = pr;// 左右节点都不存在,replacement为删除节点elsereplacement = p;// replacement不为删除节点(他有子节点,或者s节点有右节点)// 看replacement的赋值p的情况//       p节点不存在子节点//         p节点存在左右节点,且s节点无右节点if (replacement != p) {// 替换节点的父节点指向当前节点的父节点TreeNode<K,V> pp = replacement.parent = p.parent;// replacement 替换 p// 存在左右节点是,此时的p为替换之后的节点,replacement为sr(替换p的节点的右节点)// 存在左节点,或者右节点,那他的子节点替换p节点if (pp == null)// 如果父节点为null,替换节点为root节点root = replacement;else if (p == pp.left)pp.left = replacement;elsepp.right = replacement;// 孤立p节点p.left = p.right = p.parent = null;}// p节点为红色,删除后无影响,不为红色需要进行平衡TreeNode<K,V> r = p.red ? root : balanceDeletion(root, replacement);// p 没有儿子或者s没有儿子,直接移除pif (replacement == p) {  // detachTreeNode<K,V> pp = p.parent;p.parent = null;if (pp != null) {if (p == pp.left)pp.left = null;else if (p == pp.right)pp.right = null;}}// 处理根节点if (movable)moveRootToFront(tab, r);}

删除元素后的平衡红黑树–balanceDeletion

    static <K,V> TreeNode<K,V> balanceDeletion(TreeNode<K,V> root,TreeNode<K,V> x) {for (TreeNode<K,V> xp, xpl, xpr;;) {// 要删除的节点是根节点,或者为null,返回根节点if (x == null || x == root)return root;// x为根节点,设为黑色,返回else if ((xp = x.parent) == null) {x.red = false;return x;}// 删除的节点是红色,无需调整else if (x.red) {x.red = false;return root;}// 移除的节点是其父的左节点else if ((xpl = xp.left) == x) {// 兄弟为红色if ((xpr = xp.right) != null && xpr.red) {// 兄弟设为黑色xpr.red = false;// 父节点为红色xp.red = true;// 左旋root = rotateLeft(root, xp);// 重新将xp指向x的父节点,xpr指向xp新的右孩子xpr = (xp = x.parent) == null ? null : xp.right;}// 没有兄弟节点,将x指向父节点,向上调整if (xpr == null)x = xp;else {// 存在兄弟节点,进一步调整// xpr是兄弟节点兄弟TreeNode<K,V> sl = xpr.left, sr = xpr.right;// 侄子为空或者侄子为黑(没有就算黑)if ((sr == null || !sr.red) &&(sl == null || !sl.red)) {// 兄弟节点设为红色,将x指向父节点xpr.red = true;x = xp;}// 侄子节点存在红色节点else {// 兄弟右节点为空或者黑子,说明兄弟左节点为红色if (sr == null || !sr.red) {// 兄弟左节点设置黑色if (sl != null)sl.red = false;// 兄弟节点设为红色xpr.red = true;// 基于兄弟节点右旋root = rotateRight(root, xpr);// 兄弟节点重新指向xpr = (xp = x.parent) == null ?null : xp.right;}// 如果兄弟节点不为空if (xpr != null) {// 让兄弟节点跟父节点同时xpr.red = (xp == null) ? false : xp.red;// 兄弟右节点不为空,设置黑色if ((sr = xpr.right) != null)sr.red = false;}// 存在父节点if (xp != null) {xp.red = false;// 设置黑色root = rotateLeft(root, xp);// 基于父节点左旋}x = root;}}}// x为右节点,与上面类似判断节点颜色,颜色变化与旋转else { // symmetricif (xpl != null && xpl.red) {xpl.red = false;xp.red = true;root = rotateRight(root, xp);xpl = (xp = x.parent) == null ? null : xp.left;}if (xpl == null)x = xp;else {TreeNode<K,V> sl = xpl.left, sr = xpl.right;if ((sl == null || !sl.red) &&(sr == null || !sr.red)) {xpl.red = true;x = xp;}else {if (sl == null || !sl.red) {if (sr != null)sr.red = false;xpl.red = true;root = rotateLeft(root, xpl);xpl = (xp = x.parent) == null ?null : xp.left;}if (xpl != null) {xpl.red = (xp == null) ? false : xp.red;if ((sl = xpl.left) != null)sl.red = false;}if (xp != null) {xp.red = false;root = rotateRight(root, xp);}x = root;}}}}}

HashMap对左旋右旋的实现

左旋
             //root为根节点,p为旋转的结点static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,TreeNode<K,V> p) {// r当前节点的右节点// pp当前节点的父节点// rl当前节点的右节点的左节点TreeNode<K,V> r, pp, rl;//如果p不为空且存在右子结点rif (p != null && (r = p.right) != null) { //判断右子结点的左子结点rl存在if ((rl = p.right = r.left) != null)    //存在设置rl的父节点为prl.parent = p;  //判断p的父节点pp是否存在if ((pp = r.parent = p.parent) == null) //如果不存在设置新的根节点为r且黑色(root = r).red = false; //父结点pp存在且p为pp的左子结点else if (pp.left == p)                 pp.left = r;else //父结点pp存在且p为pp的左子结点pp.right = r;r.left = p;p.parent = r;}return root;}
右旋
           //root为根节点,p为旋转的结点static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root,TreeNode<K,V> p) {// r当前节点的左节点// pp当前节点的父节点// rl当前节点的左节点的右节点TreeNode<K,V> l, pp, lr;//如果p不为空且存在左子结点lif (p != null && (l = p.left) != null) { //判断左子结点的右子结点lr存在if ((lr = p.left = l.right) != null)   //存在则设置rl的父结点为plr.parent = p;   //判断p的父结点pp是否存在if ((pp = l.parent = p.parent) == null)//如果不存在设置新的根节点为l且l为黑色(root = l).red = false;else if (pp.right == p)              //父结点pp存在且p为pp的右子结点pp.right = l;else                                 //父结点pp存在且p为pp的左子结点pp.left = l;l.right = p;p.parent = l;}return root;}

【源码解析】HashMap源码跟进(红黑树的实现)相关推荐

  1. 为什么HashMap中链表转红黑树的阀值是8?

    在JDK1.8以后,HashMap中引入红黑树,主要原因为: 当一个桶(Bucket)中的元素过度填充时,链表的查找效率将会大大下降,因此在适当的时候,转换链表为红黑树,可以在桶过度填充时提高查询效率 ...

  2. hashmap为什么要引入红黑树?

    hashmap为什么要引入红黑树? 在JDK1.6,JDK1.7中,HashMap采用位桶+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里.但是当位于一个桶中的元素较多,即has ...

  3. HashMap之TreeNode(红黑树)源码分析

    HashMap-TreeNode源码分析(jdk1.8 HashMap之TreeNode源码分析 属性及构造方法 find() putTreeVal() removeTreeNode() treeif ...

  4. java map 变量_Java源码解析HashMap成员变量

    本文基于jdk1.8进行分析 首先看一下HashMap的一些静态常量.第一个是DEFAULT_INITIAL_CAPACITY,默认初始大小,16.从注释中可以了解到,大小必须为2的指数.这里的16, ...

  5. 死磕 java集合之TreeMap源码分析(一)- 内含红黑树分析全过程

    欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 简介 TreeMap使用红黑树存储元素,可以保证元素按key值的大小进行遍历. 继承体系 Tr ...

  6. 死磕 java集合之TreeMap源码分析(二)- 内含红黑树分析全过程

    2019独角兽企业重金招聘Python工程师标准>>> 欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 插入元素 插入元素, ...

  7. 死磕 java集合之TreeMap源码分析(三)- 内含红黑树分析全过程

    2019独角兽企业重金招聘Python工程师标准>>> 欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 删除元素 删除元素本 ...

  8. yolov3之pytorch源码解析_springmvc源码架构解析之view

    说在前面 前期回顾 sharding-jdbc源码解析 更新完毕 spring源码解析 更新完毕 spring-mvc源码解析 更新完毕 spring-tx源码解析 更新完毕 spring-boot源 ...

  9. STL源码剖析 关联式容器 树 红黑树、二叉搜索树、平衡二叉搜索树

    所谓关联式容器,观念上类似关联式数据库(实际上则简单许多):每笔数据(每个元素)都有一个键值(key)和一个实值(value) 2.当元素被插入到关联式 容器中时,容器内部结构(可能是RB-tree, ...

  10. Kafka核心源码解析 - KafkaController源码解析

    在进入源码解析之前,我先来介绍一些KafkaController在Kafka集群中的作用. (1)负责监听zookeeper上所有的元数据变更请求: (2)负责topic的partition迁移任务分 ...

最新文章

  1. 清除右键菜单CMD入口
  2. 开发运维效率提升 80%,计算成本下降 50%,分众传媒的 Serverless 实践
  3. 2013.10u-boot移植之SD保存环境变量
  4. Uniswap 24h交易量约6.54亿美元涨18.05%
  5. java linux 信号_Java 中关于信号的处理在Linux下的实现
  6. WPF transform示例
  7. 2016计算机考研408答案,2016年计算机408统考考研真题及答案解析.pdf
  8. 新主播如何在直播行业混得好
  9. PWM智能温控风扇的原理
  10. 2018 CVPR:Pyramidal Person Re-IDentification via Multi-Loss Dynamic Training
  11. Java—二维码生成与识别(一)
  12. Modeling Personalized Item Frequency Information for Next-basket Recommendation
  13. clipboard使用总结(复制文本到剪贴板功能的JavaScript插件)
  14. 用Kanban-Ace框架改进Scrum
  15. 联通沃音乐发布283万元大数据项目采购需求
  16. 新品周刊 | ​内外、UR、迪士尼商店、Kipling、资生堂、林清轩等女王节新品发布...
  17. Excel如何批量将文本型数值转为数值
  18. [excel]如果去掉科学计数法,以及将正常的减号进行计算
  19. 继承QWidget使用QPainter自定义二维图形控件【Qt学习】
  20. android 使用mediaplayer播放网络音乐

热门文章

  1. Linux磁盘配额应用
  2. 学习CSS的背景图像属性background
  3. java 画笔跟swing组件_java组件及事件处理(简单基础的界面操作)1
  4. Android中setLayoutParams要用父控件的LayoutParams
  5. 给gridview动态生成radiobutton添加OnCheckedChanged事件
  6. golang中的并发服务器
  7. 大型网站演化发展历程之三
  8. 深入理解阻塞socket和非阻塞socket
  9. c++顺序容器vector用法
  10. idea创建mybatis的config.xml和mapper.xml方法