当前位置: 首页 > news >正文

具有口碑的柳州网站建设哪家好南城做网站

具有口碑的柳州网站建设哪家好,南城做网站,北京东道设计公司官网,网站的icp备案信息是什么文章目录 说一下HashMap的实现原理(非常重要)①HashMap的工作原理HashMap存储结构常用的变量HashMap 构造函数tableSizeFor() put()方法详解hash()计算原理resize() 扩容机制get()方法为什么HashMap链表会形成死循环 HashMap是我们在工作中使用到存储数据特别频繁的数据结构那么我们需要对其的实现原理以及 说一下HashMap的实现原理(非常重要) HashMap概述 HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作并允许使用null值和null键。此类不保证映射的顺序特别是它不保证该顺序恒久不变。 HashMap的数据结构 在Java编程语言中最基本的结构就是两种一个是数组另外一个是模拟指针引用所有的数据结构都可以用这两个基本结构来构造的HashMap也不例外。HashMap实际上是一个“链表散列”的数据结构即数组和链表的结合体。 ①HashMap的工作原理 HashMap基于hashing原理我们通过put()和get()方法储存和获取对象。当我们将键值对传递给put()方法时它调用键对象的hashCode()方法来计算hashcode让后找到bucket位置来储存值对象。当获取对象时通过键对象的equals()方法找到正确的键值对然后返回值对象。HashMap使用链表来解决碰撞问题当发生碰撞了对象将会储存在链表的下一个节点中。 HashMap在每个链表节点中储存键值对对象。 HashMap存储结构 这里需要区分一下JDK1.7和 JDK1.8之后的 HashMap 存储结构。在JDK1.7及之前是用数组加链表的方式存储的。 但是众所周知当链表的长度特别长的时候查询效率将直线下降查询的时间复杂度为 O(n)。因此JDK1.8 把它设计为达到一个特定的阈值之后就将链表转化为红黑树。 这里简单说下红黑树的特点 每个节点只有两种颜色红色或者黑色 根节点必须是黑色 每个叶子节点NIL都是黑色的空节点 从根节点到叶子节点不能出现两个连续的红色节点 从任一节点出发到它下边的子节点的路径包含的黑色节点数目都相同 由于红黑树是一个自平衡的二叉搜索树因此可以使查询的时间复杂度降为O(logn)。红黑树不是本文重点不了解的童鞋可自行查阅相关资料哈 常用的变量 在 HashMap源码中比较重要的常用变量主要有以下这些。还有两个内部类来表示普通链表的节点和红黑树节点 //默认的初始化容量为16必须是2的n次幂 static final int DEFAULT_INITIAL_CAPACITY 1 4; // aka 16//最大容量为 2^30 static final int MAXIMUM_CAPACITY 1 30;//默认的加载因子0.75乘以数组容量得到的值用来表示元素个数达到多少时需要扩容。 //为什么设置 0.75 这个值呢简单来说就是时间和空间的权衡。 //若小于0.75如0.5则数组长度达到一半大小就需要扩容空间使用率大大降低 //若大于0.75如0.8则会增大hash冲突的概率影响查询效率。 static final float DEFAULT_LOAD_FACTOR 0.75f;//刚才提到了当链表长度过长时会有一个阈值超过这个阈值8就会转化为红黑树 static final int TREEIFY_THRESHOLD 8;//当红黑树上的元素个数减少到6个时就退化为链表 static final int UNTREEIFY_THRESHOLD 6;//链表转化为红黑树除了有阈值的限制还有另外一个限制需要数组容量至少达到64才会树化。 //这是为了避免数组扩容和树化阈值之间的冲突。 static final int MIN_TREEIFY_CAPACITY 64;//存放所有Node节点的数组 transient NodeK,V[] table;//存放所有的键值对 transient SetMap.EntryK,V entrySet;//map中的实际键值对个数即数组中元素个数 transient int size;//每次结构改变时都会自增fail-fast机制这是一种错误检测机制。 //当迭代集合的时候如果结构发生改变则会发生 fail-fast抛出异常。 transient int modCount;//数组扩容阈值 int threshold;//加载因子 final float loadFactor; //普通单向链表节点类 static class NodeK,V implements Map.EntryK,V {//key的hash值put和get的时候都需要用到它来确定元素在数组中的位置final int hash;final K key;V value;//指向单链表的下一个节点NodeK,V next;Node(int hash, K key, V value, NodeK,V next) {this.hash hash;this.key key;this.value value;this.next next;} }//转化为红黑树的节点类 static final class TreeNodeK,V extends LinkedHashMap.EntryK,V {//当前节点的父节点TreeNodeK,V parent; //左孩子节点TreeNodeK,V left;//右孩子节点TreeNodeK,V right;//指向前一个节点TreeNodeK,V prev; // needed to unlink next upon deletion//当前节点是红色或者黑色的标识boolean red;TreeNode(int hash, K key, V val, NodeK,V next) {super(hash, key, val, next);} } HashMap 构造函数 HashMap有四个构造函数可供我们使用一起来看下 //默认无参构造指定一个默认的加载因子 public HashMap() {this.loadFactor DEFAULT_LOAD_FACTOR; }//可指定容量的有参构造但是需要注意当前我们指定的容量并不一定就是实际的容量下面会说 public HashMap(int initialCapacity) {//同样使用默认加载因子this(initialCapacity, DEFAULT_LOAD_FACTOR); }//可指定容量和加载因子但是笔者不建议自己手动指定非0.75的加载因子 public HashMap(int initialCapacity, float loadFactor) {if (initialCapacity 0)throw new IllegalArgumentException(Illegal initial capacity: initialCapacity);if (initialCapacity MAXIMUM_CAPACITY)initialCapacity MAXIMUM_CAPACITY;if (loadFactor 0 || Float.isNaN(loadFactor))throw new IllegalArgumentException(Illegal load factor: loadFactor);this.loadFactor loadFactor;//这里就是把我们指定的容量改为一个大于它的的最小的2次幂值如传过来的容量是14则返回16//注意这里按理说返回的值应该赋值给 capacity即保证数组容量总是2的n次幂为什么这里赋值给了 threshold 呢//先卖个关子等到 resize 的时候再说this.threshold tableSizeFor(initialCapacity); }//可传入一个已有的map public HashMap(Map? extends K, ? extends V m) {this.loadFactor DEFAULT_LOAD_FACTOR;putMapEntries(m, false); }//把传入的map里边的元素都加载到当前map final void putMapEntries(Map? extends K, ? extends V m, boolean evict) {int s m.size();if (s 0) {if (table null) { // pre-sizefloat ft ((float)s / loadFactor) 1.0F;int t ((ft (float)MAXIMUM_CAPACITY) ?(int)ft : MAXIMUM_CAPACITY);if (t threshold)threshold tableSizeFor(t);}else if (s threshold)resize();for (Map.Entry? extends K, ? extends V e : m.entrySet()) {K key e.getKey();V value e.getValue();//put方法的具体实现后边讲putVal(hash(key), key, value, false, evict);}} }tableSizeFor() 上边的第三个构造函数中调用了 tableSizeFor 方法这个方法是怎么实现的呢 static final int tableSizeFor(int cap) {int n cap - 1;n | n 1;n | n 2;n | n 4;n | n 8;n | n 16;return (n 0) ? 1 : (n MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n 1; }我们以传入参数为14 来举例计算这个过程。 首先14传进去之后先减1n此时为13。然后是一系列的无符号右移运算。 //13的二进制 0000 0000 0000 0000 0000 0000 0000 1101 //无右移1位高位补0 0000 0000 0000 0000 0000 0000 0000 0110 //然后把它和原来的13做或运算得到此时的n值 0000 0000 0000 0000 0000 0000 0000 1111 //再以上边的值右移2位 0000 0000 0000 0000 0000 0000 0000 0011 //然后和第一次或运算之后的 n 值再做或运算此时得到的n值 0000 0000 0000 0000 0000 0000 0000 1111 ... //我们会发现再执行右移 4,8,16位同样n的值不变 //当n小于0时返回1否则判断是否大于最大容量是的话返回最大容量否则返回 n1 return (n 0) ? 1 : (n MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n 1; //很明显我们这里返回的是 n1 的值 0000 0000 0000 0000 0000 0000 0000 11111 0000 0000 0000 0000 0000 0000 0001 0000将它转为十进制就是 2^4 16 。我们会发现一个规律以上的右移运算最终会把最低位的值都转化为 1111 这样的结构然后再加1就是1 0000 这样的结构它一定是 2的n次幂。因此这个方法返回的就是大于当前传入值的最小最接近当前值的一个2的n次幂的值。 put()方法详解 //put方法会先调用一个hash()方法得到当前key的一个hash值 //用于确定当前key应该存放在数组的哪个下标位置 //这里的 hash方法我们姑且先认为是key.hashCode()其实不是的一会儿细讲 public V put(K key, V value) {return putVal(hash(key), key, value, false, true); }//把hash值和当前的keyvalue传入进来 //这里onlyIfAbsent如果为true表明不能修改已经存在的值因此我们传入false //evict只有在方法 afterNodeInsertion(boolean evict) { }用到可以看到它是一个空实现因此不用关注这个参数 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {NodeK,V[] tab; NodeK,V p; int n, i;//判断table是否为空如果空的话会先调用resize扩容if ((tab table) null || (n tab.length) 0)n (tab resize()).length;//根据当前key的hash值找到它在数组中的下标判断当前下标位置是否已经存在元素//若没有则把key、value包装成Node节点直接添加到此位置。// i (n - 1) hash 是计算下标位置的为什么这样算后边讲if ((p tab[i (n - 1) hash]) null)tab[i] newNode(hash, key, value, null);else { //如果当前位置已经有元素了分为三种情况。NodeK,V e; K k;//1.当前位置元素的hash值等于传过来的hash并且他们的key值也相等//则把p赋值给e跳转到①处后续需要做值的覆盖处理if (p.hash hash ((k p.key) key || (key ! null key.equals(k))))e p;//2.如果当前是红黑树结构则把它加入到红黑树 else if (p instanceof TreeNode)e ((TreeNodeK,V)p).putTreeVal(this, tab, hash, key, value);else {//3.说明此位置已存在元素并且是普通链表结构则采用尾插法把新节点加入到链表尾部for (int binCount 0; ; binCount) {if ((e p.next) null) {//如果头结点的下一个节点为空则插入新节点p.next newNode(hash, key, value, null);//如果在插入的过程中链表长度超过了8则转化为红黑树if (binCount TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);//插入成功之后跳出循环跳转到①处break;}//若在链表中找到了相同key的话直接退出循环跳转到①处if (e.hash hash ((k e.key) key || (key ! null key.equals(k))))break;p e;}}//①//说明发生了碰撞e代表的是旧值因此节点位置不变但是需要替换为新值if (e ! null) { // existing mapping for keyV oldValue e.value;//用新值替换旧值并返回旧值。if (!onlyIfAbsent || oldValue null)e.value value;//看方法名字即可知这是在node被访问之后需要做的操作。其实此处是一个空实现//只有在 LinkedHashMap才会实现用于实现根据访问先后顺序对元素进行排序hashmap不提供排序功能// Callbacks to allow LinkedHashMap post-actions//void afterNodeAccess(NodeK,V p) { }afterNodeAccess(e);return oldValue;}}//fail-fast机制modCount;//如果当前数组中的元素个数超过阈值则扩容if (size threshold)resize();//同样的空实现afterNodeInsertion(evict);return null; } hash()计算原理 前面 put 方法中说到需要先把当前key进行哈希处理我们看下这个方法是怎么实现的。 static final int hash(Object key) {int h;return (key null) ? 0 : (h key.hashCode()) ^ (h 16); }运行一段程序把它的 hashCode的二进制打印出来如下。 public static void main(String[] args) {Object o new Object();int hash o.hashCode();System.out.println(hash);System.out.println(Integer.toBinaryString(hash));} //1836019240 //1101101011011110110111000101000然后进行 (h key.hashCode()) ^ (h 16) 这一段运算。 //h原来的值 0110 1101 0110 1111 0110 1110 0010 1000 //无符号右移16位其实相当于把低位16位舍去只保留高16位 0000 0000 0000 0000 0110 1101 0110 1111 //然后高16位和原 h进行异或运算 0110 1101 0110 1111 0110 1110 0010 1000 ^ 0000 0000 0000 0000 0110 1101 0110 11110110 1101 0110 1111 0000 0011 0100 0111 resize() 扩容机制 在上边 put 方法中我们会发现当数组为空的时候会调用 resize 方法当数组的 size 大于阈值的时候也会调用 resize方法。 那么看下 resize 方法都做了哪些事情吧。 final NodeK,V[] resize() {//旧数组NodeK,V[] oldTab table;//旧数组的容量int oldCap (oldTab null) ? 0 : oldTab.length;//旧数组的扩容阈值注意看这里取的是当前对象的 threshold 值下边的第2种情况会用到。int oldThr threshold;//初始化新数组的容量和阈值分三种情况讨论。int newCap, newThr 0;//1.当旧数组的容量大于0时说明在这之前肯定调用过 resize扩容过一次才会导致旧容量不为0。//为什么这样说呢之前我在 tableSizeFor 卖了个关子需要注意的是它返回的值是赋给了 threshold 而不是 capacity。//我们在这之前压根就没有在任何地方看到过它给 capacity 赋初始值。if (oldCap 0) {//容量达到了最大值if (oldCap MAXIMUM_CAPACITY) {threshold Integer.MAX_VALUE;return oldTab;}//新数组的容量和阈值都扩大原来的2倍else if ((newCap oldCap 1) MAXIMUM_CAPACITY oldCap DEFAULT_INITIAL_CAPACITY)newThr oldThr 1; // double threshold}//2.到这里说明 oldCap 0并且 oldThr(threshold) 0这就是 map 初始化的时候第一次调用 resize的情况//而 oldThr的值等于 threshold此时的 threshold 是通过 tableSizeFor 方法得到的一个2的n次幂的值(我们以16为例)。//因此需要把 oldThr 的值也就是 threshold 赋值给新数组的容量 newCap以保证数组的容量是2的n次幂。//所以我们可以得出结论当map第一次 put 元素的时候就会走到这个分支把数组的容量设置为正确的值2的n次幂)//但是此时 threshold 的值也是2的n次幂这不对啊它应该是数组的容量乘以加载因子才对。别着急这个会在③处理。else if (oldThr 0) // initial capacity was placed in thresholdnewCap oldThr;//3.到这里说明 oldCap 和 oldThr 都是小于等于0的。也说明我们的map是通过默认无参构造来创建的//于是数组的容量和阈值都取默认值就可以了即 16 和 12。else { // zero initial threshold signifies using defaultsnewCap DEFAULT_INITIAL_CAPACITY;newThr (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);}//③ 这里就是处理第2种情况因为只有这种情况 newThr 才为0//因此计算 newThr用 newCap即16 乘以加载因子 0.75得到 12 并把它赋值给 thresholdif (newThr 0) {float ft (float)newCap * loadFactor;newThr (newCap MAXIMUM_CAPACITY ft (float)MAXIMUM_CAPACITY ?(int)ft : Integer.MAX_VALUE);}//赋予 threshold 正确的值表示数组下次需要扩容的阈值此时就把原来的 16 修正为了 12。threshold newThr;SuppressWarnings({rawtypes,unchecked})//我们可以发现在构造函数时并没有创建数组在第一次调用put方法导致resize的时候才会把数组创建出来。这是为了延迟加载提高效率。NodeK,V[] newTab (NodeK,V[])new Node[newCap];table newTab;//如果原来的数组不为空那么我们就需要把原来数组中的元素重新分配到新的数组中//如果是第2种情况由于是第一次调用resize此时数组肯定是空的因此也就不需要重新分配元素。if (oldTab ! null) {//遍历旧数组for (int j 0; j oldCap; j) {NodeK,V e;//取到当前下标的第一个元素如果存在则分三种情况重新分配位置if ((e oldTab[j]) ! null) {oldTab[j] null;//1.如果当前元素的下一个元素为空则说明此处只有一个元素//则直接用它的hash()值和新数组的容量取模就可以了得到新的下标位置。if (e.next null)newTab[e.hash (newCap - 1)] e;//2.如果是红黑树结构则拆分红黑树必要时有可能退化为链表else if (e instanceof TreeNode)((TreeNodeK,V)e).split(this, newTab, j, oldCap);//3.到这里说明这是一个长度大于 1 的普通链表则需要计算并//判断当前位置的链表是否需要移动到新的位置else { // preserve order// loHead 和 loTail 分别代表链表旧位置的头尾节点NodeK,V loHead null, loTail null;// hiHead 和 hiTail 分别代表链表移动到新位置的头尾节点NodeK,V hiHead null, hiTail null;NodeK,V next;do {next e.next;//如果当前元素的hash值和oldCap做与运算为0则原位置不变if ((e.hash oldCap) 0) {if (loTail null)loHead e;elseloTail.next e;loTail e;}//否则需要移动到新的位置else {if (hiTail null)hiHead e;elsehiTail.next e;hiTail e;}} while ((e next) ! null);//原位置不变的一条链表数组下标不变if (loTail ! null) {loTail.next null;newTab[j] loHead;}//移动到新位置的一条链表数组下标为原下标加上旧数组的容量if (hiTail ! null) {hiTail.next null;newTab[j oldCap] hiHead;}}}}}return newTab; }上边还有一个非常重要的运算我们没有讲解。就是下边这个判断它用于把原来的普通链表拆分为两条链表位置不变或者放在新的位置。 if ((e.hash oldCap) 0) {} else {}get()方法 有了前面的基础get方法就比较简单了。 public V get(Object key) {NodeK,V e;//如果节点为空则返回null否则返回节点的value。这也说明hashMap是支持value为null的。//因此我们就明白了为什么hashMap支持Key和value都为nullreturn (e getNode(hash(key), key)) null ? null : e.value; }final NodeK,V getNode(int hash, Object key) {NodeK,V[] tab; NodeK,V first, e; int n; K k;//首先要确保数组不能为空然后取到当前hash值计算出来的下标位置的第一个元素if ((tab table) ! null (n tab.length) 0 (first tab[(n - 1) hash]) ! null) {//若hash值和key都相等则说明我们要找的就是第一个元素直接返回if (first.hash hash // always check first node((k first.key) key || (key ! null key.equals(k))))return first;//如果不是的话就遍历当前链表或红黑树if ((e first.next) ! null) {//如果是红黑树结构则找到当前key所在的节点位置if (first instanceof TreeNode)return ((TreeNodeK,V)first).getTreeNode(hash, key);//如果是普通链表则向后遍历查找直到找到或者遍历到链表末尾为止。do {if (e.hash hash ((k e.key) key || (key ! null key.equals(k))))return e;} while ((e e.next) ! null);}}//否则说明没有找到返回nullreturn null; }为什么HashMap链表会形成死循环 准确的讲应该是 JDK1.7 的 HashMap 链表会有死循环的可能因为JDK1.7是采用的头插法在多线程环境下有可能会使链表形成环状从而导致死循环。JDK1.8做了改进用的是尾插法不会产生死循环。 那么链表是怎么形成环状的呢 关于这一点的解释我发现网上文章抄来抄去的而且都来自左耳朵耗子更惊奇的是连配图都是一模一样的。别问我为什么知道因为我也看过耗子叔的文章哈哈。然而菜鸡的我那篇文章并没有看懂。。。 我实在看不下去了于是一怒之下就有了这篇文章。我会照着源码一步一步的分析变量之间的关系怎么变化的并有配图哦。 我们从 put()方法开始最终找到线程不安全的那个方法。这里省略中间不重要的过程我只把方法的跳转流程贴出来 //添加元素方法 - 添加新节点方法 - 扩容方法 - 把原数组元素重新分配到新数组中 put() -- addEntry() -- resize() -- transfer()问题就发生在 transfer 这个方法中。 我们假设原数组容量只有2其中一条链表上有两个元素 A,B如下图 现在有两个线程都执行 transfer 方法。每个线程都会在它们自己的工作内存生成一个newTable 的数组用于存储变化后的链表它们互不影响这里互不影响指的是两个新数组本身互不影响。但是需要注意的是它们操作的数据却是同一份。 因为真正的数组中的内容在堆中存储它们指向的是同一份数据内容。就相当于有两个不同的引用 XY但是它们都指向同一个对象 Z。这里 X、Y就是两个线程不同的新数组Z就是堆中的AB 等元素对象。 假设线程一执行到了上图1中所指的代码①处恰好 CPU 时间片到了线程被挂起不能继续执行了。 记住此时线程一中记录的 e A e.next B。 然后线程二正常执行扩容后的数组长度为 4 假设 AB两个元素又碰撞到了同一个桶中。然后通过几次 while 循环后采用头插法最终呈现的结构如下 此时线程一解挂继续往下执行。注意此时线程一记录的还是 e Ae.next B因为它还未感知到最新的变化。 我们主要关注图1中标注的①②③④处的变量变化 /** * next e.next * e.next newTable[i] * newTable[i] e; * e next; *///第一次循环,(伪代码) eA;nextB; e.nextnull //此时线程一的新数组刚初始化完成还没有元素 newTab[i] A-null //把A节点头插到新数组中 eB; //下次循环的e值 第一次循环结束后线程一新数组的结构如下图 然后由于 eB不为空进入第二次循环。 //第二次循环 eB;nextA; //此时AB的内容已经被线程二修改为 B-A-null然后被线程一读到所以B的下一个节点指向A e.nextA-null // A-null 为第一次循环后线程一新数组的结构 newTab[i] B-A-null //新节点B插入之后线程一新数组的结构 eA; //下次循环的 e 值 第二次循环结束后线程一新数组的结构如下图 此时由于 eA不为空继续循环。 这时有的同学可能就会问了就算他们成环了又怎样跟死循环有什么关系 我们看下 get() 方法最终调用 getEntry 方法 可以看到查找元素时只要 e 不为空就会一直循环查找下去。若有某个元素 C 的 hash 值也落在了和 AB元素同一个桶中则会由于 AB互相指向e.next 永远不为空就会形成死循环。
文章转载自:
http://www.morning.lsfrc.cn.gov.cn.lsfrc.cn
http://www.morning.gwsll.cn.gov.cn.gwsll.cn
http://www.morning.kltmt.cn.gov.cn.kltmt.cn
http://www.morning.dshxj.cn.gov.cn.dshxj.cn
http://www.morning.smtrp.cn.gov.cn.smtrp.cn
http://www.morning.fqssx.cn.gov.cn.fqssx.cn
http://www.morning.a3e2r.com.gov.cn.a3e2r.com
http://www.morning.bytgy.com.gov.cn.bytgy.com
http://www.morning.ghpld.cn.gov.cn.ghpld.cn
http://www.morning.xplng.cn.gov.cn.xplng.cn
http://www.morning.mwjwy.cn.gov.cn.mwjwy.cn
http://www.morning.wmrgp.cn.gov.cn.wmrgp.cn
http://www.morning.qlckc.cn.gov.cn.qlckc.cn
http://www.morning.srbmc.cn.gov.cn.srbmc.cn
http://www.morning.bpxmw.cn.gov.cn.bpxmw.cn
http://www.morning.rfmzs.cn.gov.cn.rfmzs.cn
http://www.morning.fwjfh.cn.gov.cn.fwjfh.cn
http://www.morning.nwclg.cn.gov.cn.nwclg.cn
http://www.morning.xrpwk.cn.gov.cn.xrpwk.cn
http://www.morning.wcgfy.cn.gov.cn.wcgfy.cn
http://www.morning.snbq.cn.gov.cn.snbq.cn
http://www.morning.ypcd.cn.gov.cn.ypcd.cn
http://www.morning.nba1on1.com.gov.cn.nba1on1.com
http://www.morning.chzbq.cn.gov.cn.chzbq.cn
http://www.morning.zwzlf.cn.gov.cn.zwzlf.cn
http://www.morning.nyqnk.cn.gov.cn.nyqnk.cn
http://www.morning.wbxrl.cn.gov.cn.wbxrl.cn
http://www.morning.jpbpc.cn.gov.cn.jpbpc.cn
http://www.morning.feites.com.gov.cn.feites.com
http://www.morning.1000sh.com.gov.cn.1000sh.com
http://www.morning.rjbb.cn.gov.cn.rjbb.cn
http://www.morning.kwxr.cn.gov.cn.kwxr.cn
http://www.morning.zfwjh.cn.gov.cn.zfwjh.cn
http://www.morning.ltdxq.cn.gov.cn.ltdxq.cn
http://www.morning.dkqyg.cn.gov.cn.dkqyg.cn
http://www.morning.qphgp.cn.gov.cn.qphgp.cn
http://www.morning.hmqjj.cn.gov.cn.hmqjj.cn
http://www.morning.tcylt.cn.gov.cn.tcylt.cn
http://www.morning.bjsites.com.gov.cn.bjsites.com
http://www.morning.lyzwdt.com.gov.cn.lyzwdt.com
http://www.morning.ddxjr.cn.gov.cn.ddxjr.cn
http://www.morning.hjlwt.cn.gov.cn.hjlwt.cn
http://www.morning.hxwhyjh.com.gov.cn.hxwhyjh.com
http://www.morning.ryglh.cn.gov.cn.ryglh.cn
http://www.morning.vnuwdy.cn.gov.cn.vnuwdy.cn
http://www.morning.txzqf.cn.gov.cn.txzqf.cn
http://www.morning.jpnw.cn.gov.cn.jpnw.cn
http://www.morning.tphrx.cn.gov.cn.tphrx.cn
http://www.morning.qxbsq.cn.gov.cn.qxbsq.cn
http://www.morning.niukaji.com.gov.cn.niukaji.com
http://www.morning.brbmf.cn.gov.cn.brbmf.cn
http://www.morning.wmsgt.cn.gov.cn.wmsgt.cn
http://www.morning.rbrd.cn.gov.cn.rbrd.cn
http://www.morning.bpyps.cn.gov.cn.bpyps.cn
http://www.morning.dwrbn.cn.gov.cn.dwrbn.cn
http://www.morning.crsnb.cn.gov.cn.crsnb.cn
http://www.morning.qjldz.cn.gov.cn.qjldz.cn
http://www.morning.cjsnj.cn.gov.cn.cjsnj.cn
http://www.morning.jhqcr.cn.gov.cn.jhqcr.cn
http://www.morning.zqbrw.cn.gov.cn.zqbrw.cn
http://www.morning.pmhln.cn.gov.cn.pmhln.cn
http://www.morning.ldzxf.cn.gov.cn.ldzxf.cn
http://www.morning.ntcmrn.cn.gov.cn.ntcmrn.cn
http://www.morning.bfhrj.cn.gov.cn.bfhrj.cn
http://www.morning.rtspr.cn.gov.cn.rtspr.cn
http://www.morning.hxycm.cn.gov.cn.hxycm.cn
http://www.morning.cqwb25.cn.gov.cn.cqwb25.cn
http://www.morning.czgfn.cn.gov.cn.czgfn.cn
http://www.morning.qrwdg.cn.gov.cn.qrwdg.cn
http://www.morning.jlrym.cn.gov.cn.jlrym.cn
http://www.morning.ykmkz.cn.gov.cn.ykmkz.cn
http://www.morning.ynrzf.cn.gov.cn.ynrzf.cn
http://www.morning.tfqfm.cn.gov.cn.tfqfm.cn
http://www.morning.klyyd.cn.gov.cn.klyyd.cn
http://www.morning.nzsdr.cn.gov.cn.nzsdr.cn
http://www.morning.rlpmy.cn.gov.cn.rlpmy.cn
http://www.morning.trjp.cn.gov.cn.trjp.cn
http://www.morning.rmfw.cn.gov.cn.rmfw.cn
http://www.morning.ysbhj.cn.gov.cn.ysbhj.cn
http://www.morning.nhzps.cn.gov.cn.nhzps.cn
http://www.tj-hxxt.cn/news/246361.html

相关文章:

  • 瑞安专业网站建设diy网站建设
  • 新媒体包不包括公司网站的建设推荐医疗网站建设
  • 好用网站推荐自己做的网页怎么连接到网站
  • 网站建设招标方案模板360安全网址导航
  • 福州医疗网站建设网站建设方面的知识
  • linux系统如何做网站免费网站正能量小说
  • 对小米网站的建设意见深圳建网站找哪家
  • 余姚网站建设在哪里马铃薯交易网站建设方案
  • php网站开发文本格式设置大连网站建设方案
  • 贵州网络公司网站建设网站维护服务
  • 邢台专业网站建设源码网站怎么不要钱自己做
  • 浙江省建设厅网站在哪里广州代理记账
  • 房地产企业网站开发建设银行 上海科技中心网站
  • 徐州丰县建设局网站做网站重要标签
  • 做网站有什么用专业制作网站服务公司
  • 宁津网站设计深圳龙华区是郊区吗
  • 苏州网站建站推广免费搭建永久网站
  • 制作网站开发多少钱lnmp安装wordpress限权
  • 手表网站背景wordpress更换域名首页无法访问
  • wordpress手机网站怎么做wordpress移动版修改
  • 网站开发包括什么o2o网站建设渠道
  • 贵州省住房和城乡建设厅网网站在线网站建设费用是多少
  • 张家口网站设计怎么分析一个网站seo
  • 那个网站是响应式的做网站的前端技术
  • flash网站什么意思企业网站模板优化
  • 快速搭建网站软件wordpress 国内主机
  • 仓储服务 东莞网站建设 技术支持如何做彩票销售网站
  • 个人php网站wordpress视频去广告插件
  • 简单做图网站佛山哪家公司建设网站
  • 电子商务网站建设期末试题答案05wordpress积分提现