1.选择唯一性索引

唯一性索引的值是唯一的,可以更快速的通过该索引来确定某条记录。例如,学生表中学号是具有唯一性的字段。为该字段建立唯一性索引可以很快的确定某个学生的信息。如果使用姓名的话,可能存在同名现象,从而降低查询速度。

2.为经常需要排序、分组和联合操作的字段建立索引

经常需要ORDER BY、GROUP BY、DISTINCT和UNION等操作的字段,排序操作会浪费很多时间。如果为其建立索引,可以有效地避免排序操作。

3.为常作为查询条件的字段建立索引

如果某个字段经常用来做查询条件,那么该字段的查询速度会影响整个表的查询速度。因此,为这样的字段建立索引,可以提高整个表的查询速度。

4.限制索引的数目

索引的数目不是越多越好。每个索引都需要占用磁盘空间,索引越多,需要的磁盘空间就越大。修改表时,对索引的重构和更新很麻烦。越多的索引,会使更新表变得很浪费时间。

5.尽量使用数据量少的索引

如果索引的值很长,那么查询的速度会受到影响。例如,对一个CHAR(100)类型的字段进行全文检索需要的时间肯定要比对CHAR(10)类型的字段需要的时间要多。

6.尽量使用前缀来索引

如果索引字段的值很长,最好使用值的前缀来索引。例如,TEXT和BLOG类型的字段,进行全文检索会很浪费时间。如果只检索字段的前面的若干个字符,这样可以提高检索速度。

7.删除不再使用或者很少使用的索引

表中的数据被大量更新,或者数据的使用方式被改变后,原有的一些索引可能不再需要。数据库管理员应当定期找出这些索引,将它们删除,从而减少索引对更新操作的影响。

8.最左前缀匹配原则,非常重要的原则。

mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a 1=”” and=”” b=”2” c=”“> 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。

9.=和in可以乱序。

比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式

10.尽量选择区分度高的列作为索引。

区分度的公式是count(distinct col)/count(*),表示字段不重复的比例,比例越大我们扫描的记录数越少,唯一键的区分度是1,而一些状态、性别字段可能在大数据面前区分度就 是0,那可能有人会问,这个比例有什么经验值吗?使用场景不同,这个值也很难确定,一般需要join的字段我们都要求是0.1以上,即平均1条扫描10条 记录

11.索引列不能参与计算,保持列“干净”。

比如from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,原因很简单,b+树中存的都是数据表中的字段值,但进行检索时,需要把所有元素都应用函数才能比较,显然成本 太大。所以语句应该写成create_time = unix_timestamp(’2014-05-29’);

12.尽量的扩展索引,不要新建索引。

比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可

注意:选择索引的最终目的是为了使查询的速度变快。上面给出的原则是最基本的准则,但不能拘泥于上面的准则。读者要在以后的学习和工作中进行不断的实践。根据应用的实际情况进行分析和判断,选择最合适的索引方式。

Java中一共有4种引用类型(其实还有一些其他的引用类型比如FinalReference):强引用、软引用、弱引用、虚引用。

其中强引用就是我们经常使用的Object a = new Object(); 这样的形式,在Java中并没有对应的Reference类。

本篇文章主要是分析软引用、弱引用、虚引用的实现,这三种引用类型都是继承于Reference这个类,主要逻辑也在Reference中。

问题

在分析前,先抛几个问题?

  1. 网上大多数文章对于软引用的介绍是:在内存不足的时候才会被回收,那内存不足是怎么定义的?什么才叫内存不足?
  2. 网上大多数文章对于虚引用的介绍是:形同虚设,虚引用并不会决定对象的生命周期。主要用来跟踪对象被垃圾回收器回收的活动。真的是这样吗?
  3. 虚引用在Jdk中有哪些场景下用到了呢?

Reference

我们先看下Reference.java中的几个字段

public abstract class Reference<T> { //引用的对象 private T referent;         //回收队列,由使用者在Reference的构造函数中指定 volatile ReferenceQueue<? super T> queue; //当该引用被加入到queue中的时候,该字段被设置为queue中的下一个元素,以形成链表结构 volatile Reference next; //在GC时,JVM底层会维护一个叫DiscoveredList的链表,存放的是Reference对象,discovered字段指向的就是链表中的下一个元素,由JVM设置 transient private Reference<T> discovered;   //进行线程同步的锁对象 static private class Lock { } private static Lock lock = new Lock(); //等待加入queue的Reference对象,在GC时由JVM设置,会有一个java层的线程(ReferenceHandler)源源不断的从pending中提取元素加入到queue private static Reference<Object> pending = null;
} 

一个Reference对象的生命周期如下:

主要分为Native层和Java层两个部分。

Native层在GC时将需要被回收的Reference对象加入到DiscoveredList中(代码在referenceProcessor.cpp中

process_discovered_references方法),然后将DiscoveredList的元素移动到PendingList中(代码在referenceProcessor.cpp中enqueue_discovered_ref_helper方法),PendingList的队首就是Reference类中的pending对象。

看看Java层的代码

private static class ReferenceHandler extends Thread { ... public void run() { while (true) { tryHandlePending(true); } } }
static boolean tryHandlePending(boolean waitForNotify) { Reference<Object> r; Cleaner c; try { synchronized (lock) { if (pending != null) { r = pending; //如果是Cleaner对象,则记录下来,下面做特殊处理 c = r instanceof Cleaner ? (Cleaner) r : null; //指向PendingList的下一个对象 pending = r.discovered; r.discovered = null; } else { //如果pending为null就先等待,当有对象加入到PendingList中时,jvm会执行notify if (waitForNotify) { lock.wait(); } // retry if waited return waitForNotify; } } }  ... // 如果时CLeaner对象,则调用clean方法进行资源回收 if (c != null) { c.clean(); return true; } //将Reference加入到ReferenceQueue,开发者可以通过从ReferenceQueue中poll元素感知到对象被回收的事件。 ReferenceQueue<? super Object> q = r.queue; if (q != ReferenceQueue.NULL) q.enqueue(r); return true; } 

流程比较简单:就是源源不断的从PendingList中提取出元素,然后将其加入到ReferenceQueue中去,开发者可以通过从ReferenceQueue中poll元素感知到对象被回收的事件。

另外需要注意的是,对于Cleaner类型(继承自虚引用)的对象会有额外的处理:在其指向的对象被回收时,会调用clean方法,该方法主要是用来做对应的资源回收,在堆外内存DirectByteBuffer中就是用Cleaner进行堆外内存的回收,这也是虚引用在java中的典型应用。

看完了Reference的实现,再看看几个实现类里,各自有什么不同。

SoftReference

public class SoftReference<T> extends Reference<T> { static private long clock; private long timestamp; public SoftReference(T referent) { super(referent); this.timestamp = clock; } public SoftReference(T referent, ReferenceQueue<? super T> q) { super(referent, q); this.timestamp = clock; } public T get() { T o = super.get(); if (o != null && this.timestamp != clock) this.timestamp = clock; return o; } } 

软引用的实现很简单,就多了两个字段:clock和timestamp。clock是个静态变量,每次GC时都会将该字段设置成当前时间。timestamp字段则会在每次调用get方法时将其赋值为clock(如果不相等且对象没被回收)。

那这两个字段的作用是什么呢?这和软引用在内存不够的时候才被回收,又有什么关系呢?

这些还得看JVM的源码才行,因为决定对象是否需要被回收都是在GC中实现的。

size_t
ReferenceProcessor::process_discovered_reflist( DiscoveredList               refs_lists[], ReferencePolicy*             policy, bool                         clear_referent, BoolObjectClosure*           is_alive, OopClosure*                  keep_alive, VoidClosure*                 complete_gc, AbstractRefProcTaskExecutor* task_executor)
{ ... //还记得上文提到过的DiscoveredList吗?refs_lists就是DiscoveredList。 //对于DiscoveredList的处理分为几个阶段,SoftReference的处理就在第一阶段 ... for (uint i = 0; i < _max_num_q; i++) { process_phase1(refs_lists[i], policy, is_alive, keep_alive, complete_gc); } ...
} //该阶段的主要目的就是当内存足够时,将对应的SoftReference从refs_list中移除。
void
ReferenceProcessor::process_phase1(DiscoveredList&    refs_list, ReferencePolicy*   policy, BoolObjectClosure* is_alive, OopClosure*        keep_alive, VoidClosure*       complete_gc) { DiscoveredListIterator iter(refs_list, keep_alive, is_alive); // Decide which softly reachable refs should be kept alive. while (iter.has_next()) { iter.load_ptrs(DEBUG_ONLY(!discovery_is_atomic() /* allow_null_referent */)); //判断引用的对象是否存活 bool referent_is_dead = (iter.referent() != NULL) && !iter.is_referent_alive(); //如果引用的对象已经不存活了,则会去调用对应的ReferencePolicy判断该对象是不时要被回收 if (referent_is_dead && !policy->should_clear_reference(iter.obj(), _soft_ref_timestamp_clock)) { if (TraceReferenceGC) { gclog_or_tty->print_cr("Dropping reference (" INTPTR_FORMAT ": %s"  ") by policy", (void *)iter.obj(), iter.obj()->klass()->internal_name()); } // Remove Reference object from list iter.remove(); // Make the Reference object active again iter.make_active(); // keep the referent around iter.make_referent_alive(); iter.move_to_next(); } else { iter.next(); } } ...
} 

refs_lists中存放了本次GC发现的某种引用类型(虚引用、软引用、弱引用等),而

process_discovered_reflist方法的作用就是将不需要被回收的对象从refs_lists移除掉,refs_lists最后剩下的元素全是需要被回收的元素,最后会将其第一个元素赋值给上文提到过的Reference.java#pending字段。

ReferencePolicy一共有4种实现:NeverClearPolicy,AlwaysClearPolicy,LRUCurrentHeapPolicy,LRUMaxHeapPolicy。

其中NeverClearPolicy永远返回false,代表永远不回收SoftReference,在JVM中该类没有被使用,AlwaysClearPolicy则永远返回true,在referenceProcessor.hpp#setup方法中中可以设置policy为AlwaysClearPolicy,至于什么时候会用到AlwaysClearPolicy,大家有兴趣可以自行研究。

LRUCurrentHeapPolicy和LRUMaxHeapPolicy的should_clear_reference方法则是完全相同:

bool LRUMaxHeapPolicy::should_clear_reference(oop p, jlong timestamp_clock) { jlong interval = timestamp_clock - java_lang_ref_SoftReference::timestamp(p); assert(interval >= 0, "Sanity check"); // The interval will be zero if the ref was accessed since the last scavenge/gc. if(interval <= _max_interval) { return false; } return true;
} 

timestamp_clock就是SoftReference的静态字段clock,

java_lang_ref_SoftReference::timestamp(p)对应是字段timestamp。如果上次GC后有调用SoftReference#get,interval值为0,否则为若干次GC之间的时间差。

_max_interval则代表了一个临界值,它的值在LRUCurrentHeapPolicy和LRUMaxHeapPolicy两种策略中有差异。

void LRUCurrentHeapPolicy::setup() { _max_interval = (Universe::get_heap_free_at_last_gc() / M) * SoftRefLRUPolicyMSPerMB; assert(_max_interval >= 0,"Sanity check");
} void LRUMaxHeapPolicy::setup() { size_t max_heap = MaxHeapSize; max_heap -= Universe::get_heap_used_at_last_gc(); max_heap /= M; _max_interval = max_heap * SoftRefLRUPolicyMSPerMB; assert(_max_interval >= 0,"Sanity check");
} 

看到这里你就知道SoftReference到底什么时候被被回收了,它和使用的策略(默认应该是LRUCurrentHeapPolicy),堆可用大小,该SoftReference上一次调用get方法的时间都有关系。

WeakReference

public class WeakReference<T> extends Reference<T> { public WeakReference(T referent) { super(referent); } public WeakReference(T referent, ReferenceQueue<? super T> q) { super(referent, q); } } 

可以看到,对于Soft references和Weak references clear_referent字段传入的都是true,这也符合我们的预期:对象不可达后,引用字段就会被置为null,然后对象就会被回收(对于软引用来说,如果内存足够的话,在Phase 1,相关的引用就会从refs_list中被移除,到Phase 3时refs_list为空集合)。

但对于Final references和 Phantom references,clear_referent字段传入的是false,也就意味着被这两种引用类型引用的对象,如果没有其他额外处理,只要Reference对象还存活,那引用的对象是不会被回收的。Final references和对象是否重写了finalize方法有关,不在本文分析范围之内,我们接下来看看Phantom references。

可以看到WeakReference在Java层只是继承了Reference,没有做任何的改动。那referent字段是什么时候被置为null的呢?要搞清楚这个问题我们再看下上文提到过的

process_discovered_reflist方法:size_t
ReferenceProcessor::process_discovered_reflist( DiscoveredList               refs_lists[], ReferencePolicy*             policy, bool                         clear_referent, BoolObjectClosure*           is_alive, OopClosure*                  keep_alive, VoidClosure*                 complete_gc, AbstractRefProcTaskExecutor* task_executor)
{ ... //Phase 1:将所有不存活但是还不能被回收的软引用从refs_lists中移除(只有refs_lists为软引用的时候,这里policy才不为null) if (policy != NULL) { if (mt_processing) { RefProcPhase1Task phase1(*this, refs_lists, policy, true /*marks_oops_alive*/); task_executor->execute(phase1); } else { for (uint i = 0; i < _max_num_q; i++) { process_phase1(refs_lists[i], policy, is_alive, keep_alive, complete_gc); } } } else { // policy == NULL assert(refs_lists != _discoveredSoftRefs, "Policy must be specified for soft references."); } // Phase 2: // 移除所有指向对象还存活的引用 if (mt_processing) { RefProcPhase2Task phase2(*this, refs_lists, !discovery_is_atomic() /*marks_oops_alive*/); task_executor->execute(phase2); } else { for (uint i = 0; i < _max_num_q; i++) { process_phase2(refs_lists[i], is_alive, keep_alive, complete_gc); } } // Phase 3: // 根据clear_referent的值决定是否将不存活对象回收 if (mt_processing) { RefProcPhase3Task phase3(*this, refs_lists, clear_referent, true /*marks_oops_alive*/); task_executor->execute(phase3); } else { for (uint i = 0; i < _max_num_q; i++) { process_phase3(refs_lists[i], clear_referent, is_alive, keep_alive, complete_gc); } } return total_list_count;
} void
ReferenceProcessor::process_phase3(DiscoveredList&    refs_list, bool               clear_referent, BoolObjectClosure* is_alive, OopClosure*        keep_alive, VoidClosure*       complete_gc) { ResourceMark rm; DiscoveredListIterator iter(refs_list, keep_alive, is_alive); while (iter.has_next()) { iter.update_discovered(); iter.load_ptrs(DEBUG_ONLY(false /* allow_null_referent */)); if (clear_referent) { // NULL out referent pointer //将Reference的referent字段置为null,之后会被GC回收 iter.clear_referent(); } else { // keep the referent around //标记引用的对象为存活,该对象在这次GC将不会被回收 iter.make_referent_alive(); } ... } ...
}

不管是弱引用还是其他引用类型,将字段referent置null的操作都发生在process_phase3中,而具体行为是由clear_referent的值决定的。而clear_referent的值则和引用类型相关。

ReferenceProcessorStats ReferenceProcessor::process_discovered_references( BoolObjectClosure*           is_alive, OopClosure*                  keep_alive, VoidClosure*                 complete_gc, AbstractRefProcTaskExecutor* task_executor, GCTimer*                     gc_timer) { NOT_PRODUCT(verify_ok_to_handle_reflists()); ... //process_discovered_reflist方法的第3个字段就是clear_referent // Soft references size_t soft_count = 0; { GCTraceTime tt("SoftReference", trace_time, false, gc_timer); soft_count = process_discovered_reflist(_discoveredSoftRefs, _current_soft_ref_policy, true, is_alive, keep_alive, complete_gc, task_executor); } update_soft_ref_master_clock(); // Weak references size_t weak_count = 0; { GCTraceTime tt("WeakReference", trace_time, false, gc_timer); weak_count = process_discovered_reflist(_discoveredWeakRefs, NULL, true, is_alive, keep_alive, complete_gc, task_executor); } // Final references size_t final_count = 0; { GCTraceTime tt("FinalReference", trace_time, false, gc_timer); final_count = process_discovered_reflist(_discoveredFinalRefs, NULL, false, is_alive, keep_alive, complete_gc, task_executor); } // Phantom references size_t phantom_count = 0; { GCTraceTime tt("PhantomReference", trace_time, false, gc_timer); phantom_count = process_discovered_reflist(_discoveredPhantomRefs, NULL, false, is_alive, keep_alive, complete_gc, task_executor); } ...
} 

可以看到,对于Soft references和Weak references clear_referent字段传入的都是true,这也符合我们的预期:对象不可达后,引用字段就会被置为null,然后对象就会被回收(对于软引用来说,如果内存足够的话,在Phase 1,相关的引用就会从refs_list中被移除,到Phase 3时refs_list为空集合)。

但对于Final references和 Phantom references,clear_referent字段传入的是false,也就意味着被这两种引用类型引用的对象,如果没有其他额外处理,只要Reference对象还存活,那引用的对象是不会被回收的。Final references和对象是否重写了finalize方法有关,不在本文分析范围之内,我们接下来看看Phantom references。

PhantomReference

public class PhantomReference<T> extends Reference<T> { public T get() { return null; } public PhantomReference(T referent, ReferenceQueue<? super T> q) { super(referent, q); } } 

可以看到虚引用的get方法永远返回null,我们看个demo。

public static void demo() throws InterruptedException { Object obj = new Object(); ReferenceQueue<Object> refQueue =new ReferenceQueue<>(); PhantomReference<Object> phanRef =new PhantomReference<>(obj, refQueue); Object objg = phanRef.get(); //这里拿到的是null System.out.println(objg); //让obj变成垃圾 obj=null; System.gc(); Thread.sleep(3000); //gc后会将phanRef加入到refQueue中 Reference<? extends Object> phanRefP = refQueue.remove(); //这里输出true System.out.println(phanRefP==phanRef); } 

从以上代码中可以看到,虚引用能够在指向对象不可达时得到一个'通知'(其实所有继承References的类都有这个功能),需要注意的是GC完成后,phanRef.referent依然指向之前创建Object,也就是说Object对象一直没被回收!

而造成这一现象的原因在上一小节末尾已经说了:对于Final references和 Phantom references,clear_referent字段传入的时false,也就意味着被这两种引用类型引用的对象,如果没有其他额外处理,在GC中是不会被回收的。

对于虚引用来说,从refQueue.remove();得到引用对象后,可以调用clear方法强行解除引用和对象之间的关系,使得对象下次可以GC时可以被回收掉。

End

针对文章开头提出的几个问题,看完分析,我们已经能给出回答:

1.我们经常在网上看到软引用的介绍是:在内存不足的时候才会回收,那内存不足是怎么定义的?为什么才叫内存不足?

软引用会在内存不足时被回收,内存不足的定义和该引用对象get的时间以及当前堆可用内存大小都有关系,计算公式在上文中也已经给出。

2.网上对于虚引用的介绍是:形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。主要用来跟踪对象被垃圾回收器回收的活动。真的是这样吗?

严格的说,虚引用是会影响对象生命周期的,如果不做任何处理,只要虚引用不被回收,那其引用的对象永远不会被回收。所以一般来说,从ReferenceQueue中获得PhantomReference对象后,如果PhantomReference对象不会被回收的话(比如被其他GC ROOT可达的对象引用),需要调用clear方法解除PhantomReference和其引用对象的引用关系。

3.虚引用在Jdk中有哪些场景下用到了呢?

DirectByteBuffer中是用虚引用的子类Cleaner.java来实现堆外内存回收的,后续会写篇文章来说说堆外内存的里里外外。

 最后
为大家整理了 Java 核心知识点+全套架构师学习资料和视频+一线大厂面试宝典+面试简历模板可以领取+阿里美团网易腾讯等面试题+Spring 源码合集+Java 架构实战电子书供大家学习!需要这份资料的,点击关注,私信我【333】即可获取,或者评论区留下脚印【我爱学习】即可领取

面试中经常被问到Java引用类型原理,带你深入剖析相关推荐

  1. (免费领取Java面试题)Java面试中经常被问到的问题

    (免费领取Java面试题)Java面试中经常被问到的问题 免费领取Java面试题 -------------https://blog.csdn.net/kaikeba/article/details/ ...

  2. java 实体类包含list 怎么取值_舅舅是面试官,偷偷告诉你们面试官最爱问的Java面试题...

    2015 年,因为工作岗位的变动,舅舅开始负责给集团招聘一些技术人员,出于对公司的负责,也为了更好的胜任技术经理的职位,在面试的这件事上,舅舅做了大量的"功课",首先研究了几乎所有 ...

  3. 这些面试中经常被问到的线程池问题,你都能回答的上来吗?

    点击上方"Java知音",选择"置顶公众号" 技术文章第一时间送达! 作者:Real_man juejin.im/post/5e435ac3f265da5753 ...

  4. Python培训常识:Python面试中常被问到的几种设计模式要知道

    学习Python技术大家都是为了日后能够找到适合自己的工作岗位,那么除了要学习好Python技术外,对于面试环节的问题也要有所了解,本期小编为大家介绍的Python培训教程就算关于Python面试中常 ...

  5. 面试中经常会问的智力题,来看看你会做几道

    转载自   面试中经常会问的智力题,来看看你会做几道 下面是大部分题目来自滴滴出行2017秋招题.开始头脑风暴吧~~~ 问题 question one 有50家人家,每家一条狗.有一天警察通知,50条 ...

  6. e - 数据结构实验之查找五:平方之哈希表_面试中常被问到的Hash表,你了解吗

    #新人扶持计划# Hash表在面试中经常被问到,今天我们来了解下. Hash表也称散列表,也有直接译作哈希表,Hash表是一种特殊的数据结构,它同数组.链表以及二叉排序树等相比较有很明显的区别,它能够 ...

  7. 面试中,被问到“哑口无言”的瞬间怎么办?

    在面试过程中,可能会遇到各种意想不到的问题,有些问题会让你感到非常自信,而有些问题却会让你感到犯愁,甚至哑口无言.这种情况发生时,如何应对是很关键的.面试中,被问到"哑口无言"的瞬 ...

  8. 面试中常被问到(11)虚函数/纯虚函数

    虚函数 如何定义一个虚函数?在基类成员函数前加入virtual关键字,但并不代表此函数不被实现,只是说明允许基类指针调用派生类重写的此函数 一个类只要声明有虚函数或者从基类继承了虚函数,在编译过程中就 ...

  9. 淘宝十年资深程序员在面试中最爱问的秒杀的面试题

    淘宝十年资深程序员在面试中最爱问的秒杀的面试题 文章目录 淘宝十年资深程序员在面试中最爱问的秒杀的面试题 1.什么是秒杀?秒杀是什么场景? 2.秒杀场景下有哪些挑战?如何应对这些挑战? 3.如何防止商 ...

最新文章

  1. pytorch安装-Windows(pip install失败)
  2. Intellij IDEA 14中使用MyBatis-generator 自动生成MyBatis代码
  3. window.onbeforeunload() 事件调用ajax的解决方法
  4. 做python开发要用多大的内存_Python 在分配内存需要考虑的问题
  5. gcc 编译流程分析
  6. 基于Hexo+Node.js+github+coding搭建个人博客——基础篇
  7. 使用MUI框架实现JQ购物车增减
  8. 经典排序算法(十八)--Proxmap Sort
  9. lzg_ad:EWF RAM Reg模式原理介绍
  10. Android源码参考
  11. 城市记忆:(一)灵洲----河奇;(二)富平三迁
  12. html的lang属性
  13. 小米11刷root记录
  14. 每日LeetCode一道题————有效的数独
  15. 【深度学习】textCNN论文与原理
  16. 计算机启动后无法进入系统怎么办,开机进不了windows系统怎么办
  17. 商城电商day 06 三、商品详情业务需求分析
  18. 微信小程序实现视频上下滑动
  19. selenium打不开Ie浏览器的解决办法
  20. 自制wpf年会抽奖小软件

热门文章

  1. OSPF FA地址分析
  2. 1bit quantization
  3. 使用stata临床决策曲线进行外部模型验证
  4. xp进入win7计算机要密码怎么设置密码,XP系统设置直接跳过密码的多种方法
  5. windows播放函数PlaySound
  6. axure8.1 授权码
  7. 计算机在制造业中的应用领域是什么,计算机在制造业中的应用ppt
  8. win8.1 终于删除可恶的微软拼音
  9. 项目实训--Unity多人游戏开发(十六、草丛隐身与道具隐身)
  10. 品优购项目--登录页面