LinkedList它实现的基础是双向链表,因此在插入删除方面具有性能优势,它也可以用来实现stack和queue。顺便说一句,Java容器框架中有一个遗留的类Stack,它是基于Vector实现的,被大师们评价为“幼稚的设计”,我们不要用。
LinkedList主要有三个属性:

int size
Node<E> first
Node<E> last

也就是通过一个链表把size个Node从头串到尾。而Node就是一个类的节点包装类,有item,有prev和next。图示如下:

LinkedList也是List,因此同样具有List的那些操作,这一点跟ArrayList一样,因此下面我们只介绍它不一样的部分。
LinkedList同时也实现了Deque,因此它具有Deque的方法,如下面的12个:

First Element (Head)    Last Element (Tail)
Insert    addFirst(e)
offerFirst(e)
addLast(e)
offerLast(e)Remove    removeFirst()
pollFirst()
removeLast()
pollLast()Examine    getFirst()
peekFirst()
getLast()
peekLast()

如果我们把它看成FIFO先进先出的,就成了一个Queue了,Deque扩展了Queue,有几个方法是完全等同的:

Queue Method    Equivalent Deque Method
add(e)
addLast(e)offer(e)
offerLast(e)remove()
removeFirst()poll()
pollFirst()element()
getFirst()peek()
peekFirst()

我们如果把它看成FILO先进后出的,那就成了一个stack,事实上有几个方法也是一样的功能:

Stack Method    Equivalent Deque Method
push(e)
addFirst(e)pop()
removeFirst()peek()
peekFirst()

下面我们还是用一个例子把上面的12个方法简单演示一下,使用的方法有

addFirst(e)    offerFirst(e)   addLast(e)  offerLast(e)
removeFirst()    pollFirst() removeLast()    pollLast()
getFirst()    peekFirst() getLast()   peekLast()
代码如下(LinkedListTest1.java):
public class LinkedListTest1 {public static void main(String[] args) {LinkedList<String> list1 = new LinkedList<>();list1.addFirst("北京");list1.offerFirst("上海");list1.addLast("广州");list1.offerLast("深圳");list1.offer("杭州");list1.add("苏州");list1.push("厦门");System.out.println(list1);System.out.println(list1.get(2));System.out.println(list1.getLast());System.out.println(list1.getFirst());System.out.println(list1.peek());System.out.println(list1.peekFirst());System.out.println(list1.peekLast());System.out.println(list1);list1.remove();list1.removeLast();list1.removeFirst();list1.remove("深圳");list1.poll();list1.pollLast();list1.pop();System.out.println(list1);}
}

大家自己运行一下,很简单。

我们提到过,List是有次序的,次序就是放进去的次序。如果要另外排序呢?可以的。我们看一个简单的例子,代码如下(ListSort.java):

public class ListSort {public static void main(String[] args) {List<Student> list = new ArrayList<>();list.add(new Student(2,"b","very good"));list.add(new Student(1,"a","good"));list.add(new Student(3,"c","basic"));System.out.println(list);list.sort((s1,s2)->s1.name.compareTo(s2.name));System.out.println(list);}
}

运行结果:

[b-very good, a-good, c-basic]
[a-good, b-very good, c-basic]

从运行结果可以看出,list重新按照我们给的规则(名字排序)排序了,实现一个Comparator就可以了。
我们这边自定义的是值的比较规则,而排序算法是没有地方选择的,不同的JDK版本内部的排序算法是不一样的,JDK6和之前的版本,都是用的merge sort(归并排序算法),JDK7及之后用的Tim排序算法。Tim排序算法是结合了归并排序和插入排序的新算法,对各种数据排列都比较好,而merge排序算法要对基本排好的数据再排序会很好,而有的数据效果比较差,性能接近o(n2)。

public class ListSort {public static void main(String[] args) {long start;long end;int bound = 10;List<Integer> list1 = new ArrayList<>();for (int i=0; i<bound; i++){list1.add(i);}start=System.currentTimeMillis();System.out.println(list1);list1.sort((i1,i2)->i1-i2);end=System.currentTimeMillis();System.out.println(list1);System.out.println(end-start);Random r = new Random();List<Integer> list2 = new ArrayList<>();for (int i=0; i<bound; i++){list2.add(r.nextInt(bound));}start=System.currentTimeMillis();System.out.println(list2);list2.sort((i1,i2)->i1-i2);end=System.currentTimeMillis();System.out.println(list2);System.out.println(end-start);List<Integer> list3 = new ArrayList<>();for (int i=bound-1; i>=0; i--){list3.add(i);}start=System.currentTimeMillis();System.out.println(list3);list3.sort((i1,i2)->i1-i2);end=System.currentTimeMillis();System.out.println(list3);System.out.println(end-start);      }
}

结果为:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
442
[7, 8, 1, 7, 3, 0, 8, 0, 6, 8]
[0, 0, 1, 3, 6, 7, 7, 8, 8, 8]
2
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
2

不用管具体的数值,只是感觉一下原始数据不同排列情况下,排序算法性能差异很大。

我们嘴巴上老是说LinkedList和ArrayList之间性能的差异,现在我用一个例子演示一下,这个例子不是我写的,我直接从《Thinking in Java》里抄过来的,所以版权属于Bruce Eckel。代码如下(ListPerformance.java):

public class ListPerformance {private static final int REPS = 100;private abstract static class Tester {String name;int size;Tester(String name, int size) {this.name = name;this.size = size;}abstract void test(List a);}private static Tester[] tests = {new Tester("get", 300) {void test(List a) {for (int i = 0; i < REPS; i++) {for (int j = 0; j < a.size(); j++) {a.get(j);}}}}, new Tester("iteration", 300) {void test(List a) {for (int i = 0; i < REPS; i++) {Iterator it = a.iterator();while (it.hasNext()) it.next();}}}, new Tester("insert", 1000) {void test(List a) {int half = a.size() / 2;String s = "test";ListIterator it = a.listIterator(half);for (int i = 0; i < size * 10; i++) {it.add(s);}}}, new Tester("remove", 5000) {void test(List a) {ListIterator it = a.listIterator(3);while (it.hasNext()) {it.next();it.remove();}}},};public static void test(List a) {System.out.println("Testing " + a.getClass().getName());for (int i = 0; i < tests.length; i++) {fill(a, tests[i].size);System.out.print(tests[i].name);long t1 = System.currentTimeMillis();tests[i].test(a);long t2 = System.currentTimeMillis();System.out.print(":" + (t2 - t1)+" ms ");}}public static Collection fill(Collection c, int size) {for (int i = 0; i < size; i++) {c.add(Integer.toString(i));}return c;}public static void main(String[] args) {test(new ArrayList());System.out.println();test(new LinkedList());}
}

运行之后的结果是:

Testing java.util.ArrayList
get:4 ms iteration:6 ms insert:6 ms remove:28 ms
Testing java.util.LinkedList
get:13 ms iteration:5 ms insert:2 ms remove:4 ms

结果印证了我们的说法,ArrayList确实get比较块,LinkedList确实删除增加比较快,而iterator两者差不多的。

Bruce Eckel还提供了一个更加专业的测试,结果如下:

--- Array as List ---size     get     set10     130     183100     130     1641000     129     165
10000     129     165
--------------------- ArrayList ---------------------size     add     get     set         iteradd  insert  remove10     121     139     191     435    3952     446100      72     141     191     247    3934     2961000      98     141     194     839    2202     923
10000     122     144     190    6880   14042    7333
--------------------- LinkedList ---------------------size     add     get     set         iteradd  insert  remove10     182     164     198     658     366     262100     106     202     230     457     108     2011000     133    1289    1353     430     136     239
10000     172   13648   13187     435     255     239
----------------------- Vector -----------------------size     add     get     set         iteradd  insert  remove10     129     145     187     290    3635     253100      72     144     190     263    3691     2921000      99     145     193     846    2162     927
10000     108     145     186    6871   14730    7135
-------------------- Queue tests --------------------size            addFirst     addLast     rmFirst      rmLast10         199         163         251         253100          98          92         180         1791000          99          93         216         212
10000         111         109         262         384

按照Bruce Eckel的建议,首选ArrayList,当确认要对数据进行频繁的增加删除的时候,就用LinkedList。

好,我们讲过了List,我们接着讲讲Map。
Java容器框架中,Map是独立的一类。我们讲讲用得最多的HashMap。接口是Map,还有个抽象类AbstractMap,具体的实现类是HashMap。
HashMap 是Java的键值对数据类型容器。它根据键的哈希值(hashCode)来存储数据,访问速度高,性能是常数,没有顺序。HashMap 允许键值为空和记录为空,非线程安全。
先看一个简单的例子,代码如下(HashMapTest.java):

public class HashMapTest {public static void main(String[] args) {Map<String, String> map = new HashMap<>();map.put("BJC", "北京首都机场");map.put("PDX", "上海浦东机场");map.put("GZB", "广州白云机场");map.put("SZX", "深圳宝安机场");String usage = map.get("SZX");System.out.println("Map: " + map);System.out.println("Map Size:  " + map.size());System.out.println("Map is empty:  " + map.isEmpty());System.out.println("Map contains PDX key:   " + map.containsKey("PDX"));System.out.println("Usage:  " + usage);System.out.println("removed:  " + map.remove("SZX"));}
}

程序很简单,把一个个key-value放入HashMap中,然后执行get(),size(),isEmpty,containsKey(),remove()等操作。
结果如下:

Map: {SZX=深圳宝安机场, PDX=上海浦东机场, BJC=北京首都机场, GZB=广州白云机场}
Map Size:  4
Map is empty:  false
Map contains PDX key:   true
Usage:  深圳宝安机场
removed:  深圳宝安机场

我们翻一下JDK,看看HashMap的介绍。

public class HashMap
extends AbstractMap
implements Map, Cloneable, Serializable
Hash table 实现了Map 接口,允许null values and the null key,不是同步的。这个类不保证数据的次序,特别地,也不保证数据次序的恒定,也就是说,第一次查找的时候是这个次序,下一次可能就变了。
HashMap有两个参数影响性能: initial capacity and load factor. The capacity是bucket桶的数量,默认值是16,load factor是hash表多满后自动扩容,0.75是默认值。每次扩容是增加一倍容量,扩容可以很耗时间。对capacity和load factor,要有一个平衡,合理兼顾空间占用和时间消耗。
HashMap不是同步的,如果要同步需要在外面自己实现,或者用Map m = Collections.synchronizedMap(new HashMap(…));转换成同步的。
跟Collection一样,多线程的情况下,如果一个 iterator遍历中间HashMap有结构性变化,就会fail-fast,抛出 ConcurrentModificationException。

看看HashMap的构造函数:

HashMap()
Constructs an empty HashMap with the default initial capacity (16) and the default load factor (0.75).
HashMap(int initialCapacity)
Constructs an empty HashMap with the specified initial capacity and the default load factor (0.75).
HashMap(int initialCapacity, float loadFactor)
Constructs an empty HashMap with the specified initial capacity and load factor.
HashMap(Map<? extends K,? extends V> m)
Constructs a new HashMap with the same mappings as the specified Map.

对HashMap里面的方法不一一举例了,我们看一个小例子,代码如下(HashMapTest2.java):

public class HashMapTest2 {public static void main(String[] args) {HashMap<String, String> map = new HashMap<>();map.put("BJC", "北京首都机场");map.put("PDX", "上海虹桥机场");map.put("GZB", "广州白云机场");map.put("SZX", "深圳宝安机场");Set<String> keys  = map.keySet();keys.forEach(System.out::println);for (String key : map.keySet()) {System.out.println("value=" +map.get(key));}Set<Map.Entry<String, String>> entries = map.entrySet();entries.forEach((Map.Entry<String, String> entry) -> {String key = entry.getKey();String value = entry.getValue();System.out.println("key=" + key + ",  value=" + value);});map.replace("PDX", "上海浦东机场");Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();while (iterator.hasNext()) {Map.Entry<String, String> entry = iterator.next();System.out.println("key=" + entry.getKey() + ",  value=" + entry.getValue());map.merge(entry.getKey(), "有限公司", (oldVal, newVal) -> oldVal + newVal);map.compute(entry.getKey(), (key, oldVal) -> oldVal + "有限公司");}List<String> valuesList = new ArrayList<String>(map.values());for(String str:valuesList){System.out.println(str);}}
}

程序简单。大家熟悉一下几种遍历方式,还有merge(), compute(),和map.values()。

有了这些基础,接下来我们要讲更多的东西,帮助大家更好地理解HashMap。我们先看看数据结构中介绍的一点理论知识。
简单来讲,HashMap底下用的数据结构是数组+链表(红黑树)。Key值通过一个hash函数映射到数组的下标,重复的下标通过链表(红黑树)解决冲突。术语中把此处的数组叫做bucket桶。
有一个图,很形象地说明了HashMap的结构。

table是一个数组,数组每个位置(就是每一个桶)保存一个元素,或者是跟着一个链表或者红黑树(开头都是链表,数据量>8之后,就自动转成红黑树)。查找数据先定位在数组哪个位置,再顺藤摸瓜找到在链表或者红黑树上的哪一个具体节点。
我们知道,查找数据来说,其实数组是最快的,因为可以根据下标直接定位。所以哈希的核心思路是用一个函数将查找的key值转换成一个整数值,然后以此为下标,把key值存放在数组中。这样下次再找的时候,还用这个函数,直接定位了。所以定位数组下标,性能是o(1),如果定位的这个数组后面跟了一个链表,要接着找具体的节点,性能是o(l),其中l是链表长度。自然,链表长度越短越好,意味着需要这个hash函数冲突越少越好。所以,HashMap的性能关键在于要找到一个合适的函数。
要写出一个像样子的哈希函数,在《Effective Java》这本书中,Joshua Bloch给了一个指导:
1 给int变量result赋予一个非零值常量,如17
2 为对象内每个有意义的域f(即每个可以做equals()操作的域)计算出一个int散列码c:

域类型                                计算
boolean                                c=(f?0:1)
byte、char、short或int                c=(int)f
long                                    c=(int)(f^(f>>>32))
float                                    c=Float.floatToIntBits(f);
double                                long l = Double.doubleToLongBits(f);c=(int)(l^(l>>>32))
Object,其equals()调用这个域的equals()    c=f.hashCode()
数组                                对每个元素应用上述规则
3. 合并计算散列码:result = 37 * result + c;
4. 返回result。
5. 检查hashCode()最后生成的结果,确保相同的对象有相同的散列码。

《Thinking in Java》里面给了一个简单的例子,我拷贝到这里,版权属于Bruce Eckel。代码如下(CountedString.java):

public class CountedString {private static List<String> created = new ArrayList<String>();private String s;private int id = 0;public CountedString(String str) {s = str;created.add(s);// id is the total number of instances// of this string in use by CountedString:for(String s2 : created)if(s2.equals(s))id++;}public String toString() {return "String: " + s + " id: " + id + " hashCode(): " + hashCode();}public int hashCode() {// The very simple approach:// return s.hashCode() * id;// Using Joshua Bloch's recipe:int result = 17;result = 37 * result + s.hashCode();result = 37 * result + id;return result;}public boolean equals(Object o) {return o instanceof CountedString &&s.equals(((CountedString)o).s) &&id == ((CountedString)o).id;}public static void main(String[] args) {Map<CountedString,Integer> map = new HashMap<CountedString,Integer>();CountedString[] cs = new CountedString[5];for(int i = 0; i < cs.length; i++) {cs[i] = new CountedString("hi");map.put(cs[i], i); // Autobox int -> Integer}System.out.println(map);for(CountedString cstring : cs) {System.out.println("Looking up " + cstring);System.out.println(map.get(cstring));}}
}

运行结果如下:

{String: hi id: 4 hashCode(): 146450=3, String: hi id: 5 hashCode(): 146451=4, String: hi id: 2 hashCode(): 146448=1, String: hi id: 3 hashCode(): 146449=2, String: hi id: 1 hashCode(): 146447=0}
Looking up String: hi id: 1 hashCode(): 146447
0
Looking up String: hi id: 2 hashCode(): 146448
1
Looking up String: hi id: 3 hashCode(): 146449
2
Looking up String: hi id: 4 hashCode(): 146450
3
Looking up String: hi id: 5 hashCode(): 146451
4

大家可以看出对给定的key值生成的不一样的hashcode。

讲完了HashMap,我再简单介绍一下Set。大家或许觉得奇怪,Set不是Collection里面的一员吗?没什么不放在更前面谈?我这么讲是因为Set底层是基于Map实现的,所以讲授放在哪一边都是可以的。说白了,Set是Map的一层马甲。Set不能有重复数据。
Set是实现Collection接口的,除了Collection的常规操作,还有一些与集合相关的操作,并,交,补等等。
看一个简单的例子,代码如下(HashSetTest.java):

public class HashSetTest {public static void main(String[] args) {Set<String> s1 = new HashSet<>();s1.add("北京首都机场");s1.add("上海虹桥机场");s1.add("广州白云机场");s1.add("深圳宝安机场");s1.add("上海虹桥机场");Set<String> s2 = new HashSet<>();s2.add("上海虹桥机场");s2.add("长沙黄花机场");s2.add("杭州萧山机场");for(String s : s1) {System.out.print(s+" ");}System.out.println("");Iterator iterator = s2.iterator();while (iterator.hasNext()) {System.out.print(iterator.next()+" ");          }System.out.println("");doUnion(s1, s2);doIntersection(s1, s2);doDifference(s1, s2);isSubset(s1, s2);}public static void doUnion(Set<String> s1, Set<String> s2) {Set<String> s1Unions2 = new HashSet<>(s1);s1Unions2.addAll(s2);System.out.println("s1 union  s2: " + s1Unions2);}public static void doIntersection(Set<String> s1, Set<String> s2) {Set<String> s1Intersections2 = new HashSet<>(s1);s1Intersections2.retainAll(s2);System.out.println("s1 intersection  s2: " + s1Intersections2);}public static void doDifference(Set<String> s1, Set<String> s2) {Set<String> s1Differences2 = new HashSet<>(s1);s1Differences2.removeAll(s2);Set<String> s2Differences1 = new HashSet<>(s2);s2Differences1.removeAll(s1);System.out.println("s1 difference s2: " + s1Differences2);System.out.println("s2 difference s1: " + s2Differences1);}public static void isSubset(Set<String> s1, Set<String> s2) {System.out.println("s2 is  subset s1: " + s1.containsAll(s2));System.out.println("s1 is  subset s2: " + s2.containsAll(s1));}
}

简单,不解释了。大家只要注意s1.add("上海虹桥机场");执行了两遍,但是最后Set里面只有一个。因为判断这是同一个对象。
这儿要多提一下,世界上没有两片完全一样的树叶,两个字符串,怎么会认为是同一个呢?这是因为判断是否为同一个采用的方法是调用equals()方法。所以对自定义的类,需要重新写equals()方法,否则就是直接用的Object自带的equals()方法,那是比较的引用地址,肯定就不同了,而我们需要比较的是对象里面的内容。
看一个例子。以前讲过,再试一遍。
先写一个自定义类Student:

public class Student {int id = 0;String name = "";String mark = "";public Student() {}public Student(int id, String name, String mark) {this.id = id;this.name = name;this.mark = mark;}public void setId(int id) {this.id = id;}public void setName(String name) {this.name = name;}public void setMark(String mark) {this.mark = mark;}   public String toString() {return name + "-" + mark;}
}

再用一个测试程序看看,代码如下(HashSetTest2.java):

public class HashSetTest2 {public static void main(String[] args) {Set<Student> s1 = new HashSet<>();s1.add(new Student(1,"a","aaa"));s1.add(new Student(2,"b","bbb"));s1.add(new Student(3,"c","ccc"));s1.add(new Student(1,"a","aaa"));System.out.print(s1);}
}

运行结果如下:

[c-ccc, a-aaa, b-bbb, a-aaa]
注意了,aaa添加了两遍,在Set中也有两份。这不是重复了吗?造成这种情况的原因就是重复不重复,是看的equals()。因此,我们必须改写equals(),Student程序增加一个方法如下:public boolean equals (Object obj){if(this==obj){return true;}if(!(obj instanceof Student)){return false;} Student s=(Student) obj;if(this.id==s.id&&this.name.equals(s.name)&&this.mark.equals(s.mark)) {return true;}return false;}

我们重写equals()覆盖Object默认的方法,比较对象内部的内容。
再次运行,结果是:
[c-ccc, a-aaa, b-bbb, a-aaa]
没有变化!这是怎么回事呢?我们回顾一下HashMap的查找方法,第一步是比较hashcode,相同的话,就在同一个bucket桶里找相同的元素,这个时候才会调用在equals()。我们的程序,在hashcode这一层就被挡住了,不会调用equals(),所以,我们对Student类,还需要重写hashCode(),Student程序修改一下,增加hashCode():

public int hashCode(){return this.name.hashCode();
}

再运行,就出现了我们想要的结果。同时也印证了我们的说法,Set其实是基于Map的。JDK说明中,对HashSet的第一句话就是:This class implements the Set interface, backed by a hash table (actually a HashMap instance)。

好,到此为止,我们就把几个基本的类介绍过了,ArrayList,LinkedList,HashMap,HashSet。普通应用主要用它们几个,一般也认为容器类是一门实用的语言最重要的类。大家要好好掌握这些基本的使用方法。

Java语言十五讲(第十四讲 容器框架二)相关推荐

  1. java程序设计p75_java语言程序设计实用教程第四讲控制结构.ppt

    java语言程序设计实用教程第四讲控制结构.ppt 还剩 29页未读, 继续阅读 下载文档到电脑,马上远离加班熬夜! 亲,很抱歉,此页已超出免费预览范围啦! 如果喜欢就下载吧,价低环保! 内容要点: ...

  2. 基于Java语言构建区块链(四)—— 交易(UTXO)

    基于Java语言构建区块链(四)-- 交易(UTXO) 2018年03月11日 00:48:01 wangwei_hz 阅读数:909 标签: 区块链比特币 更多 个人分类: 区块链 文章的主要思想和 ...

  3. Linux_arm_启动_c语言部分详解,[原创]Linux arm 启动 c语言部分详解第四讲

    Linux arm启动c语言部分详解第四讲(from setup_per_cpu_areas();) Written by leeming 上面的setup_arch花了我们大量的篇幅,现在我们要继续 ...

  4. Java语言十五讲(第十二讲 Multi-Thread多线程12.2)

    实例变量如balance在线程间是共享的.有的时候,我们真的需要线程级别的变量,不希望共享,也是有办法的.Java里面有ThreadLocal变量. 比如,我们的线程从inventory里面拿东西,上 ...

  5. 视觉SLAM十四讲---第一、二讲(讲解slam框架, c++编译实践)

    同时定位与地图构建:首先是估计自己的运动,然后描述环境这两件事. 困难之处: 数据来源只有图像等 视觉slam14讲有编程内容 书中有相关知识的小例题 一.视觉slam框架分为四个模块 定位和建图的相 ...

  6. 基于华为鲲鹏云的c语言程序设计,华为DevRun第四讲,华为云鲲鹏云服务移植快速入门与实践...

    6月23日,华为DevRun开发者沙龙系列活动在线上举行,"华为云和TA的朋友们公开课"第四次直播课程顺利结束,本次公开课的主题是<华为云鲲鹏云服务移植快速入门及实践> ...

  7. JAVA语言程序设计课后习题----第四单元解析(仅供参考)

    1 本题水题,主要理解题目的意思即可,访问方法和修改方法可以通过快捷方式alt+insert选中你需要的成员变量即可 1 public class Person { 2 public String n ...

  8. java群发图文消息_使用Java语言开发微信公众平台(四)——图文消息的发送与响应...

    在上一篇文章中,我们实现了被关注回复与关键词回复功能.在用户关注的时候自动推送功能菜单,并根据用户输入的关键词,回复特定信息.但是,我们只能回复文本消息给用户,如何才回复一条图文消息呢?本周,我们一起 ...

  9. Java语言基础(三)——异常、容器、泛型

    目录 一.异常 1.1.简单异常 1.1.1.检查型异常 1.1.2.运行时异常 1.1.3.错误Error 1.2.异常处理机制 1.3.自定义异常 1.4.实际应用中的经验总结 二.容器 2.1. ...

  10. JAVA面试灵魂108问(四)---集合框架2---hashmap

      大家好,我是陈哈哈,北漂五年.认识我的朋友们知道,我是非科班出身,半路出家,大学也很差!这种背景来北漂,你都不知道你会经历什么

最新文章

  1. cocos2d-x注意事项(十)Lua发展飞机战争-4-创建主角
  2. 机器学习算法与技术简介
  3. win32 api setwindowlong 第2个参数_第 6 篇:分页接口
  4. 控制台双缓冲防闪烁--功能封装
  5. 8006.ros2发布与订阅
  6. slect,poll,epoll区别
  7. Redis 中两种持久化机制详解
  8. 【机器学习】流程模板
  9. knex 单表查询_sql 单表查询练习
  10. ps怎么导入lut预设?Photoshop导入lut调色预设教程
  11. logisim数据选择器_【Logisim实验】构建立即数-随机存储器-寄存器的传送
  12. [分享]来自CSDN的精华网址
  13. linux 查看dhcp dns,RHEL6 DNS+DHCP+DDNS
  14. r语言做绘制精美pcoa图_三分钟绘制一张优美的PCoA图 | 云平台
  15. B2B、B2C、C2C、O2O分别是什么意思
  16. Centos的GPT分区 硬盘超过2T
  17. ModuleNotFoundError: No module named ‘django‘
  18. mysql数据库技术_MySQL数据库操作技术大全
  19. ZUCC_Linux系统管理_实验六 磁盘管理
  20. Wi-Fi理论基础概述

热门文章

  1. Amesim学习——弹球仿真
  2. 第22节项目6-定期存款利息计算器
  3. 网站SEO实践之 - 网站关键词库扩展的几种方法
  4. windows(微软知识库)可以根据事件ID查看 各种事件
  5. 深股通,沪股通,港股通
  6. WORD转PDF格式快捷方法(转贴)
  7. 【bat】bat批处理使用WinRAR.exe 压缩文件,解压文件
  8. 连线封面:2亿多支付宝用户选择的背后,一个数据与评分带来的「等级世界」
  9. fastboot烧机
  10. 腾讯视频、爱奇艺、豆果美食等为线下贷款中介导流、个人信息遭倒卖