zoukankan      html  css  js  c++  java
  • 2017年4月26号课堂笔记

    2017年4月26号 晴 空气质量:优

    内容:U1知识总结

    以下为补充或者自己薄弱的环节:(很多是网上搜的,回头慢慢消化)

    一、Tips

    ctrl+shift+r : open resource, 打开资源.
    它可以打开当前eclipse的工作区中所有(打开的)工程中所有类型的文件,但只限手动编写的文件,不含工程中引用到的
    jar包中的类、接口;
    ctrl+shift+t : open type, 打开类型.
    它可以打开当前eclipse的工作区中所有(打开的)工程中所有java文件,包括jar包中的类和接口.

    二、二分查找法

    二分查找又称折半查找,优点是比较次数少,查找速度快,平均性能好;其缺点是要求待查表为有序表,且插入删除困难。因此,折半查找方法适用于不经常变动而查找频繁的有序列表。

    首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。

    重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。

    二分查找的基本思想是将n个元素分成大致相等的两部分,取a[n/2]与x做比较,如果x=a[n/2],则找到x,算法中止;
    如果x<a[n/2],则只要在数组a的左半部分继续搜索x,如果x>a[n/2],则只要在数组a的右半部搜索x.
    时间复杂度无非就是while循环的次数!
    总共有n个元素,
    渐渐跟下去就是n,n/2,n/4,....n/2^k(接下来操作元素的剩余个数),其中k就是循环的次数
    由于你n/2^k取整后>=1
    即令n/2^k=1
    可得k=log2n,(是以2为底,n的对数)
    所以时间复杂度可以表示O(h)=O(log2n)
    下面提供一段二分查找实现的伪代码:
    BinarySearch(max,min,des)
    mid-<(max+min)/2
    while(min<=max)
    mid=(min+max)/2
    if mid=des then
    return mid
    elseif mid >des then
    max=mid-1
    else
    min=mid+1
    return max
     

    三、元数据

    元数据被定义为:描述数据的数据,对数据及信息资源的描述性信息。

    元数据(Metadata)是描述其它数据的数据(data about other data),或者说是用于提供某种资源的有关信息的结构数据(structured data)。
    元数据是描述信息资源或数据等对象的数据,其使用目的在于:识别资源;评价资源;追踪资源在使用过程中的变化;实现简单高效地管理大量网络化数据;实现信息资源的有效发现、查找、一体化组织和对使用资源的有效管理。
     
    元数据的基本特点主要有:
    a)元数据一经建立,便可共享。元数据的结构和完整性依赖于信息资源的价值和使用环境;元数据的开发与利用环境往往是一个变化的分布式环境;任何一种格式都不可能完全满足不同团体的不同需要;
    b)元数据首先是一种编码体系。元数据是用来描述数字化信息资源,特别是网络信息资源的编码体系,这导致了元数据和传统数据编码体系的根本区别;元数据的最为重要的特征和功能是为数字化信息资源建立一种机器可理解框架。
    元数据体系构建了电子政务的逻辑框架和基本模型,从而决定了电子政务的功能特征、运行模式和系统运行的总体性能。电子政务的运作都基于元数据来实现。
    其主要作用有:描述功能、整合功能、控制功能和代理功能。
    由于元数据也是数据,因此可以用类似数据的方法在数据库中进行存储和获取。如果提供数据元的组织同时提供描述数据元的元数据,将会使数据元的使用变得准确而高效。用户在使用数据时可以首先查看其元数据以便能够获取自己所需的信息。
     

    四、this的用法

    this关键字主要有三个应用
    (1)this调用本类中的属性,也就是类中的成员变量
    (2)this调用本类中的其他方法
    (3)this调用本类中的其他构造方法,调用时要放在构造方法的首行


    Public Class Student {
    String name; //定义一个成员变量name
    private void SetName(String name) { //定义一个参数(局部变量)name
    this.name=name; //将局部变量的值传递给成员变量
    }
    }

    应用一:引用成员变量

    如上面这段代码中,有一个成员变量name,同时在方法中有一个形式参数,名字也是name,然后在方法中将形式参数name的值传递给成员变量name,虽然我们可以看明白这个代码的含义,但是作为Java编译器它是怎么判断的呢?

    到底是将形式参数name的值传递给成员变量name,还是反过来将成员变量name的值传递给形式参数name呢?也就是说,两个变量名字如果相同的话,那么Java如何判断使用哪个变量?此时this这个关键字就起到作用了。

    this这个关键字其代表的就是对象中的成员变量或者方法。也就是说,如果在某个变量前面加上一个this关键字,其指的就是这个对象的成员变量或者方法,而不是指成员方法的形式参数或者局部变量。

    为此在上面这个代码中,this.name代表的就是对象中的成员变量,又叫做对象的属性,而后面的name则是方法的形式参数,代码this.name=name就是将形式参数的值传递给成员变量。这就是上面这个代码的具体含义。

    一般情况下,在Java语言中引用成员变量或者成员方法都是以对象名.成员变量或者对象名.成员方法的形式。不过有些程序员即使在没有相同变量的时候,也喜欢使用this.成员变量的形式来引用变量,这主要是从便于代码的阅读考虑的。

    一看到这个this关键字就知道现在引用的变量是成员变量或者成员方法,而不是局部变量。这无形中就提高了代码的阅读性。不过话说回来,这是this关键字在Java语言中的最简单的应用。从这个应用中,我们可以看出this关键字其代表的就是对象的名字。

    其实如果是局部变量的话,也是相同的道理。如在上面的代码中,name不是形式参数,而是一个局部变量。此时Java也会遇到相同的疑惑,即变量名name代表的到底是局部变量还是形式参数?name=name到底代表的是什么含义?根据局部变量的作用域,在方法内部,如果局部变量与成员变量同名的话,那么是以局部变量为准。可是在name=name这个赋值语句中,将局部变量的值赋值给自己,显然并不是很合适。

    根据代码的含义,本来的意思应该是将局部变量赋值给成员变量。为了更清晰的表达这个含义,为此最好采用如下的书写格式this.name=name。这里的this关键字含义就是对象名student,为此this.name就表示student.name。

    应用二:调用类的构造方法

    public class Student { //定义一个类,类的名字为student。
    public Student() { //定义一个方法,名字与类相同故为构造方法
    this(“Hello!”);
    }
    public Student(String name) { //定义一个带形式参数的构造方法
    }
    }


    this关键字除了可以调用成员变量之外,还可以调用构造方法。在一个Java类中,其方法可以分为成员方法和构造方法两种。

    构造方法是一个与类同名的方法,在Java类中必须存在一个构造方法。如果在代码中没有显示的体现构造方法的话,那么编译器在编译的时候会自动添加一个没有形式参数的构造方法。这个构造方法跟普通的成员方法还是有很多不同的地方。如构造方法一律是没有返回值的,而且也不用void关键字来说明这个构造方法没有返回值。而普通的方法可以有返回值、也可以没有返回值,程序员可以根据自己的需要来定义。不过如果普通的方法没有返回值的话,那么一定要在方法定义的时候采用void关键字来进行说明。

    其次构造方法的名字有严格的要求,即必须与类的名字相同。也就是说,Java编译器发现有个方法与类的名字相同才把其当作构造方法来对待。而对于普通方法的话,则要求不能够与类的名字相同,而且多个成员方法不能够采用相同的名字。在一个类中可以存在多个构造方法,这些构造方法都采用相同的名字,只是形式参数不同。Java语言就凭形式参数不同来判断调用哪个构造方法。

    在上面这段代码中,定义了两个构造方法,一个带参数,另一个没有带参数。构造方法都不会有返回值,不过由于构造方法的特殊性,为此不必要在构造方法定义时带上void关键字来说明这个问题。在第一个没有带参数的构造方法中,使用了this(“Hello!”)这句代码,这句代码表示什么含义呢?

    在构造方法中使this关键字表示调用类中的构造方法。如果一个类中有多个构造方法,因为其名字都相同,跟类名一致,那么这个this到底是调用哪个构造方法呢?其实,这跟采用其他方法引用构造方法一样,都是通过形式参数来调用构造方法的。如上例中,this关键字后面加上了一个参数,那么就表示其引用的是带参数的构造方法。

    如果现在有三个构造方法,分别为不带参数、带一个参数、带两个参数。那么Java编译器会根据所传递的参数数量的不同,来判断该调用哪个构造方法。从上面示例中可以看出,this关键字不仅可以用来引用成员变量,而且还可以用来引用构造方法。

    不过如果要使用这种方式来调用构造方法的话,有一个语法上的限制。一般来说,利用this关键字来调用构造方法,只有在无参数构造方法中第一句使用this调用有参数的构造方法。否则的话,翻译的时候,就会有错误信息。这跟引用成员变量不同。如果引用成员变量的话,this关键字是没有位置上的限制的。如果不熟悉这个限制的话,那么还是老老实实的采用传统的构造方法调用方式为好。虽然比较麻烦,但是至少不会出错。


    应用三:返回对象的值
    this关键字除了可以引用变量或者成员方法之外,还有一个重大的作用就是返回类的引用。如在代码中,可以使用return this,来返回某个类的引用。此时这个this关键字就代表类的名称。如代码在上面student类中,那么代码代表的含义就是return student。可见,这个this关键字除了可以引用变量或者成员方法之外,还可以作为类的返回值,这才是this关键字最引人注意的地方。

    五、接口和抽象类区别(面试题)

    abstract class和interface是Java语言中对于抽象类定义进行支持的两种机制,正是由于这两种机制的存在,才赋予了Java强大的面向对象能力。 abstract class和interface之间在对于抽象类定义的支持方面具有很大的相似性,甚至可以相互替换,因此很多开发者在进行抽象类定义时对于 abstract class和interface的选择显得比较随意。

    其实,两者之间还是有很大的区别的,对于它们的选择甚至反映出对于问题领域本质的理解、对于设计意图的理解是否正确、合理。本文将对它们之间的区别进行一番剖析,试图给开发者提供一个在二者之间进行选择的依据。

    (一)理解抽象类

    abstract class和interface在Java语言中都是用来进行抽象类(本文中的抽象类并非从abstract class翻译而来,它表示的是一个抽象体,而abstract class为Java语言中用于定义抽象类的一种方法,请读者注意区分)定义的,那么什么是抽象类,使用抽象类能为我们带来什么好处呢?

    在面向对象的概念中,我们知道所有的对象都是通过类来描绘的,但是反过来却不是 这样。并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。抽象类往往用来表征我们在对问题领 域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。

    比如:如果我们进行一个图形编辑软件的开发,就会发现问题领域存在着圆、三角形 这样一些具体概念,它们是不同的,但是它们又都属于形状这样一个概念,形状这个概念在问题领域是不存在的,它就是一个抽象概念。正是因为抽象的概念在问题 领域没有对应的具体概念,所以用以表征抽象概念的抽象类是不能够实例化的。

    在面向对象领域,抽象类主要用来进行类型隐藏。我们可以构造出一个固定的一组行 为的抽象描述,但是这组行为却能够有任意个可能的具体实现方式。这个抽象描述就是抽象类,而这一组任意个可能的具体实现则表现为所有可能的派生类。模块可 以操作一个抽象体。由于模块依赖于一个固定的抽象体,因此它可以是不允许修改的;同时,通过从这个抽象体派生,也可扩展此模块的行为功能。熟悉OCP的读 者一定知道,为了能够实现面向对象设计的一个最核心的原则OCP(Open-Closed Principle),抽象类是其中的关键所在。

    (二)从语法定义层面看abstract class和interface

    在语法层面,Java语言对于abstract class和interface给出了不同的定义方式,下面以定义一个名为Demo的抽象类为例来说明这种不同。使用abstract class的方式定义Demo抽象类的方式如下:

    java 代码
    1. abstract class Demo {    
    2.   
    3. abstract void method1();    
    4.   
    5. abstract void method2();    
    6.   
    7. …    
    8.   
    9. }    

    使用interface的方式定义Demo抽象类的方式如下:

    java 代码
    1. interface Demo {    
    2.   
    3. void method1();    
    4.   
    5. void method2();    
    6.   
    7. …    
    8.   
    9. }    

    在abstract class方式中,Demo可以有自己的数据成员,也可以有非abstarct的成员方法,而在interface方式的实现中,Demo只能够有静态的 不能被修改的数据成员(也就是必须是static final的,不过在interface中一般不定义数据成员),所有的成员方法都是abstract的。从某种意义上说,interface是一种特殊 形式的abstract class。

    从编程的角度来看,abstract class和interface都可以用来实现"design by contract"的思想。但是在具体的使用上面还是有一些区别的。

    首先,abstract class在Java语言中表示的是一种继承关系,一个类只能使用一次继承关系。但是,一个类却可以实现多个interface。也许,这是Java语言的设计者在考虑Java对于多重继承的支持方面的一种折中考虑吧。

    其次,在abstract class的定义中,我们可以赋予方法的默认行为。但是在interface的定义中,方法却不能拥有默认行为,为了绕过这个限制,必须使用委托,但是这会 增加一些复杂性,有时会造成很大的麻烦。

    在抽象类中不能定义默认行为还存在另一个比较严重的问题,那就是可能会造成维护上的 麻烦。因为如果后来想修改类的界面(一般通过abstract class或者interface来表示)以适应新的情况(比如,添加新的方法或者给已用的方法中添加新的参数)时,就会非常的麻烦,可能要花费很多的时 间(对于派生类很多的情况,尤为如此)。但是如果界面是通过abstract class来实现的,那么可能就只需要修改定义在abstract class中的默认行为就可以了。

    同样,如果不能在抽象类中定义默认行为,就会导致同样的方法实现出现在该抽象类 的每一个派生类中,违反了"one rule,one place"原则,造成代码重复,同样不利于以后的维护。因此,在abstract class和interface间进行选择时要非常的小心。

    (三)从设计理念层面看abstract class和interface

    上面主要从语法定义和编程的角度论述了abstract class和interface的区别,这些层面的区别是比较低层次的、非本质的。本文将从另一个层面:abstract class和interface所反映出的设计理念,来分析一下二者的区别。作者认为,从这个层面进行分析才能理解二者概念的本质所在。

    前面已经提到过,abstarct class在Java语言中体现了一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在"is a"关系,即父类和派生类在概念本质上应该是相同的。对于interface 来说则不然,并不要求interface的实现者和interface定义在概念本质上是一致的,仅仅是实现了interface定义的契约而已。为了使 论述便于理解,下面将通过一个简单的实例进行说明。

    考虑这样一个例子,假设在我们的问题领域中有一个关于Door的抽象概念,该Door具有执行两个动作open和close,此时我们可以通过abstract class或者interface来定义一个表示该抽象概念的类型,定义方式分别如下所示:

    使用abstract class方式定义Door:

    java 代码
    1. abstract class Door {    
    2.   
    3. abstract void open();    
    4.   
    5. abstract void close();    
    6.   
    7. }    

    使用interface方式定义Door:

    java 代码
    1. interface Door {    
    2.   
    3. void open();    
    4.   
    5. void close();    
    6.   
    7. }    

    其他具体的Door类型可以extends使用abstract class方式定义的Door或者implements使用interface方式定义的Door。看起来好像使用abstract class和interface没有大的区别。

    如果现在要求Door还要具有报警的功能。我们该如何设计针对该例子的类结构呢(在 本例中,主要是为了展示abstract class和interface反映在设计理念上的区别,其他方面无关的问题都做了简化或者忽略)下面将罗列出可能的解决方案,并从设计理念层面对这些不 同的方案进行分析。

    解决方案一:

    简单的在Door的定义中增加一个alarm方法,如下:

    java 代码

    或者

    java 代码
    1. interface Door {    
    2.   
    3. void open();    
    4.   
    5. void close();    
    6.   
    7. void alarm();    
    8.   
    9. }    

    那么具有报警功能的AlarmDoor的定义方式如下:

    java 代码
    1. class AlarmDoor extends Door {    
    2.   
    3. void open() { … }    
    4.   
    5. void close() { … }    
    6.   
    7. void alarm() { … }    
    8.   
    9. }    

    或者

    java 代码
    1. class AlarmDoor implements Door {    
    2.   
    3. void open() { … }    
    4.   
    5. void close() { … }    
    6.   
    7. void alarm() { … }    
    8.   
    9. }    

    这种方法违反了面向对象设计中的一个核心原则ISP(Interface Segregation Priciple),在Door的定义中把Door概念本身固有的行为方法和另外一个概念"报警器"的行为方法混在了一起。这样引起的一个问题是那些仅仅 依赖于Door这个概念的模块会因为"报警器"这个概念的改变(比如:修改alarm方法的参数)而改变,反之依然。

    解决方案二:

    既然open、close和alarm属于两个不同的概念,根据ISP原则应该把它 们分别定义在代表这两个概念的抽象类中。定义方式有:这两个概念都使用abstract class方式定义;两个概念都使用interface方式定义;一个概念使用abstract class方式定义,另一个概念使用interface方式定义。

    显然,由于Java语言不支持多重继承,所以两个概念都使用abstract class方式定义是不可行的。后面两种方式都是可行的,但是对于它们的选择却反映出对于问题领域中的概念本质的理解、对于设计意图的反映是否正确、合理。我们一一来分析、说明。

    如果两个概念都使用interface方式来定义,那么就反映出两个问题:

    1、我们可能没有理解清楚问题领域,AlarmDoor在概念本质上到底是Door还是报警器?

    2、如果我们对于问题领域的理解没有问题,比如:我们通过对于问题领域的分析发现 AlarmDoor在概念本质上和Door是一致的,那么我们在实现时就没有能够正确的揭示我们的设计意图,因为在这两个概念的定义上(均使用 interface方式定义)反映不出上述含义。

    如果我们对于问题领域的理解是:AlarmDoor在概念本质上是Door,同 时它有具有报警的功能。我们该如何来设计、实现来明确的反映出我们的意思呢?前面已经说过,abstract class在Java语言中表示一种继承关系,而继承关系在本质上是"is a"关系。所以对于Door这个概念,我们应该使用abstarct class方式来定义。另外,AlarmDoor又具有报警功能,说明它又能够完成报警概念中定义的行为,所以报警概念可以通过interface方式定 义。如下所示:

    java 代码
    1. abstract class Door {    
    2.   
    3. abstract void open();    
    4.   
    5. abstract void close();    
    6.   
    7. }    
    java 代码
    1. interface Alarm {    
    2.   
    3. void alarm();    
    4.   
    5. }    
    java 代码
    1. class AlarmDoor extends Door implements Alarm {    
    2.   
    3. void open() { … }    
    4.   
    5. void close() { … }    
    6.   
    7. void alarm() { … }    
    8.   
    9. }    

    这种实现方式基本上能够明确的反映出我们对于问题领域的理解,正确的揭示我们的设计 意图。其实abstract class表示的是"is a"关系,interface表示的是"like a"关系,大家在选择时可以作为一个依据,当然这是建立在对问题领域的理解上的,比如:如果我们认为AlarmDoor在概念本质上是报警器,同时又具有 Door的功能,那么上述的定义方式就要反过来了。

    abstract class和interface是Java语言中的两种定义抽象类的方式,它们之间有很大的相似性。但是对于它们的选择却又往往反映出对于问题领域中的概 念本质的理解、对于设计意图的反映是否正确、合理,因为它们表现了概念间的不同的关系(虽然都能够实现需求的功能)。这其实也是语言的一种的惯用法。

     (四)总结几句话来说:

    1、抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象。

    2、抽象类要被子类继承,接口要被类实现。

    3、接口只能做方法申明,抽象类中可以做方法申明,也可以做方法实现

    4、接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。

    5、抽象类里的抽象方法必须全部被子类所实现,如果子类不能全部实现父类抽象方法,那么该子类只能是抽象类。同样,一个实现接口的时候,如不能全部实现接口方法,那么该类也只能为抽象类。

    6、抽象方法只能申明,不能实现。abstract void abc();不能写成abstract void abc(){}。

    7、抽象类里可以没有抽象方法

    8、如果一个类里有抽象方法,那么这个类只能是抽象类

    9、抽象方法

    六、自定义异常类

    转载自:http://blog.csdn.net/csdn_gia/article/details/53032248

     sun提供了很多的异常类给我们用于描述程序中各种的不正常情况,但是sun 给我
    提供异常类还不足以描述我们现实生活中所有不正常情况,那么这时候我们就需要
    自定义异常类。

    需求: 模拟feiQ上线的时候,如果没有插上网线,那么就抛出一个没有插上网线的异常,
    如果已经插上了网上,那么就正常显示好友列表。

    自定义异常类的步骤:  自定义一个类继承Exception即可。

    [java] view plain copy
     
    1. //自定义了一个没有网线的异常类了。  
    2. class NoIpException extends Exception{  
    3.   
    4.   
    5.     public NoIpException(String message){  
    6.         super(message);  //调用了Exception一个参数的构造函数。  
    7.     }  
    8.   
    9. }  
    10.   
    11.   
    12.   
    13. class Demo2   
    14. {  
    15.     public static void main(String[] args)   
    16.     {  
    17.         String ip = "192.168.10.100";  
    18.         ip = null;  
    19.         try{  
    20.             feiQ(ip);  // 如果调用了一个声明抛出异常类型的方法,那么调用者必须要处理。  
    21.           
    22.           
    23.         }catch(NoIpException e){  
    24.             e.printStackTrace();  
    25.             System.out.println("马上插上网线!");  
    26.         }  
    27.           
    28.   
    29.     }  
    30.   
    31.   
    32.     public static void feiQ(String ip) throws NoIpException{  
    33.         if(ip==null){  
    34.             throw new  NoIpException("没有插网线啊,小白!");  
    35.         }  
    36.         System.out.println("正常显示好友列表..");  
    37.     }  
    38.   
    39.   
    40. }  


    需求:模拟你去吃木桶饭,如果带钱少于了10块,那么就抛出一个没有带够钱的异常对象,
    如果带够了,那么就可以吃上香喷喷的地沟油木桶饭.

    [java] view plain copy
     
      1. //定义没钱的异常  
      2. class NoMoneyException extends Exception {  
      3.   
      4.     public NoMoneyException(String message){  
      5.         super(message);  
      6.     }  
      7.   
      8. }  
      9.   
      10.   
      11.   
      12. class Demo3   
      13. {  
      14.     public static void main(String[] args)   
      15.     {  
      16.         //System.out.println("Hello World!");  
      17.         try{  
      18.             eat(9);  
      19.   
      20.         }catch(NoMoneyException e){  
      21.             e.printStackTrace();  
      22.             System.out.println("跟我洗碗一个月!!");  
      23.         }  
      24.     }  
      25.   
      26.   
      27.     public static void eat(int money) throws NoMoneyException{  
      28.         if(money<10){  
      29.             throw new NoMoneyException("吃霸王餐");  
      30.         }  
      31.         System.out.println("吃上了香喷喷的地沟油木桶饭!!");  
      32.     }  
      33. }  

    七、Entry

    转载自:http://www.cnblogs.com/guanjie20/p/3769772.html

    Map是java中的接口,Map.Entry是Map的一个内部接口。

    Map提供了一些常用方法,如keySet()、entrySet()等方法。

    keySet()方法返回值是Map中key值的集合;entrySet()的返回值也是返回一个Set集合,此集合的类型为Map.Entry。

    Map.Entry是Map声明的一个内部接口,此接口为泛型,定义为Entry<K,V>。它表示Map中的一个实体(一个key-value对)。接口中有getKey(),getValue方法。

             

            由以上可以得出,遍历Map的常用方法:

           1.  Map map = new HashMap();

               Irerator iterator = map.entrySet().iterator();

               while(iterator.hasNext()) {

                       Map.Entry entry = iterator.next();

                       Object key = entry.getKey();

                       //

               }

           2.Map map = new HashMap(); 

               Set  keySet= map.keySet();

               Irerator iterator = keySet.iterator;

               while(iterator.hasNext()) {

                       Object key = iterator.next();

                       Object value = map.get(key);

                       //

               }

           另外,还有一种遍历方法是,单纯的遍历value值,Map有一个values方法,返回的是value的Collection集合。通过遍历collection也可以遍历value,如

          Map map = new HashMap();

          Collection c = map.values();

          Iterator iterator = c.iterator();

          while(iterator.hasNext()) {

                 Object value = iterator.next(); 

         }

    八、GC工作机制详解

    转载自:http://blog.csdn.net/tonytfjing/article/details/44278233

    题外话:最近在应聘阿里2015暑期实习,感触颇多。机会总是留给有准备的人的,所以平常一定要注意知识的巩固和积累。知识的深度也要有一定的理解,不比别人知道的多,公司干嘛选你?关于JVM和GC,我相信学Java的绝大部分人都听过,很多公司的面试官都爱问,一开始我也很头痛,问这么底层干什么,所以我每次面试也只是看看答案敷衍了事。最近面完阿里感觉真不能这样,知识不仅要知其然,还要知其所以然。其实弄懂了JVM和GC,对我们理解很多java知识都有帮助。网上有很多关于GC和JVM的文章,这篇博文主要是根据我最近看《深入理解Java虚拟机》的一些体会总结出来的,希望对新手有些帮助,也欢迎大牛拍砖。文章主要分为以下四个部分

    JVM结构、内存分配、垃圾回收算法、垃圾收集器。下面我们一一来看。

    一、JVM结构

    根据《java虚拟机规范》规定,JVM的基本结构一般如下图所示:

    从左图可知,JVM主要包括四个部分:

    1.类加载器(ClassLoader):在JVM启动时或者在类运行时将需要的class加载到JVM中。(右图表示了从java源文件到JVM的整个过程,可配合理解。 关于类的加载机制,可以参考http://blog.csdn.net/tonytfjing/article/details/47212291

    2.执行引擎:负责执行class文件中包含的字节码指令(执行引擎的工作机制,这里也不细说了,这里主要介绍JVM结构);

    3.内存区(也叫运行时数据区):是在JVM运行的时候操作所分配的内存区。运行时内存区主要可以划分为5个区域,如图:

    • 方法区(Method Area):用于存储类结构信息的地方,包括常量池、静态变量、构造函数等。虽然JVM规范把方法区描述为堆的一个逻辑部分, 但它却有个别名non-heap(非堆),所以大家不要搞混淆了。方法区还包含一个运行时常量池。
    • java堆(Heap):存储java实例或者对象的地方。这块是GC的主要区域(后面解释)。从存储的内容我们可以很容易知道,方法区和堆是被所有java线程共享的。
    • java栈(Stack):java栈总是和线程关联在一起,每当创建一个线程时,JVM就会为这个线程创建一个对应的java栈。在这个java栈中又会包含多个栈帧,每运行一个方法就创建一个栈帧,用于存储局部变量表、操作栈、方法返回值等。每一个方法从调用直至执行完成的过程,就对应一个栈帧在java栈中入栈到出栈的过程。所以java栈是现成私有的。
    • 程序计数器(PC Register):用于保存当前线程执行的内存地址。由于JVM程序是多线程执行的(线程轮流切换),所以为了保证线程切换回来后,还能恢复到原先状态,就需要一个独立的计数器,记录之前中断的地方,可见程序计数器也是线程私有的。
    • 本地方法栈(Native Method Stack):和java栈的作用差不多,只不过是为JVM使用到的native方法服务的。

    4.本地方法接口:主要是调用C或C++实现的本地方法及返回结果。

    二、内存分配

    我觉得了解垃圾回收之前,得先了解JVM是怎么分配内存的,然后识别哪些内存是垃圾需要回收,最后才是用什么方式回收。

    Java的内存分配原理与C/C++不同,C/C++每次申请内存时都要malloc进行系统调用,而系统调用发生在内核空间,每次都要中断进行切换,这需要一定的开销,而Java虚拟机是先一次性分配一块较大的空间,然后每次new时都在该空间上进行分配和释放,减少了系统调用的次数,节省了一定的开销,这有点类似于内存池的概念;二是有了这块空间过后,如何进行分配和回收就跟GC机制有关了。

    java一般内存申请有两种:静态内存和动态内存。很容易理解,编译时就能够确定的内存就是静态内存,即内存是固定的,系统一次性分配,比如int类型变量;动态内存分配就是在程序执行时才知道要分配的存储空间大小,比如java对象的内存空间。根据上面我们知道,java栈、程序计数器、本地方法栈都是线程私有的,线程生就生,线程灭就灭,栈中的栈帧随着方法的结束也会撤销,内存自然就跟着回收了。所以这几个区域的内存分配与回收是确定的,我们不需要管的。但是java堆和方法区则不一样,我们只有在程序运行期间才知道会创建哪些对象,所以这部分内存的分配和回收都是动态的。一般我们所说的垃圾回收也是针对的这一部分。

    总之Stack的内存管理是顺序分配的,而且定长,不存在内存回收问题;而Heap 则是为java对象的实例随机分配内存,不定长度,所以存在内存分配和回收的问题;

    三、垃圾检测、回收算法

    垃圾收集器一般必须完成两件事:检测出垃圾;回收垃圾。怎么检测出垃圾?一般有以下几种方法:

    引用计数法:给一个对象添加引用计数器,每当有个地方引用它,计数器就加1;引用失效就减1。

    好了,问题来了,如果我有两个对象A和B,互相引用,除此之外,没有其他任何对象引用它们,实际上这两个对象已经无法访问,即是我们说的垃圾对象。但是互相引用,计数不为0,导致无法回收,所以还有另一种方法:

    可达性分析算法:以根集对象为起始点进行搜索,如果有对象不可达的话,即是垃圾对象。这里的根集一般包括java栈中引用的对象、方法区常良池中引用的对象

    本地方法中引用的对象等。

    总之,JVM在做垃圾回收的时候,会检查堆中的所有对象是否会被这些根集对象引用,不能够被引用的对象就会被垃圾收集器回收。一般回收算法也有如下几种:

    1.标记-清除(Mark-sweep)

    算法和名字一样,分为两个阶段:标记和清除。标记所有需要回收的对象,然后统一回收。这是最基础的算法,后续的收集算法都是基于这个算法扩展的。

    不足:效率低;标记清除之后会产生大量碎片。效果图如下:

    2.复制(Copying)

    此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。此算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现“碎片”问题。当然,此算法的缺点也是很明显的,就是需要两倍内存空间。效果图如下:

    3.标记-整理(Mark-Compact)

    此算法结合了“标记-清除”和“复制”两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。效果图如下:

    (1,2,3 图文摘自 http://pengjiaheng.iteye.com/blog/520228,感谢原作者。)

    4.分代收集算法

    这是当前商业虚拟机常用的垃圾收集算法。分代的垃圾回收策略,是基于这样一个事实:不同的对象的生命周期是不一样的。因此,不同生命周期的对象可以采取不同的收集方式,以便提高回收效率。

    为什么要运用分代垃圾回收策略?在java程序运行的过程中,会产生大量的对象,因每个对象所能承担的职责不同所具有的功能不同所以也有着不一样的生命周期,有的对象生命周期较长,比如Http请求中的Session对象,线程,Socket连接等;有的对象生命周期较短,比如String对象,由于其不变类的特性,有的在使用一次后即可回收。试想,在不进行对象存活时间区分的情况下,每次垃圾回收都是对整个堆空间进行回收,那么消耗的时间相对会很长,而且对于存活时间较长的对象进行的扫描工作等都是徒劳。因此就需要引入分治的思想,所谓分治的思想就是因地制宜,将对象进行代的划分,把不同生命周期的对象放在不同的代上使用不同的垃圾回收方式。

    如何划分?将对象按其生命周期的不同划分成:年轻代(Young Generation)、年老代(Old Generation)、持久代(Permanent Generation)。其中持久代主要存放的是类信息,所以与java对象的回收关系不大,与回收息息相关的是年轻代和年老代。这里有个比喻很形象

    “假设你是一个普通的 Java 对象,你出生在 Eden 区,在 Eden 区有许多和你差不多的小兄弟、小姐妹,可以把 Eden 区当成幼儿园,在这个幼儿园里大家玩了很长时间。Eden 区不能无休止地放你们在里面,所以当年纪稍大,你就要被送到学校去上学,这里假设从小学到高中都称为 Survivor 区。开始的时候你在 Survivor 区里面划分出来的的“From”区,读到高年级了,就进了 Survivor 区的“To”区,中间由于学习成绩不稳定,还经常来回折腾。直到你 18 岁的时候,高中毕业了,该去社会上闯闯了。于是你就去了年老代,年老代里面人也很多。在年老代里,你生活了 20 年 (每次 GC 加一岁),最后寿终正寝,被 GC 回收。有一点没有提,你在年老代遇到了一个同学,他的名字叫爱德华 (慕光之城里的帅哥吸血鬼),他以及他的家族永远不会死,那么他们就生活在永生代。”

    具体区域可以通过VisualVM中的VisaulGC插件查看,如图(openjdk 1.7):

    年轻代:是所有新对象产生的地方。年轻代被分为3个部分——Enden区和两个Survivor区(From和to)当Eden区被对象填满时,就会执行Minor GC。并把所有存活下来的对象转移到其中一个survivor区(假设为from区)。Minor GC同样会检查存活下来的对象,并把它们转移到另一个survivor区(假设为to区)。这样在一段时间内,总会有一个空的survivor区。经过多次GC周期后,仍然存活下来的对象会被转移到年老代内存空间。通常这是在年轻代有资格提升到年老代前通过设定年龄阈值来完成的。需要注意,Survivor的两个区是对称的,没先后关系,from和to是相对的。

    年老代:在年轻代中经历了N次回收后仍然没有被清除的对象,就会被放到年老代中,可以说他们都是久经沙场而不亡的一代,都是生命周期较长的对象。对于年老代和永久代,就不能再采用像年轻代中那样搬移腾挪的回收算法,因为那些对于这些回收战场上的老兵来说是小儿科。通常会在老年代内存被占满时将会触发Full GC,回收整个堆内存。

    持久代:用于存放静态文件,比如java类、方法等。持久代对垃圾回收没有显著的影响。 

    分代回收的效果图如下:

    我这里之所以最后讲分代,是因为分代里涉及了前面几种算法。年轻代:涉及了复制算法;年老代:涉及了“标记-整理(Mark-Sweep)”的算法。

    四、垃圾收集器

    垃圾收集算法是内存回收的方法论,而实现这些方法论的则是垃圾收集器。不同厂商不同版本JVM所提供的垃圾收集器可能不同,这里参照《深入理解Java虚拟机》说的是JDK1.7版本Hotspot虚拟机,关于垃圾收集器有篇博文总结的不错,我就不说了,详见:http://blog.csdn.net/java2000_wl/article/details/8030172

    总结

    虽然我不认为学习java必须去了解Java底层的实现,但是我想如果你更加理解JVM和GC的话,你就会更加理解Java,在以后的学习和工作中绝对受益匪浅。毕竟我们的目标不是刷墙工,不是搬运工,而是开发攻城狮啊!

    九、高并发(找到两篇文章先存下来,没太懂的说,感觉自己功力差很多)

    转载1:http://www.oschina.net/news/73476/programmer-high-concurrency

    简单理解下高并发:

    高并发是指在同一个时间点,有很多用户同时的访问URL地址,比如:淘宝的双11,双12,就会产生高并发,如贴吧的爆吧,就是恶意的高并发请 求,也就是DDOS攻击,再屌丝点的说法就像玩撸啊撸被ADC暴击了一样,那伤害你懂得(如果你看懂了,这个说法说明是正在奔向人生巅峰的屌丝。

    高并发会来带的后果

    • 服务端:

      • 导致站点服务器/DB服务器资源被占满崩溃,数据的存储和更新结果和理想的设计是不一样的,比如:出现重复的数据记录,多次添加了用户积分等。

    • 用户角度:

      • 尼玛,这么卡,老子来参加活动的,刷新了还是这样,垃圾网站,再也不来了。

    • 我的经历:
      在做公司产品网站的过程中,经常会有这样的需求,比如什么搞个活动专题,抽奖,签到,搞个积分竞拍等等,如果没有考虑到高并发下的数据处理,那就Game Over了,很容易导致抽奖被多抽走,签到会发现一个用户有多条记录,签到一次获得了获得了多积分,等等,各种超出正常逻辑的现象,这就是做产品网站必须 考虑的问题,因为这些都是面向大量用户的,而不是像做ERP管理系统,OA系统那样,只是面向员工。

    下面我进行实例分析,简单粗暴,动态分析,纯属本人个人经验分享,如有说错,或者有更好的建议或者意见的请留言,大家一起成长。

    并发下的数据处理:

    通过表设计,如:记录表添加唯一约束,数据处理逻辑使用事物防止并发下的数据错乱问题
    通过服务端锁进程防止包并发下的数据错乱问题

    这里主要讲述的是在并发请求下的数据逻辑处理的接口,如何保证数据的一致性和完整性,这里的并发可能是大量用户发起的,也可能攻击者通过并发工具发起的并发请求


    如例子:通过表设计防止并发导致数据错乱

    • 需求点 
      【签到功能】 一天一个用户只能签到一次,
      签到成功后用户获取到一个积分

    • 已知表 
      用户表,包含积分字段   
      高并发意淫分析(属于开发前的猜测): 
      在高并发的情况下,会导致,一个用户签到记录会有多条,或者用户签到后不止加一积分。

    • 我的设计 
      首先根据需求我会添加一张签到记录表,重点来了,这张表需要把用户唯一标识字段(ID,Token)和签到日期字段添加为唯一约束,或者唯一索引,这样就 可以防止并发的时候插入重复用户的签到记录。然后再程序代码逻辑里,先执行签到数据的添加(这里可以防止并发,添加成功后再进行积分的添加,这样就可以防 止重复的添加积分了。最后我还是建议所有的数据操作都写在一个sql事务里面,  这样在添加失败,或者编辑用户积分失败的时候可以回滚数据。


    如例子2(事务+通过更新锁 防止并发导致数据错乱 或者事物+Update的锁表机制)

    • 需求点: 
      【抽奖功能】 抽奖一次消耗一个积分 抽奖中奖后编辑剩余奖品总数 剩余奖品总数为0,或者用户积分为0的时候无法进行抽奖

    • 已知表:  
      用户表,包含积分字段 奖品表,包含奖品剩余数量字段

    • 高并发意淫分析(属于开发前的猜测): 
      在高并发的情况下,会导致用户参与抽奖的时候积分被扣除,而奖品实际上已经被抽完了

    • 我的设计: 
      在事物里,通过WITH (UPDLOCK) 锁住商品表,或者Update 表的奖品剩余数量和最后编辑时间字段,来把数据行锁住,然后进行用户积分的消耗,都完成后提交事物,失败就回滚。 这样就可以保证,只有可能存在一个操作在操作这件商品的数量,只有等到这个操作事物提交后,其他的操作这个商品行的事物才会继续执行。


    如例子3(通过程序代码防止包并发下的数据错乱问题)

    • 需求点:
      【缓存数据到cache里】, 当缓存不存在的时候,从数据库中获取并保存在cache里,如果存在从cache里获取,每天10点必须更新一次,其他时间点缓存两个小时更新一次 到10点的时候,凡是打开页面的用户会自动刷新页面

    • 问题点:
      这里有个逻辑用户触发缓存的更新,用户刷新页面,当缓存存在的时候,会取到最后一次缓存更新时间,如果当前时间大于十点,并且最后缓存时间是10点前,则 会从数据库中重新获取数据保存到cache中。 还有客户端页面会在10点时候用js发起页面的刷新,就是因为有这样的逻辑,导致10点的时候有很多并发请求同时过来,然后就会导致很多的sql查询操 作,理想的逻辑是,只有一个请求会去数据库获取,其他都是从缓存中获取数据。(因为这个sql查询很耗服务器性能,所以导致在10点的时候,突然间数据库 服务器压力暴增)

    • 解决问题:
      C#通过 (锁)lock,在从数据读取到缓存的那段代码前面加上锁,这样在并发的情况下只会有一个请求是从数据库里获取数据,其他都是从缓存中获取。


    访问量大的数据统计接口

    • 需求: 用户行为数据统计接口,用来记录商品展示次数,用户通过点击图片,或者链接,或者其他方式进入到商品详情的行为次数

    • 问题点:
      这接口是给前端ajax使用,访问量会很大,一页面展示的时候就会有几十件商品的展示,滚动条滚到到页面显示商品的时候就会请求接口进行展示数据的统计,每次翻页又会加载几十件

    • 意淫分析:
      设想如果同时有1W个用户同时在线访问页面,一个次拉动滚动条屏幕页面展示10件商品,这样就会有10W个请求过来,服务端需要把请求数据入库。在实际线上环境可能还会超过这个请求量,如果不经过进行高并发设计处理,服务器分分钟给跪了。

    • 解决问题:
      我们通过nodejs写了一个数据处理接口,把统计数据先存到redis的list里。(使用nodejs写接口的好处是,nodejs使用单线程异步事件机制,高并发处理能力强,不会因为数据逻辑处理问题导致服务器资源被占用而导致服务器宕机) 然后再使用nodejs写了一个脚本,脚本功能就是从redis里出列数据保存到mysql数据库中。这个脚本会一直运行,当redis没有数据需要同步到数据库中的时候,sleep,让在进行数据同步操作


    高并发的下的服务器压力均衡,合理站点架设,DB部署

    以下我所知道的:

    1. 服务器代理nginx,做服务器的均衡负载,把压力均衡到多台服务器

    2. 部署集群 mysql数据库, redis服务器,或者mongodb服务器,把一些常用的查询数据,并且不会经常的变化的数据保存到其他nosql    DB服务器中,来减少数据库服务器的压力,加快数据的响应速度。

    3. 数据缓存,Cache

    4. 在高并发接口的设计中可以使用具有高并发能力的编程语言去开发,如:nodejs 做web接口

    5. 服务器部署,图片服务器分离,静态文件走CDN

    6. DBA数据库的优化查询条件,索引优化

    7. 消息存储机制,将数据添加到信息队列中(redis list),然后再写工具去入库

    8. 脚本合理控制请求,如,防止用户重复点击导致的ajax多余的请求,等等。

    并发测试神器推荐

    1. Apache JMeter

    2. Microsoft Web Application Stress Tool

    3. Visual Studio 性能负载

    文章来源:SFLYQ

     转载2:http://www.cnblogs.com/lezai/p/4932396.html

    之前我将高并发的解决方法误认为是线程或者是队列可以解决,因为高并发的时候是有很多用户在访问,导致出现系统数据不正确、丢失数据现象,所以想到 的是用队列解决,其实队列解决的方式也可以处理,比如我们在竞拍商品、转发评论微博或者是秒杀商品等,同一时间访问量特别大,队列在此起到特别的作用,将 所有请求放入队列,以毫秒计时单位,有序的进行,从而不会出现数据丢失系统数据不正确的情况。

    今天我经过查资料,高并发的解决方法有俩种,一种是使用缓存、另一种是使用生成静态页面;还有就是从最基础的地方优化我们写代码减少不必要的资源浪费:(

    1.不要频繁的new对象,对于在整个应用中只需要存在一个实例的类使用单例模式.对于String的连接操作,使用StringBuffer或者StringBuilder.对于utility类型的类通过静态方法来访问。

    2. 避免使用错误的方式,如Exception可以控制方法推出,但是Exception要保留stacktrace消耗性能,除非必要不要使用 instanceof做条件判断,尽量使用比的条件判断方式.使用JAVA中效率高的类,比如ArrayList比Vector性能好。)

    首先缓存技术我一直没有使用过,我觉得应该是在用户请求时将数据保存在缓存中,下次请求时会检测缓存中是否有数据存在,防止多次请求服务器,导致服务器性能降低,严重导致服务器崩溃,这只是我自己的理解,详细的资料还是需要在网上收集;

    使用生成静态页面我想大家应该不模式,我们见过很多网站当在请求的时候页面的后最已经变了,如“http://developer.51cto.com/art/201207/348766.htm”该页面其实是一个服务器请求地址,在转换成htm后,访问速度将提升,因为静态页面不带有服务器组件;在这里我就多多介绍一下:

    (一)什么是页面静态化:

    简 单的说,我们如果访问一个链接 ,服务器对应的模块会处理这个请求,转到对应的jsp界面,最后生成我们想要看到的数据。这其中的缺点是显而易见的:因为每次请求服务器都会进行处理,如 果有太多的高并发请求,那么就会加重应用服务器的压力,弄不好就把服务器 搞down 掉了。那么如何去避免呢?如果我们把对 test.do 请求后的结果保存成一个 html 文件,然后每次用户都去访问 ,这样应用服务器的压力不就减少了?

    那么静态页面从哪里来呢?总不能让我们每个页面都手动处理吧?这里就牵涉到我们要讲解的内容了,静态页面生成方案… 我们需要的是自动的生成静态页面,当用户访问 ,会自动生成 test.html ,然后显示给用户。

    (二)下面我们在简单介绍一下要想掌握页面静态化方案应该掌握的知识点:

    1、 基础- URL Rewrite

    什么是 URL Rewrite 呢 ? URL 重写。用一个简单的例子来说明问题:输入网址 ,但是实际上访问的却是 abc.com/test.action,那我们就可以说 URL 被重写了。这项技术应用广泛,有许多开源的工具可以实现这个功能。

    2、 基础- Servlet web.xml

    如果你还不知道 web.xml 中一个请求和一个 servlet 是如何匹配到一起的,那么请搜索一下 servlet 的文档。这可不是乱说呀,有很多人就认为 /xyz/*.do 这样的匹配方式能有效。

    如果你还不知道怎么编写一个 servlet ,那么请搜索一下如何编写 servlet.这可不是说笑呀,在各种集成工具漫天飞舞的今天,很多人都不会去从零编写一个 servlet了。

    (三)基本的方案介绍

     
    其中,对于 URL Rewriter的部分,可以使用收费或者开源的工具来实现,如果 url不是特别的复杂,可以考虑在 servlet 中实现,那么就是下面这个样子:
     
     
    总 结:其实我们在开发中都很少考虑这种问题,直接都是先将功能实现,当一个程序员在干到1到2年,就会感觉光实现功能不是最主要的,安全性能、质量等等才是 一个开发人员最该关心的。今天我所说的是高并发,我的解决思路是,1、采用分布式应用设计2、分布式缓存数据库3、代码优化

    十、工厂模式

     老师代码:

    简单工厂模式实现加减乘除法

     

    1.创建运算的接口

    public interface Operation {
        //提供计算两个数字的方法
        double getResult(double num1,double num2);
    
    }

    2.创建对应的加减乘除四个实现类

    复制代码
    public class Addition implements Operation { //加法
    
        @Override
        public double getResult(double num1, double num2) {
            return num1+num2;
        }
    
    }
    复制代码
    复制代码
    public class Minus implements Operation { //减法
    
        @Override
        public double getResult(double num1, double num2) {
            return num1-num2;
        }
    
    }
    复制代码
    复制代码
    public class Multiplication implements Operation {//乘法
    
        @Override
        public double getResult(double num1, double num2) {
            return num1*num2;
        }
    
    }
    复制代码
    复制代码
    public class Division implements Operation {//除法
    
        @Override
        public double getResult(double num1, double num2) {
            return num1/num2;
        }
    }
    复制代码

    3.创建工厂类

    复制代码
    public class OperationFactory {  //计算机的工厂类
        /*
         * 工厂模式 是我们最常用的实例化对象的模式!
         * 用工厂的方法替代new!
         * 虽然代码量没有减少  但是 提高了程序的扩展性!
         */
        public static  Operation  getOperation(String o){
            Operation operation=null; //多态
            switch (o) {
            case "+":
                operation=new Addition();
                break;
            case "-":
                operation=new Minus();
                break;
            case "*":
                operation=new Multiplication();
                break;
            case "/":
                operation=new Division();
                break;
            }
            return  operation;
        }
    }
    复制代码

    4.创建测试类 运行 测试结果

    复制代码
    public class FactoryTest {
        public static void main(String[] args) {
            Scanner scanner=new Scanner(System.in);
            System.out.println("请输入第一个数字:");
            double num1 = scanner.nextInt();
            System.out.println("请输入运算符:");
            String operation = scanner.next();
            System.out.println("请输入第二个数字:");
            double num2 = scanner.nextInt();
            //创建运算的实例对象
            Operation o = OperationFactory.getOperation(operation);
            //输出结果
            System.out.println(o.getResult(num1, num2));
        }
    }

    十一、作业

    1、soso项目

    2、周五考试(机考+笔试),刷题刷题!

    3、随时准备答辩soso项目

    十二、老师辛苦了!

  • 相关阅读:
    Jetson Nano更改软件源
    树莓派开机启动VNC
    树莓派VNC复制粘贴
    数学之美-泰勒公式
    C++顺序容器
    eigen的简单用法汇总
    C++并发-同步并发
    C++ string类
    C++并发-互斥元
    Nginx常用命令
  • 原文地址:https://www.cnblogs.com/wsnedved2017/p/6768209.html
Copyright © 2011-2022 走看看