十一月's profileNOVEMBREBlogListsGuestbookMore ![]() | Help |
|
|
June 03 Java虚拟机一、什么是Java虚拟机 当你谈到Java虚拟机时,你可能是指: 1、抽象的Java虚拟机规范 2、一个具体的Java虚拟机实现 3、一个运行的Java虚拟机实例 二、Java虚拟机的生命周期 一个运行中的Java虚拟机有着一个清晰的任务:执行Java程序。程序开始执行时他才运行,程序结束时他就停止。你在同一台机器上运行三个程序,就会有三个运行中的Java虚拟机。 Java虚拟机总是开始于一个main()方法,这个方法必须是公有、返回void、直接受一个字符串数组。在程序执行时,你必须给Java虚拟机指明这个包换main()方法的类名。 Main()方法是程序的起点,他被执行的线程初始化为程序的初始线程。程序中其他的线程都由他来启动。Java中的线程分为两种:守护线程 (daemon)和普通线程(non-daemon)。守护线程是Java虚拟机自己使用的线程,比如负责垃圾收集的线程就是一个守护线程。当然,你也可 以把自己的程序设置为守护线程。包含Main()方法的初始线程不是守护线程。 只要Java虚拟机中还有普通的线程在执行,Java虚拟机就不会停止。如果有足够的权限,你可以调用exit()方法终止程序。 三、Java虚拟机的体系结构 在Java虚拟机的规范中定义了一系列的子系统、内存区域、数据类型和使用指南。这些组件构成了Java虚拟机的内部结构,他们不仅仅为Java虚拟机的实现提供了清晰的内部结构,更是严格规定了Java虚拟机实现的外部行为。 每一个Java虚拟机都由一个类加载器子系统(class loader subsystem),负责加载程序中的类型(类和接口),并赋予唯一的名字。每一个Java虚拟机都有一个执行引擎(execution engine)负责执行被加载类中包含的指令。 程序的执行需要一定的内存空间,如字节码、被加载类的其他额外信息、程序中的对象、方法的参数、返回值、本地变量、处理的中间变量等等。Java虚拟机将 这些信息统统保存在数据区(data areas)中。虽然每个Java虚拟机的实现中都包含数据区,但是Java虚拟机规范对数据区的规定却非常的抽象。许多结构上的细节部分都留给了 Java虚拟机实现者自己发挥。不同Java虚拟机实现上的内存结构千差万别。一部分实现可能占用很多内存,而其他以下可能只占用很少的内存;一些实现可 能会使用虚拟内存,而其他的则不使用。这种比较精炼的Java虚拟机内存规约,可以使得Java虚拟机可以在广泛的平台上被实现。 数据区中的一部分是整个程序共有,其他部分被单独的线程控制。每一个Java虚拟机都包含方法区(method area)和堆(heap),他们都被整个程序共享。Java虚拟机加载并解析一个类以后,将从类文件中解析出来的信息保存与方法区中。程序执行时创建的 对象都保存在堆中。 当一个线程被创建时,会被分配只属于他自己的PC寄存器“pc register”(程序计数器)和Java堆栈(Java stack)。当线程不掉用本地方法时,PC寄存器中保存线程执行的下一条指令。Java堆栈保存了一个线程调用方法时的状态,包括本地变量、调用方法的 参数、返回值、处理的中间变量。调用本地方法时的状态保存在本地方法堆栈中(native method stacks),可能再寄存器或者其他非平台独立的内存中。 Java堆栈有堆栈块(stack frames (or frames))组成。堆栈块包含Java方法调用的状态。当一个线程调用一个方法时,Java虚拟机会将一个新的块压到Java堆栈中,当这个方法运行结束时,Java虚拟机会将对应的块弹出并抛弃。 Java虚拟机不使用寄存器保存计算的中间结果,而是用Java堆栈在存放中间结果。这是的Java虚拟机的指令更紧凑,也更容易在一个没有寄存器的设备上实现Java虚拟机。 图中的Java堆栈中向下增长的,PC寄存器中线程三为灰色,是因为它正在执行本地方法,他的下一条执行指令不保存在PC寄存器中。 四、数据类型(Data Types) 所有Java虚拟机中使用的数据都有确定的数据类型,数据类型和操作都在Java虚拟机规范中严格定义。Java中的数据类型分为原始数据类型 (primitive types)和引用数据类型(reference type)。引用类型依赖于实际的对象,但不是对象本身。原始数据类型不依赖于任何东西,他们就是本身表示的数据。 所有Java程序语言中的原始 数据类型,都是Java虚拟机的原始数据类型,除了布尔型(boolean)。当编译器将Java源代码编译为自己码时,使用整型(int)或者字节型 (byte)去表示布尔型。在Java虚拟机中使用整数0表示布尔型的false,使用非零整数表示布尔型的true,布尔数组被表示为字节数组,虽然他 们可能会以字节数组或者字节块(bit fields)保存在堆中。 除了布尔型,其他Java语言中的原始类型都是Java虚拟机中的数据类型。在Java中数据类型被分为:整形的byte,short,int,long;char和浮点型的float,double。Java语言中的数据类型在任何主机上都有同样的范围。 在Java虚拟机中还存在一个Java语言中不能使用的原始数据类型返回值类型(returnValue)。这种类型被用来实现Java程序中的“finally clauses”,具体的参见18章的“Finally Clauses”。 引用类型可能被创建为:类类型(class type),接口类型(interface type),数组类型(array type)。他们都引用被动态创建的对象。当引用类型引用null时,说明没有引用任何对象。 Java虚拟机规范只定义了每一种数据类型表示的范围,没有定义在存储时每种类型占用的空间。他们如何存储由Java虚拟机的实现者自己决定。关于浮点型更多信息参见14章“Floating Point Arithmetic”。 TypeRange byte8-bit signed two's complement integer (-27 to 27 - 1, inclusive) short16-bit signed two's complement integer (-215 to 215 - 1, inclusive) int32-bit signed two's complement integer (-231 to 231 - 1, inclusive) long64-bit signed two's complement integer (-263 to 263 - 1, inclusive) char16-bit unsigned Unicode character (0 to 216 - 1, inclusive) float32-bit IEEE 754 single-precision float double64-bit IEEE 754 double-precision float returnValueaddress of an opcode within the same method referencereference to an object on the heap, or null 五、字节长度 Java虚拟机中最小的数据单元式字(word),其大小由Java虚拟机的实现者定义。但是一个字的大小必须足够容纳byte,short,int, char,float,returnValue,reference;两个字必须足够容纳long,double。所以虚拟机的实现者至少提供的字不能小 于31bits的字,但是最好选择特定平台上最有效率的字长。 在运行时,Java程序不能决定所运行机器的字长。字长也不会影响程序的行为,他只是在Java虚拟机中的一种表现方式。 六、类加载器子系统 Java虚拟机中的类加载器分为两种:原始类加载器(primordial class loader)和类加载器对象(class loader objects)。原始类加载器是Java虚拟机实现的一部分,类加载器对象是运行中的程序的一部分。不同类加载器加载的类被不同的命名空间所分割。 类加载器调用了许多Java虚拟机中其他的部分和java.lang包中的很多类。比如,类加载对象就是java.lang.ClassLoader子类 的实例,ClassLoader类中的方法可以访问虚拟机中的类加载机制;每一个被Java虚拟机加载的类都会被表示为一个 java.lang.Class类的实例。像其他对象一样,类加载器对象和Class对象都保存在堆中,被加载的信息被保存在方法区中。 1、加载、连接、初始化(Loading, Linking and Initialization) 类加载子系统不仅仅负责定位并加载类文件,他按照以下严格的步骤作了很多其他的事情:(具体的信息参见第七章的“类的生命周期”) 1)、加载:寻找并导入指定类型(类和接口)的二进制信息 2)、连接:进行验证、准备和解析 ①验证:确保导入类型的正确性 ②准备:为类型分配内存并初始化为默认值 ③解析:将字符引用解析为直接饮用 3)、初始化:调用Java代码,初始化类变量为合适的值 2、原始类加载器(The Primordial Class Loader) 每个Java虚拟机都必须实现一个原始类加载器,他能够加载那些遵守类文件格式并且被信任的类。但是,Java虚拟机的规范并没有定义如何加载类,这由 Java虚拟机实现者自己决定。对于给定类型名的类型,原始莱加载器必须找到那个类型名加“.class”的文件并加载入虚拟机中。 3、类加载器对象 虽然类加载器对象是Java程序的一部分,但是ClassLoader类中的三个方法可以访问Java虚拟机中的类加载子系统。 1)、protected final Class defineClass(…):使用这个方法可以出入一个字节数组,定义一个新的类型。 2)、protected Class findSystemClass(String name):加载指定的类,如果已经加载,就直接返回。 3)、protected final void resolveClass(Class c):defineClass()方法只是加载一个类,这个方法负责后续的动态连接和初始化。 具体的信息,参见第八章“连接模型”( The Linking Model)。 4、命名空间 当多个类加载器加载了同一个类时,为了保证他们名字的唯一性,需要在类名前加上加载该类的类加载器的标识。具体的信息,参见第八章“连接模型”( The Linking Model)。 七、方法区(The Method Area) 在Java虚拟机中,被加载类型的信息都保存在方法区中。这写信息在内存中的组织形式由虚拟机的实现者定义,比如,虚拟机工作在一个“little- endian”的处理器上,他就可以将信息保存为“little-endian”格式的,虽然在Java类文件中他们是以“big-endian”格式保 存的。设计者可以用最适合并地机器的表示格式来存储数据,以保证程序能够以最快的速度执行。但是,在一个只有很小内存的设备上,虚拟机的实现者就不会占用 很大的内存。 程序中的所有线程共享一个方法区,所以访问方法区信息的方法必须是线程安全的。如果你有两个线程都去加载一个叫Lava的类,那只能由一个线程被容许去加载这个类,另一个必须等待。 在程序运行时,方法区的大小是可变的,程序在运行时可以扩展。有些Java虚拟机的实现也可以通过参数也订制方法区的初始大小,最小值和最大值。 方法区也可以被垃圾收集。因为程序中的内由类加载器动态加载,所有类可能变成没有被引用(unreferenced)的状态。当类变成这种状态时,他就可 能被垃圾收集掉。没有加载的类包括两种状态,一种是真正的没有加载,另一个种是“unreferenced”的状态。详细信息参见第七章的类的生命周期 (The Lifetime of a Class)。 1、类型信息(Type Information) 每一个被加载的类型,在Java虚拟机中都会在方法区中保存如下信息: 1)、类型的全名(The fully qualified name of the type) 2)、类型的父类型的全名(除非没有父类型,或者弗雷形式java.lang.Object)(The fully qualified name of the typeís direct superclass) 3)、给类型是一个类还是接口(class or an interface)(Whether or not the type is a class ) 4)、类型的修饰符(public,private,protected,static,final,volatile,transient等)(The typeís modifiers) 5)、所有父接口全名的列表(An ordered list of the fully qualified names of any direct superinterfaces) 类型全名保存的数据结构由虚拟机实现者定义。除此之外,Java虚拟机还要为每个类型保存如下信息: 1)、类型的常量池(The constant pool for the type) 2)、类型字段的信息(Field information) 3)、类型方法的信息(Method information) 4)、所有的静态类变量(非常量)信息(All class (static) variables declared in the type, except constants) 5)、一个指向类加载器的引用(A reference to class ClassLoader) 6)、一个指向Class类的引用(A reference to class Class) 1)、类型的常量池(The constant pool for the type) 常量池中保存中所有类型是用的有序的常量集合,包含直接常量(literals)如字符串、整数、浮点数的常量,和对类型、字段、方法的符号引用。常量池 中每一个保存的常量都有一个索引,就像数组中的字段一样。因为常量池中保存中所有类型使用到的类型、字段、方法的字符引用,所以它也是动态连接的主要对 象。详细信息参见第六章“The Java Class File”。 2)、类型字段的信息(Field information) 字段名、字段类型、字段的修饰符(public,private,protected,static,final,volatile,transient等)、字段在类中定义的顺序。 3)、类型方法的信息(Method information) 方法名、方法的返回值类型(或者是void)、方法参数的个数、类型和他们的顺序、字段的修饰符(public,private,protected,static,final,volatile,transient等)、方法在类中定义的顺序 如果不是抽象和本地本法还需要保存 方法的字节码、方法的操作数堆栈的大小和本地变量区的大小(稍候有详细信息)、异常列表(详细信息参见第十七章“Exceptions”。) 4)、类(静态)变量(Class Variables) 类变量被所有类的实例共享,即使不通过类的实例也可以访问。这些变量绑定在类上(而不是类的实例上),所以他们是类的逻辑数据的一部分。在Java虚拟机使用这个类之前就需要为类变量(non-final)分配内存 常量(final)的处理方式于这种类变量(non-final)不一样。每一个类型在用到一个常量的时候,都会复制一份到自己的常量池中。常量也像类变 量一样保存在方法区中,只不过他保存在常量池中。(可能是,类变量被所有实例共享,而常量池是每个实例独有的)。Non-final类变量保存为定义他的 类型数据(data for the type that declares them)的一部分,而final常量保存为使用他的类型数据(data for any type that uses them)的一部分。详情参见第六章“The Java Class FileThe Java Class File” 5)、指向类加载器的引用(A reference to class ClassLoader) 每一个被Java虚拟机加载的类型,虚拟机必须保存这个类型是否由原始类加载器或者类加载器加载。那些被类加载器加载的类型必须保存一个指向类加载器的引 用。当类加载器动态连接时,会使用这条信息。当一个类引用另一个类时,虚拟机必须保存那个被引用的类型是被同一个类加载器加载的,这也是虚拟机维护不同命 名空间的过程。详情参见第八章“The Linking Model” 6)、指向Class类的引用(A reference to class Class) Java虚拟机为每一个加载的类型创建一个java.lang.Class类的实例。你也可以通过Class类的方法: public static Class forName(String className)来查找或者加载一个类,并取得相应的Class类的实例。通过这个Class类的实例,我们可以访问Java虚拟机方法区中的信息。具体参照Class类的JavaDoc。 2、方法列表(Method Tables) 为了更有效的访问所有保存在方法区中的数据,这些数据的存储结构必须经过仔细的设计。所有方法区中,除了保存了上边的那些原始信息外,还有一个为了加快存 取速度而设计的数据结构,比如方法列表。每一个被加载的非抽象类,Java虚拟机都会为他们产生一个方法列表,这个列表中保存了这个类可能调用的所有实例 方法的引用,报错那些父类中调用的方法。详情参见第八章“The Linking Model” 八、堆 当Java程序创建一个类的实例或者数组时,都在堆中为新的对象分配内存。虚拟机中只有一个堆,所有的线程都共享他。 1、垃圾收集(Garbage Collection) 垃圾收集是释放没有被引用的对象的主要方法。它也可能会为了减少堆的碎片,而移动对象。在Java虚拟机的规范中没有严格定义垃圾收集,只是定义一个Java虚拟机的实现必须通过某种方式管理自己的堆。详情参见第九章“Garbage Collection”。 2、对象存储结构(Object Representation) Java虚拟机的规范中没有定义对象怎样在堆中存储。每一个对象主要存储的是他的类和父类中定义的对象变量。对于给定的对象的引用,虚拟机必须嫩耨很快的 定位到这个对象的数据。另为,必须提供一种通过对象的引用方法对象数据的方法,比如方法区中的对象的引用,所以一个对象保存的数据中往往含有一个某种形式 指向方法区的指针。 一个可能的堆的设计是将堆分为两个部分:引用池和对象池。一个对象的引用就是指向引用池的本地指针。每一个引用池中的条目都包含两个部分:指向对象池中对 象数据的指针和方法区中对象类数据的指针。这种设计能够方便Java虚拟机堆碎片的整理。当虚拟机在对象池中移动一个对象的时候,只需要修改对应引用池中 的指针地址。但是每次访问对象的数据都需要处理两次指针。下图演示了这种堆的设计。在第九章的“垃圾收集”中的HeapOfFish Applet演示了这种设计。 另一种堆的设计是:一个对象的引用就是一个指向一堆数据和指向相应对象的偏移指针。这种设计方便了对象的访问,可是对象的移动要变的异常复杂。下图演示了这种设计 当程序试图将一个对象转换为另一种类型时,虚拟机需要判断这种转换是否是这个对象的类型,或者是他的父类型。当程序适用instanceof语句的时候也 会做类似的事情。当程序调用一个对象的方法时,虚拟机需要进行动态绑定,他必须判断调用哪一个类型的方法。这也需要做上面的判断。 无论虚拟机实现者使用哪一种设计,他都可能为每一个对象保存一个类似方法列表的信息。因为他可以提升对象方法调用的速度,对提升虚拟机的性能非常重要,但 是虚拟机的规范中比没有要求必须实现类似的数据结构。下图描述了这种结构。图中显示了一个对象引用相关联的所有的数据结构,包括: 1)、一个指向类型数据的指针 2)、一个对象的方法列表。方法列表是一个指向所有可能被调用对象方法的指针数组。方法数据包括三个部分:操作码堆栈的大小和方法堆栈的本地变量区;方法的字节码;异常列表。 每一个Java虚拟机中的对象必须关联一个用于同步多线程的lock(mutex)。同一时刻,只能有一个对象拥有这个对象的锁。当一个拥有这个这个对象 的锁,他就可以多次申请这个锁,但是也必须释放相应次数的锁才能真正释放这个对象锁。很多对象在整个生命周期中都不会被锁,所以这个信息只有在需要时才需 要添加。很多Java虚拟机的实现都没有在对象的数据中包含“锁定数据”,只是在需要时才生成相应的数据。除了实现对象的锁定,每一个对象还逻辑关联到一 个“wait set”的实现。锁定帮组线程独立处理共享的数据,不需要妨碍其他的线程。“wait set”帮组线程协作完成同一个目标。“wait set”往往通过Object类的wait()和notify()方法来实现。 垃圾收集也需要堆中的对象是否被关联的信息。Java虚拟机规范中指出垃圾收集一个运行一个对象的finalizer方法一次,但是容许 finalizer方法重新引用这个对象,当这个对象再次不被引用时,就不需要再次调用finalize方法。所以虚拟机也需要保存finalize方法 是否运行过的信息。更多信息参见第九章的“垃圾收集” 3、数组的保存(Array Representation) 在Java 中,数组是一种完全意义上的对象,他和对象一样保存在堆中、有一个指向Class类实例的引用。所有同一维度和类型的数组拥有同样的Class,数组的长 度不做考虑。对应Class的名字表示为维度和类型。比如一个整型数据的Class为“[I”,字节型三维数组Class名为“[[[B”,两维对象数据 Class名为“[[Ljava.lang.Object”。 多维数组被表示为数组的数组,如下图: 数组必须在堆中保存数组的长度,数组的数据和一些对象数组类型数据的引用。通过一个数组引用的,虚拟机应该能够取得一个数组的长度,通过索引能够访问特定 的数据,能够调用Object定义的方法。Object是所有数据类的直接父类。更多信息参见第六章“类文件”。 九、PC寄存器(程序计数器)(The Program Counter) 每一个线程开始执行时都会被创建一个程序计数器。程序计数器只有一个字长(word),所以它能够保存一个本地指针和returnValue。当线程执行 时,程序计数器中存放了正在执行指令的地址,这个地址可以使一个本地指针,也可以使一个从方法字节码开始的偏移指针。如果执行本地方法,程序计数器的值没 有被定义。 十、Java堆栈(The Java Stack) 当一个线程启动时,Java虚拟机会为他创建一个Java堆栈。Java堆栈用一些离散的frame类纪录线程的状态。Java虚拟机堆Java堆栈的操作只有两种:压入和弹出frames。 线程中正在执行的方法被称为当前方法(current method),当前方法所对应的frame被称为当前帧(current frame)。定义当前方法的类被称为当前类(current class),当前类的常量池被称为当前常量池(current constant pool.)。当线程执行时,Java虚拟机会跟踪当前类和当前常量池。但线程操作保存在帧中的数据时,他只操作当前帧的数据。 当线程调用一个方法时,虚拟机会生成一个新的帧,并压入线程的Java堆栈。这个新的帧变成当前帧。当方法执行时,他使用当前帧保存方法的参数、本地变 量、中间结构和其他数据。方法有两种退出方式:正常退出和异常推出。无论方法以哪一种方式推出,Java虚拟机都会弹出并丢弃方法的帧,上一个方法的帧变 为当前帧。 所有保存在帧中的数据都只能被拥有它的线程访问,线程不能访问其他线程的堆栈中的数据。所以,访问方法的本地变量时,不需要考虑多线程同步。 和方法区、堆一样,Java堆栈不需要连续的内存空间,它可以被保存在一个分散的内存空间或者堆上。堆栈具体的数据和长度都有Java虚拟机的实现者自己定义。一些实现可能提供了执行堆栈最大值和最小值的方法。 十一、堆栈帧(The Stack Frame) 堆栈帧包含三部分:本地变量、操作数堆栈和帧数据。本地变量和操作数堆栈的大小都是一字(word)为单位的,他们在编译就已经确定。帧数据的大小取决于 不同的实现。当程序调用一个方法时,虚拟机从类数据中取得本地变量和操作数堆栈的大小,创建一个合适大小和帧,然后压入Java堆栈中。 1、本地变量(Local Variables) 本地变量在Java堆栈帧中被组织为一个从0计数的数组,指令通过提供他们的索引从本地变量区中取得相应的值。Int,float,reference, returnValue占一个字,byte,short,char被转换成int然后存储,long和doubel占两个字。 指令通过提供两个字索引中的前一个来取得long,doubel的值。比如一个long的值存储在索引3,4上,指令就可以通过3来取得这个long类型的值。 本地变量区中包含了方法的参数和本地变量。编译器将方法的参数以他们申明的顺序放在数组的前面。但是编译器却可以将本地变量任意排列在本地变量数组中,甚至两个本地变量可以公用一个地址,比如,当两个本地变量在两个不交叠的区域内,就像循环变量i,j。 虚拟机的实现者可以使用任何结构来描述本地变量区中的数据,虚拟机规范中没有定义如何存储long和doubel。 2、操作数堆栈(Operand Stack) 向本地变量一样,操作数堆栈也被组织为一个以字为单位的数组。但是不像本地变量那样通过索引访问,而是通过push和pop值来实现访问的。如果一个指令push一个值到堆栈中,那么下一个指令就可以pop并且使用这个值。 操作数堆栈不像程序计数器那样不可以被指令直接访问,指令可以直接访问操作数堆栈。Java虚拟机是一个以堆栈为基础,而不是以寄存器为基础的,因为它的 指令从堆栈中取得操作数,而不是同寄存器中。当然,指令也可以从其他地方去的操作数,比如指令后面的操作码,或者常量池。但是Java虚拟机指令主要是从 操作数堆栈中取得他们需要的操作数。 Java虚拟机将操作数堆栈视为工作区,很多指令通过先从操作数堆栈中pop值,在处理完以后再将结果push回操作数堆栈。一个add的指令执行过程如 下图所示:先执行iload_0和iload_1两条指令将需要相加的两个数,从本地方法区中取出,并push到操作数堆栈中;然后执行iadd指令,现 pop出两个值,相加,并将结果pusp进操作数堆栈中;最后执行istore_2指令,pop出结果,赋值到本地方法区中。 3、帧数据(Frame Data) 处理本地变量和操作数堆栈以外,java堆栈帧还包括了为了支持常量池,方法返回值和异常分发需要的数据,他们被保存在帧数据中。 当虚拟机遇到使用指向常量池引用的指令时,就会通过帧数据中指向常量区的指针来访问所需要的信息。前面提到过,常量区中的引用在最开始时都是符号引用。即使当虚拟机检查这些引用时,他们也是字符引用。所以虚拟机需要在这时转换这个引用。 当一个方法正常返回时,虚拟机需要重建那个调用这个方法的方法的堆栈帧。如果执行完的方法有返回值,虚拟机就需要将这个值push进调用方法的哪个操作数堆栈中。 帧数据中也包含虚拟机用来处理异常的异常表的引用。异常表定义了一个被catch语句保护的一段字节码。每一个异常表中的个体又包含了需要保护的字节玛的 范围,和异常被捕捉到时需要执行的字节码的位置。当一个方法抛出一个异常时,Java虚拟机就是用异常表去判断如何处理这个异常。如果虚拟机找到了一个匹 配的catch,他就会将控制权交给catch语句。如果没有找到匹配的catch,方法就会异常返回,然后再调用的方法中继续这个过程。 除了以上的三个用途外,帧数据还可能包含一些依赖于实现的数据,比如调试的信息。 十二、本地方法堆栈 本地方法区依赖于虚拟机的不同实现。虚拟机的实现者可以自己决定使用哪一种机制去执行本地方法。 任何本地方法接口(Native Method Interface)都使用某种形式的本地方法堆栈。 十三、执行引擎 一个java虚拟机实现的核心就是执行引擎。在Java虚拟机规范,执行引擎被描述为一系列的指令。对于每一个指令,规范都描述了他们应该做什么,但是没有说要如何去做。 1、指令集 在Java虚拟机中一个方法的字节码流就是一个指令的序列。每一个指令由一个字节的操作码(Opcode)和可能存在的操作数(Operands)。操作 码指示去做什么,操作数提供一些执行这个操作码可能需要的额外的信息。一个抽象的执行引擎每次执行一个指令。这个过程发生在每一个执行的线程中。 有时,执行引擎可能会遇到一个需要调用本地方法的指令,在这种情况下,执行引擎会去试图调用本地方法,但本地方法返回时,执行引擎会继续执行字节码流中的下一个指令。本地方法也可以看成对Java虚拟机中的指令集的一种扩充。 决定下一步执行那一条指令也是执行引擎工作的一部分。执行引擎有三种方法去取得下一条指令。多数指令会执行跟在他会面的指令;一些像goto, return的指令,会在他们执行的时候决定他们的下一条指令;当一个指令抛出异常时,执行引擎通过匹配catch语句来决定下一条应该执行的指令。 平台独立性、网络移动性、安全性左右了Java虚拟机指令集的设计。平台独立性是指令集设计的主要影响因素之一。基于堆栈的结构使得Java虚拟机可以在 更多的平台上实现。更小的操作码,紧凑的结构使得字节码可以更有效的利用网络带宽。一次性的字节码验证,使得字节码更安全,而不影响太多的性能。 2、执行技术 许多种执行技术可以用在Java虚拟机的实现中:解释执行,及时编译(just-in-time compiling),hot-spot compiling,native execution in silicon。 3、线程 Java虚拟机规范定义了一种为了在更多平台上实现的线程模型。Java线程模型的一个目标时可以利用本地线程。利用本地线程可以让Java程序中的线程能过在多处理器机器上真正的同时执行。 Java线程模型的一个代价就是线程优先级,一个Java线程可以在1-10的优先级上运行。1最低,10最高。如果设计者使用了本地线程,他们可能将这 10个优先级映射到本地优先级上。Java虚拟机规范只定义了,高一点优先级的线程可以却一些cpu时间,低优先级的线程在所有高优先级线程都堵塞时,也 可以获取一些cpu时间,但是这没有保证:低优先级的线程在高优先级线程没有堵塞时不可以获得一定的cpu时间。因此,如果需要在不同的线程间协作,你必 须使用的“同步(synchronizatoin)”。 同步意味着两个部分:对象锁(object locking)和线程等待、激活(thread wait and notify)。对象锁帮助线程可以不受其他线程的干扰。线程等待、激活可以让不同的线程进行协作。 在Java虚拟机的规范中,Java线程被描述为变量、主内存、工作内存。每一个Java虚拟机的实例都有一个主内存,他包含了所有程序的变量:对象、数组合类变量。每一个线程都有自己的工作内存,他保存了哪些他可能用到的变量的拷贝。规则: 1)、从主内存拷贝变量的值到工作内存中 2)、将工作内存中的值写会主内存中 如果一个变量没有被同步化,线程可能以任何顺序更新主内存中的变量。为了保证多线程程序的正确的执行,必须使用同步机制。 十四、本地方法接口(Native Method Interface) Java虚拟机的实现并不是必须实现本地方法接口。一些实现可能根本不支持本地方法接口。Sun的本地方法接口是JNI(Java Native Interface)。 十五、现实中的机器(The Real Machine) 十六、数学方法:仿真(Eternal Math : A Simulation) April 25 [转]Java程序员的推荐阅读书籍JavaEye (http://www.javaeye.com) 范凯(http://robbin.javaeye.com) 作为Java程序员来说,最痛苦的事情莫过于可以选择的范围太广,可以读的书太多,往往容易无所适从。我想就我自己读过的技术书籍中挑选出来一些,按照学习的先后顺序,推荐给大家,特别是那些想不断提高自己技术水平的Java程序员们。 一、Java编程入门类 对于没有Java编程经验的程序员要入门,随便读什么入门书籍都一样,这个阶段需要你快速的掌握Java基础语法和基本用法,宗旨就是“囫囵吞枣不求甚解”,先对Java熟悉起来再说。用很短的时间快速过一遍Java语法,连懵带猜多写写代码,要“知其然”。 1、《Java编程思想》 在有了一定的Java编程经验之后,你需要“知其所以然”了。这个时候《Java编程思想》是一本让你知其所以然的好书,它对于基本的面向对象知识有比较清楚的交待,对Java基本语法,基本类库有比较清楚的讲解,可以帮你打一个良好的Java编程基础。这本书的缺点是实在太厚,也比较罗嗦,不适合现代人快节奏学习,因此看这本书要懂得取舍,不是每章每节都值得一看的,挑重点的深入看就可以了。 2、《Agile Java》中文版 这本书是出版社送给我的,我一拿到就束之高阁,放在书柜一页都没有翻过,但是前两天整理书柜的时候,拿出来一翻,竟然发现这绝对是一本好书!这本书一大特点是以单元测试和TDD来贯穿全书的,在教你Java各种重要的基础知识的过程中,潜移默化的影响你的编程思维走向敏捷,走向TDD。另外这本书成书很新,以JDK5.0的语法为基础讲解,要学习JDK5.0的新语法也不错。还有这本书对于内容取舍也非常得当,Java语言毕竟类库庞大,可以讲的内容太多,这本书选择的内容以及内容的多寡都很得当,可以让你以最少的时间掌握Java最重要的知识,顺便培养出来优秀的编程思路,真是一本不可多得的好书。 虽然作者自己把这本书定位在入门级别,但我不确定这本书用来入门是不是稍微深了点,我自己也准备有空的时候翻翻这本书,学习学习。 二、Java编程进阶类 打下一个良好的Java基础,还需要更多的实践经验积累,我想没有什么捷径。有两本书值得你在编程生涯的这个阶段阅读,培养良好的编程习惯,提高你的代码质量。 1、《重构 改善既有代码的设计》 这本书名气很大,不用多介绍,可以在闲暇的时候多翻翻,多和自己的实践相互印证。这本书对你产生影响是潜移默化的。 2、《测试驱动开发 by Example》 本书最大特点是很薄,看起来没有什么负担。你可以找一个周末的下午,一边看,一边照做,一个下午就把书看完,这本书的所有例子跑完了。这本书的作用是通过实战让你培养TDD的思路。 三、Java架构师之路 到这个阶段,你应该已经非常娴熟的运用Java编程,而且有了一个良好的编程思路和习惯了,但是你可能还缺乏对应用软件整体架构的把握,现在就是你迈向架构师的第一步。 1、《Expert One-on-One J2EE Design and Development》 这本书是Rod Johnson的成名著作,非常经典,从这本书中的代码诞生了springframework。但是好像这本书没有中译本。 2、《Expert One-on-One J2EE Development without EJB》 这本书由gigix组织翻译,多位业界专家参与,虽然署名译者是JavaEye,其实JavaEye出力不多,实在是忝居译者之名。 以上两本书都是RodJohnson的经典名著,Java架构师的必读书籍。在我所推荐的这些书籍当中,是我看过的最仔细,最认真的书,我当时读这本书几乎是废寝忘食的一气读完的,有小时候挑灯夜读金庸武侠小说的劲头,书中所讲内容和自己的经验知识一一印证,又被无比精辟的总结出来,读完这本书以后,我有种被打通经脉,功力爆增的感觉。 但是后来我看过一些其他人的评价,似乎阅读体验并没有我那么high,也许是因为每个人的知识积累和经验不同导致的。我那个时候刚好是经验知识积累已经足够丰富,但是还没有系统的整理成型,让这本书一梳理,立刻形成完整的知识体系了。 3、《企业应用架构模式》 Martin的又一本名著,但这本书我只是泛泛的看了一遍,并没有仔细看。这本书似乎更适合做框架的人去看,例如如果你打算自己写一个ORM的话,这本书是一定要看的。但是做应用的人,不看貌似也无所谓,但是如果有空,我还是推荐认真看看,会让你知道框架为什么要这样设计,这样你的层次可以晋升到框架设计者的角度去思考问题。Martin的书我向来都是推崇,但是从来都没有像Rod Johnson的书那样非常认真去看。 4、《敏捷软件开发 原则、模式与实践》 Uncle Bob的名著,敏捷的经典名著,这本书比较特别,与其说是讲软件开发过程的书,不如说讲软件架构的书,本书用了很大篇幅讲各种面向对象软件开发的各种模式,个人以为看了这本书,就不必看GoF的《设计模式》了。 四、软件开发过程 了解软件开发过程不单纯是提高程序员个人的良好编程习惯,也是增强团队协作的基础。 1、《UML精粹》 UML其实和软件开发过程没有什么必然联系,却是软件团队协作沟通,撰写软件文档需要的工具。但是UML真正实用的图不多,看看这本书已经足够了,完全没有必要去啃《UML用户指南》之类的东西。要提醒大家的是,这本书的中译本翻译的非常之烂,建议有条件的看英文原版。 2、《解析极限编程 拥抱变化》XP 这是Kent Beck名著的第二版,中英文对照。没什么好说的,必读书籍。 3、《统一软件开发过程》UP 其实UP和敏捷并不一定冲突,UP也非常强调迭代,测试,但是UP强调的文档和过程驱动却是敏捷所不取的。不管怎么说,UP值得你去读,毕竟在中国真正接受敏捷的企业很少,你还是需要用UP来武装一下自己的,哪怕是披着UP的XP。 4、《敏捷建模》AM ScottAmbler的名著,这本书非常的progmatic,告诉你怎么既敏捷又UP,把敏捷和UP统一起来了,又提出了很多progmatic的建议和做法。你可以把《解析极限编程拥抱变化》、《统一软件开发过程》和《敏捷建模》这三本书放在一起读,看XP和UP的不同点,再看AM是怎么统一XP和UP的,把这三种理论融为一炉,形成自己的理论体系,那么你也可以去写书了。 五、软件项目管理 如果你突然被领导提拔为项目经理,而你完全没有项目管理经验,你肯定会心里没底;如果你觉得自己管理项目不善,很想改善你的项目管理能力,那么去考PMP肯定是远水不解近渴的。 1、《快速软件开发》 这也是一本名著。可以这样说,有本书在手,你就有了一个项目管理的高级参谋给你出谋划策,再也不必担心自己不能胜任的问题了。这本书不是讲管理的理论的,在实际的项目管理中,讲这些理论是不解决问题的,这本书有点类似于“软件项目点子大全”之类的东西,列举了种种软件项目当中面临的各种问题,以及应该如何解决问题的点子,你只需要稍加变通,找方抓药就行了。 六、总结 在这份推荐阅读书籍的名单中,我没有列举流行的软件框架类学习书籍,例如Struts,Hibernate,Spring之类,也没有列举AJAX方面的书籍。是因为这类书籍容易过时,而上述的大半书籍的生命周期都足够长,值得你去购买和收藏。 郝瑞——Java学习之路:不走弯路,就是捷径(转载)
0.引言 软件开发之路是充满荆棘与挑战之路,也是充满希望之路。Java学习也是如此,没有捷径可走。梦想像《天龙八部》中虚竹一样被无崖子醍醐灌顶而轻松获得一甲子功力,是很不现实的。每天仰天大叫"天神啊,请赐给我一本葵花宝典吧",殊不知即使你获得了葵花宝典,除了受自宫其身之苦外,你也不一定成得了"东方不败",倒是成"西方失败"的几率高一点。 "不走弯路,就是捷径",佛经说的不无道理。 1.如何学习程序设计? Java是一种平台,也是一种程序设计语言,如何学好程序设计不仅仅适用于Java,对C++等其他程序设计语言也一样管用。有编程高手认为,Java也好C也好没什么分别,拿来就用。为什么他们能达到如此境界?我想是因为编程语言之间有共通之处,领会了编程的精髓,自然能够做到一通百通。如何学习程序设计理所当然也有许多共通的地方。 1.1 培养兴趣 兴趣是能够让你坚持下去的动力。如果只是把写程序作为谋生的手段的话,你会活的很累,也太对不起自己了。多关心一些行业趣事,多想想盖茨。不是提倡天天做白日梦,但人要是没有了梦想,你觉得有味道吗?可能像许多深圳本地农民一样,打打麻将,喝喝功夫茶,拜拜财神爷;每个月就有几万十几万甚至更多的进帐,凭空多出个"食利阶层"。你认为,这样有味道吗?有空多到一些程序员论坛转转,你会发现,他们其实很乐观幽默,时不时会冒出智慧的火花。 1.2 慎选程序设计语言 男怕入错行,女怕嫁错郎。初学者选择程序设计语言需要谨慎对待。软件开发不仅仅是掌握一门编程语言了事,它还需要其他很多方面的背景知识。软件开发也不仅仅局限于某几个领域,而是已经渗透到了各行各业几乎每一个角落。 如果你对硬件比较感兴趣,你可以学习C语言/汇编语言,进入硬件开发领域。如果你对电信的行业知识及网络比较熟悉,你可以在C/C++等之上多花时间,以期进入电信软件开发领域。如果你对操作系统比较熟悉,你可以学习C/Linux等等,为Linux内核开发/驱动程序开发/嵌入式开发打基础。 如果你想介入到应用范围最广泛的应用软件开发(包括电子商务电子政务系统)的话,你可以选择J2EE或.NET,甚至LAMP组合。每个领域要求的背景知识不一样。做应用软件需要对数据库等很熟悉。总之,你需要根据自己的特点来选择合适你的编程语言。 1.3 要脚踏实地,快餐式的学习不可取 先分享一个故事。 有一个小朋友,他很喜欢研究生物学,很想知道那些蝴蝶如何从蛹壳里出来,变成蝴蝶便会飞。有一次,他走到草原上面看见一个蛹,便取了回家,然后看着,过了几天以后,这个蛹出了一条裂痕,看见里面的蝴蝶开始挣扎,想抓破蛹壳飞出来。 这个过程达数小时之久,蝴蝶在蛹里面很辛苦地拼命挣扎,怎么也没法子走出来。这个小孩看着看着不忍心,就想不如让我帮帮它吧,便随手拿起剪刀在蛹上剪开,使蝴蝶破蛹而出。 但蝴蝶出来以后,因为翅膀不够力,变得很臃肿,飞不起来。 这个故事给我们的启示是:欲速则不达。 浮躁是现代人最普遍的心态,能怪谁?也许是贫穷落后了这么多年的缘故,就像当年的大跃进一样,都想大步跨入***主义社会。现在的软件公司、客户、政府、学校、培训机构等等到处弥漫着浮躁之气。就拿我比较熟悉的大连大工IT职业培训来说吧,居然打广告宣称"20多年的计算机职业教育,辽宁省十佳学校",殊不知中国计算机发展才几年,软件发展才几年,居然去报名的学生不少,简直是藐视天下程序员。培训出来的“程序员”大多不知道OO,OP为何物?社会环境如是,我们不能改变,只能改变自己,闹市中的安宁,弥足珍贵。许多初学者C++/Java没开始学,立马使用VC/JBuilder,会使用VC/JBuilder开发一个Hello World程序,就忙不迭的向世界宣告,"我会软件开发了",简历上也大言不惭地写上"精通VC/Java"。结果到软件公司面试时要么被三两下打发走了,要么被驳的体无完肤,无地自容。到处碰壁之后才知道捧起《C++编程思想》《Java编程思想》仔细钻研,早知如此何必当初呀。 "你现在讲究简单方便,你以后的路就长了",好象也是佛经中的劝戒。 1.4 多实践,快实践 彭端淑的《为学一首示子侄》中有穷和尚与富和尚的故事。 从前,四川边境有两个和尚,一个贫穷,一个有钱。一天,穷和尚对富和尚说:"我打算去南海朝圣,你看怎么样?"富和尚说:"这里离南海有几千里远,你靠什么去呢?"穷和尚说:"我只要一个水钵,一个饭碗就够了。"富和尚为难地说:"几年前我就打算买条船去南海,可至今没去成,你还是别去吧!" 一年以后,富和尚还在为租赁船只筹钱,穷和尚却已经从南海朝圣回来了。 这个故事可解读为:任何事情,一旦考虑好了,就要马上上路,不要等到准备周全之后,再去干事情。假如事情准备考虑周全了再上路的话,别人恐怕捷足先登了。软件开发是一门工程学科,注重的就是实践,"君子动口不动手"对软件开发人员来讲根本就是错误的,他们提倡"动手至上",但别害怕,他们大多温文尔雅,没有暴力倾向,虽然有时候蓬头垢面的一副"比尔盖茨"样。有前辈高人认为,学习编程的秘诀是:编程、编程、再编程,笔者深表赞同。不仅要多实践,而且要快实践。我们在看书的时候,不要等到你完全理解了才动手敲代码,而是应该在看书的同时敲代码,程序运行的各种情况可以让你更快更牢固的掌握知识点。 1.5 多参考程序代码 程序代码是软件开发最重要的成果之一,其中渗透了程序员的思想与灵魂。许多人被《仙剑奇侠传》中凄美的爱情故事感动,悲剧的结局更有一种缺憾美。为什么要以悲剧结尾?据说是因为写《仙剑奇侠传》的程序员失恋而安排了这样的结局,他把自己的感觉融入到游戏中,却让众多的仙剑迷扼腕叹息。 多多参考代码例子,对Java而言有参考文献[4.3],有API类的源代码(JDK安装目录下的src.zip文件),也可以研究一些开源的软件或框架。 1.6 加强英文阅读能力 对学习编程来说,不要求英语, 但不能一点不会,。最起码像Java API文档(参考文献[4.4])这些东西还是要能看懂的,连猜带懵都可以;旁边再开启一个"金山词霸"。看多了就会越来越熟练。在学Java的同时学习英文,一箭双雕多好。另外好多软件需要到英文网站下载,你要能够找到它们,这些是最基本的要求。英语好对你学习有很大的帮助。口语好的话更有机会进入管理层,进而可以成为剥削程序员的"周扒皮"。 1.7 万不得已才请教别人 笔者在Martix与Java论坛的在线辅导系统中解决学生问题时发现,大部分的问题学生稍做思考就可以解决。请教别人之前,你应该先回答如下几个问题。 你是否在google中搜索了问题的解决办法? 你是否查看了Java API文档? 你是否查找过相关书籍? 你是否写代码测试过? 如果回答都是"是"的话,而且还没有找到解决办法,再问别人不迟。要知道独立思考的能力对你很重要。要知道程序员的时间是很宝贵的。 1.8 多读好书 书中自有颜如玉。比尔盖茨是一个饱读群书的人。虽然没有读完大学,但九岁的时候比尔盖茨就已经读完了所有的百科全书,所以他精通天文、历史、地理等等各类学科,可以说比尔?茨不仅是当今世界上金钱的首富,而且也可以称得上是知识的巨富。 笔者在给学生上课的时候经常会给他们推荐书籍,到后来学生实在忍无可忍开始抱怨,"天呐,这么多书到什么时候才能看完了","学软件开发,感觉上了贼船"。这时候,我的回答一般是,"别着急,什么时候带你们去看看我的书房,到现在每月花在技术书籍上的钱400元,这在软件开发人员之中还只能够算是中等的",学生当场晕倒。(注:这一部分学生是刚学软件开发的) 1.9 使用合适的工具 工欲善其事必先利其器。软件开发包含各种各样的活动,需求收集分析、建立用例模型、建立分析设计模型、编程实现、调试程序、自动化测试、持续集成等等,没有工具帮忙可以说是寸步难行。工具可以提高开发效率,使软件的质量更高BUG更少。组合称手的武器。到飞花摘叶皆可伤人的境界就很高了,无招胜有招,手中无剑心中有剑这样的境界几乎不可企及。 2.软件开发学习路线 两千多年的儒家思想孔孟之道,中庸的思想透入骨髓,既不冒进也不保守并非中庸之道,而是找寻学习软件开发的正确路线与规律。 从软件开发人员的生涯规划来讲,我们可以大致分为三个阶段,软件工程师→软件设计师→架构设计师或项目管理师。不想当元帅的士兵不是好士兵,不想当架构设计师或项目管理师的程序员也不是好的程序员。我们应该努力往上走。让我们先整理一下开发应用软件需要学习的主要技术。 A.基础理论知识,如操作系统、编译原理、数据结构与算法、计算机原理等,它们并非不重要。如不想成为计算机科学家的话,可以采取"用到的时候再来学"的原则。 B.一门编程语言,现在基本上都是面向对象的语言,Java/C++/C#等等。如果做WEB开发的话还要学习HTML/JavaScript等等。 C.一种方法学或者说思想,现在基本都是面向对象思想(OOA/OOD/设计模式)。由此而衍生的基于组件开发CBD/面向方面编程AOP等等。 D.一种关系型数据库,ORACLE/SqlServer/DB2/MySQL等等 E.一种提高生产率的IDE集成开发环境JBuilder/Eclipse/VS.NET等。 F.一种UML建模工具,用ROSE/VISIO/钢笔进行建模。 G.一种软件过程,RUP/XP/CMM等等,通过软件过程来组织软件开发的众多活动,使开发流程专业化规范化。当然还有其他的一些软件工程知识。 H.项目管理、体系结构、框架知识。 正确的路线应该是:B→C→E→F→G→H。 还需要补充几点: 1).对于A与C要补充的是,我们应该在实践中逐步领悟编程理论与编程思想。新技术虽然不断涌现,更新速度令人眼花燎乱雾里看花;但万变不离其宗,编程理论与编程思想的变化却很慢。掌握了编程理论与编程思想你就会有拨云见日之感。面向对象的思想在目前来讲是相当关键的,是强势技术之一,在上面需要多投入时间,给你的回报也会让你惊喜。 2).对于数据库来说是独立学习的,这个时机就由你来决定吧。 3).编程语言作为学习软件开发的主线,而其余的作为辅线。 4).软件工程师着重于B、C、E、D;软件设计师着重于B、C、E、D、F;架构设计师着重于C、F、H。 3.如何学习Java? 3.1 Java学习路线 3.1.1 基础语法及Java原理 基础语法和Java原理是地基,地基不牢靠,犹如沙地上建摩天大厦,是相当危险的。学习Java也是如此,必须要有扎实的基础,你才能在J2EE、J2ME领域游刃有余。参加SCJP(SUN公司认证的Java程序员)考试不失为一个好方法,原因之一是为了对得起你交的1200大洋考试费,你会更努力学习,原因之二是SCJP考试能够让你把基础打得很牢靠,它要求你跟JDK一样熟悉Java基础知识;但是你千万不要认为考过了SCJP就有多了不起,就能够获得软件公司的青睐,就能够获取高薪,这样的想法也是很危险的。获得"真正"的SCJP只能证明你的基础还过得去,但离实际开发还有很长的一段路要走。 3.1.2 OO思想的领悟 掌握了基础语法和Java程序运行原理后,我们就可以用Java语言实现面向对象的思想了。面向对象,是一种方法学;是独立于语言之外的编程思想;是CBD基于组件开发的基础;属于强势技术之一。当以后因工作需要转到别的面向对象语言的时候,你会感到特别的熟悉亲切,学起来像喝凉水这么简单。 使用面向对象的思想进行开发的基本过程是: ●调查收集需求。 ●建立用例模型。 ●从用例模型中识别分析类及类与类之间的静态动态关系,从而建立分析模型。 ●细化分析模型到设计模型。 ●用具体的技术去实现。 ●测试、部署、总结。 3.1.3 基本API的学习 进行软件开发的时候,并不是什么功能都需要我们去实现,也就是经典名言所说的"不需要重新发明轮子"。我们可以利用现成的类、组件、框架来搭建我们的应用,如SUN公司编写好了众多类实现一些底层功能,以及我们下载过来的JAR文件中包含的类,我们可以调用类中的方法来完成某些功能或继承它。那么这些类中究竟提供了哪些方法给我们使用?方法的参数个数及类型是?类的构造器需不需要参数?总不可能SUN公司的工程师打国际长途甚至飘洋过海来告诉你他编写的类该如何使用吧。他们只能提供文档给我们查看,Java DOC文档(参考文献4.4)就是这样的文档,它可以说是程序员与程序员交流的文档。 基本API指的是实现了一些底层功能的类,通用性较强的API,如字符串处理/输入输出等等。我们又把它成为类库。熟悉API的方法一是多查Java DOC文档(参考文献4.4),二是使用JBuilder/Eclipse等IDE的代码提示功能。 3.1.4 特定API的学习 Java介入的领域很广泛,不同的领域有不同的API,没有人熟悉所有的API,对一般人而言只是熟悉工作中要用到的API。如果你做界面开发,那么你需要学习Swing/AWT/SWT等API;如果你进行网络游戏开发,你需要深入了解网络API/多媒体API/2D3D等;如果你做WEB开发,就需要熟悉Servlet等API啦。总之,需要根据工作的需要或你的兴趣发展方向去选择学习特定的API。 3.1.5 开发工具的用法 在学习基础语法与基本的面向对象概念时,从锻炼语言熟练程度的角度考虑,我们推荐使用的工具是Editplus/JCreator+JDK,这时候不要急于上手JBuilder/Eclipse等集成开发环境,以免过于关注IDE的强大功能而分散对Java技术本身的注意力。过了这一阶段你就可以开始熟悉IDE了。 程序员日常工作包括很多活动,编辑、编译及构建、调试、单元测试、版本控制、维持模型与代码同步、文档的更新等等,几乎每一项活动都有专门的工具,如果独立使用这些工具的话,你将会很痛苦,你需要在堆满工具的任务栏上不断的切换,效率很低下,也很容易出错。在JBuilder、Eclipse等IDE中已经自动集成编辑器、编译器、调试器、单元测试工具JUnit、自动构建工具ANT、版本控制工具CVS、DOC文档生成与更新等等,甚至可以把UML建模工具也集成进去,又提供了丰富的向导帮助生成框架代码,让我们的开发变得更轻松。应该说IDE发展的趋势就是集成软件开发中要用到的几乎所有工具。 从开发效率的角度考虑,使用IDE是必经之路,也是从一个学生到一个职业程序员转变的里程碑。 Java开发使用的IDE主要有Eclipse、JBuilder、JDeveloper、NetBeans等几种;而Eclipse、JBuilder占有的市场份额是最大的。JBuilder在近几年来一直是Java集成开发环境中的霸主,它是由备受程序员尊敬的Borland公司开发,在硝烟弥漫的Java IDE大战中,以其快速的版本更新击败IBM的Visual Age for Java等而成就一番伟业。IBM在Visual Age for Java上已经无利可图之下,干脆将之贡献给开源社区,成为Eclipse的前身,真所谓"柳暗花明又一村"。浴火重生的Eclipse以其开放式的插件扩展机制、免费开源获得广大程序员(包括几乎所有的骨灰级程序员)的青睐,极具发展潜力。 3.1.6 学习软件工程 对小型项目而言,你可能认为软件工程没太大的必要。随着项目的复杂性越来越高,软件工程的必要性才会体现出来。参见"软件开发学习路线"小节。 3.2学习要点 确立的学习路线之后,我们还需要总结一下Java的学习要点,这些要点在前文多多少少提到过,只是笔者觉得这些地方特别要注意才对它们进行汇总,不要嫌我婆婆妈妈啊。 3.2.1勤查API文档 当程序员编写好某些类,觉得很有成就感,想把它贡献给各位苦难的同行。这时候你要使用"Javadoc"工具(包含在JDK中)生成标准的Java DOC文档,供同行使用。J2SE/J2EE/J2ME的DOC文档是程序员与程序员交流的工具,几乎人手一份,除了菜鸟之外。J2SE DOC文档官方下载地址:http://Java.sun.com/j2se/1.5.0/download.jsp,你可以到google搜索CHM版本下载。也可以在线查看:http://Java.sun.com/j2se/1.5.0/docs/api/index.html。 对待DOC文档要像毛主席语录,早上起床念一遍,吃饭睡觉前念一遍。 当需要某项功能的时候,你应该先查相应的DOC文档看看有没有现成的实现,有的话就不必劳神费心了直接用就可以了,找不到的时候才考虑自己实现。使用步骤一般如下: ●找特定的包,包一般根据功能组织。 ●找需要使用类,类命名规范的话我们由类的名字可猜出一二。 ●选择构造器,大多数使用类的方式是创建对象。 ●选择你需要的方法。 3.2.2 查书/google->写代码测试->查看源代码->请教别人 当我们遇到问题的时候该如何解决? 这时候不要急着问别人,太简单的问题,没经过思考的问题,别人会因此而瞧不起你。可以先找找书,到google中搜一下看看,绝大部分问题基本就解决了。而像"某些类/方法如何使用的问题",DOC文档就是答案。对某些知识点有疑惑是,写代码测试一下,会给你留下深刻的印象。而有的问题,你可能需要直接看API的源代码验证你的想法。万不得已才去请教别人。 3.2.3学习开源软件的设计思想 Java领域有许多源代码开放的工具、组件、框架,JUnit、ANT、Tomcat、Struts、Spring、Jive论坛、PetStore宠物店等等多如牛毛。这些可是前辈给我们留下的瑰宝呀。入宝山而空手归,你心甘吗?对这些工具、框架进行分析,领会其中的设计思想,有朝一日说不定你也能写一个XXX框架什么的,风光一把。分析开源软件其实是你提高技术、提高实战能力的便捷方法。 3.2.4 规范的重要性 没有规矩,不成方圆。这里的规范有两层含义。第一层含义是技术规范,多到http://www.jcp.org下载JSRXXX规范,多读规范,这是最权威准确最新的教材。第二层含义是编程规范,如果你使用了大量的独特算法,富有个性的变量及方法的命名方式;同时,没给程序作注释,以显示你的编程功底是多么的深厚。这样的代码别人看起来像天书,要理解谈何容易,更不用说维护了,必然会被无情地扫入垃圾堆。Java编码规范到此查看或下载http://Java.sun.com/docs/codeconv/,中文的也有,啊,还要问我在哪,请参考3.2.2节。 3.2.5 不局限于Java 很不幸,很幸运,要学习的东西还有很多。不幸的是因为要学的东西太多且多变,没时间陪老婆家人或女朋友,导致身心疲惫,严重者甚至导致抑郁症。幸运的是别人要抢你饭碗绝非易事,他们或她们需要付出很多才能达成心愿。 Java不要孤立地去学习,需要综合学习数据结构、OOP、软件工程、UML、网络编程、数据库技术等知识,用横向纵向的比较联想的方式去学习会更有效。如学习Java集合的时候找数据结构的书看看;学JDBC的时候复习数据库技术;采取的依然是"需要的时候再学"的原则。 4.结束语 需要强调的是,学习软件开发确实有一定的难度,也很辛苦,需要付出很多努力,但千万不要半途而废。本文如果能对一直徘徊在Java神殿之外的朋友有所帮助的话,笔者也欣慰了。哈哈,怎么听起来老气横秋呀?没办法,在电脑的长期辐射之下,都快变成小老头了。最后奉劝各位程序员尤其是MM程序员,完成工作后赶快远离电脑,据《胡播乱报》报道,电脑辐射会在白皙的皮肤上面点缀一些小黑点,看起来鲜艳无比…… (文章提到的大连大工IT培训。本人没有任何攻击的意思) April 24 内存管理内幕Jonathan Bartlett (johnnyb@eskimo.com), 技术总监, New Media Worx 2004 年 11 月 29 日 本文将对 Linux™ 程序员可以使用的内存管理技术进行概述,虽然关注的重点是 C 语言,但同样也适用于其他语言。文中将为您提供如何管理内存的细节,然后将进一步展示如何手工管理内存,如何使用引用计数或者内存池来半手工地管理内存,以及如何使用垃圾收集自动管理内存。 内存管理是计算机编程最为基本的领域之一。在很多脚本语言中,您不必担心内存是如何管理的,这并不能使得内存管理的重要性有一点点降低。对实际编程来说,理解您的内存管理器的能力与局限性至关重要。在大部分系统语言中,比如 C 和 C++,您必须进行内存管理。本文将介绍手工的、半手工的以及自动的内存管理实践的基本概念。 追溯到在 Apple II 上进行汇编语言编程的时代,那时内存管理还不是个大问题。您实际上在运行整个系统。系统有多少内存,您就有多少内存。您甚至不必费心思去弄明白它有多少内存,因为每一台机器的内存数量都相同。所以,如果内存需要非常固定,那么您只需要选择一个内存范围并使用它即可。 不过,即使是在这样一个简单的计算机中,您也会有问题,尤其是当您不知道程序的每个部分将需要多少内存时。如果您的空间有限,而内存需求是变化的,那么您需要一些方法来满足这些需求:
实现这些需求的程序库称为 分配程序(allocators),因为它们负责分配和回收内存。程序的动态性越强,内存管理就越重要,您的内存分配程序的选择也就更重要。让我们来了解可用于内存管理的不同方法,它们的好处与不足,以及它们最适用的情形。
C 编程语言提供了两个函数来满足我们的三个需求:
要理解内存在程序中是如何分配的,首先需要理解如何将内存从操作系统分配给程序。计算机上的每一个进程都认为自己可以访问所有的物理内存。显然,由于同时在运行多个程序,所以每个进程不可能拥有全部内存。实际上,这些进程使用的是 虚拟内存。 只是作为一个例子,让我们假定您的程序正在访问地址为 629 的内存。不过,虚拟内存系统不需要将其存储在位置为 629 的 RAM 中。实际上,它甚至可以不在 RAM 中 —— 如果物理 RAM 已经满了,它甚至可能已经被转移到硬盘上!由于这类地址不必反映内存所在的物理位置,所以它们被称为虚拟内存。操作系统维持着一个虚拟地址到物理地址的转换的表,以便计算机硬件可以正确地响应地址请求。并且,如果地址在硬盘上而不是在 RAM 中,那么操作系统将暂时停止您的进程,将其他内存转存到硬盘中,从硬盘上加载被请求的内存,然后再重新启动您的进程。这样,每个进程都获得了自己可以使用的地址空间,可以访问比您物理上安装的内存更多的内存。 在 32-位 x86 系统上,每一个进程可以访问 4 GB 内存。现在,大部分人的系统上并没有 4 GB 内存,即使您将 swap 也算上, 每个进程所使用的内存也肯定少于 4 GB。因此,当加载一个进程时,它会得到一个取决于某个称为 系统中断点(system break)的特定地址的初始内存分配。该地址之后是未被映射的内存 —— 用于在 RAM 或者硬盘中没有分配相应物理位置的内存。因此,如果一个进程运行超出了它初始分配的内存,那么它必须请求操作系统“映射进来(map in)”更多的内存。(映射是一个表示一一对应关系的数学术语 —— 当内存的虚拟地址有一个对应的物理地址来存储内存内容时,该内存将被映射。) 基于 UNIX 的系统有两个可映射到附加内存中的基本系统调用:
如您所见, 如果您曾经编写过很多 C 程序,那么您可能曾多次使用过 要试着运行这些示例,需要先 复制本代码清单,并将其粘贴到一个名为 malloc.c 的文件中。接下来,我将一次一个部分地对该清单进行解释。 在大部分操作系统中,内存分配由以下两个简单的函数来处理:
如前所述,被映射的内存的边界(最后一个有效地址)常被称为系统中断点或者 当前中断点。在很多 UNIX® 系统中,为了指出当前系统中断点,必须使用
现在,为了完全地管理内存,我们需要能够追踪要分配和回收哪些内存。在对内存块进行了
现在,您可能会认为当程序调用 在讨论分配内存之前,我们将先讨论释放,因为它更简单。为了释放内存,我们必须要做的惟一一件事情就是,获得我们给出的指针,回退
如您所见,在这个分配程序中,内存的释放使用了一个非常简单的机制,在固定时间内完成内存释放。分配内存稍微困难一些。以下是该算法的略述:
我们主要使用连接的指针遍历内存来寻找开放的内存块。这里是代码:
这就是我们的内存管理器。现在,我们只需要构建它,并在程序中使用它即可。 运行下面的命令来构建
该程序将生成一个名为 malloc.so 的文件,它是一个包含有我们的代码的共享库。 在 UNIX 系统中,现在您可以用您的分配程序来取代系统的
如果您想确保 我们的内存管理器在很多方面都还存在欠缺,但它可以有效地展示内存管理需要做什么事情。它的某些缺点包括:
每一个实现都有其自身的优缺点集合。在我们的简单的分配程序中,分配非常慢,而回收非常快。另外,由于它在使用虚拟内存系统方面较差,所以它最适于处理大的对象。 还有其他许多分配程序可以使用。其中包括:
众多可用的分配程序中最有名的就是上述这些分配程序。如果您的程序有特别的分配需求,那么您可能更愿意编写一个定制的能匹配您的程序内存分配方式的分配程序。不过,如果不熟悉分配程序的设计,那么定制分配程序通常会带来比它们解决的问题更多的问题。要获得关于该主题的适当的介绍,请参阅 Donald Knuth 撰写的 The Art of Computer Programming Volume 1: Fundamental Algorithms 中的第 2.5 节“Dynamic Storage Allocation”(请参阅 参考资料中的链接)。它有点过时,因为它没有考虑虚拟内存环境,不过大部分算法都是基于前面给出的函数。 在 C++ 中,通过重载 不只是我们的内存管理器有缺点,基于 因为管理内存的问题,很多程序倾向于使用它们自己的内存管理规则。C++ 的异常处理使得这项任务更成问题。有时好像致力于管理内存分配和清理的代码比实际完成计算任务的代码还要多!因此,我们将研究内存管理的其他选择。
引用计数是一种 半自动(semi-automated)的内存管理技术,这表示它需要一些编程支持,但是它不需要您确切知道某一对象何时不再被使用。引用计数机制为您完成内存管理任务。 在引用计数中,所有共享的数据结构都有一个域来包含当前活动“引用”结构的次数。当向一个程序传递一个指向某个数据结构指针时,该程序会将引用计数增加 1。实质上,您是在告诉数据结构,它正在被存储在多少个位置上。然后,当您的进程完成对它的使用后,该程序就会将引用计数减少 1。结束这个动作之后,它还会检查计数是否已经减到零。如果是,那么它将释放内存。 这样做的好处是,您不必追踪程序中某个给定的数据结构可能会遵循的每一条路径。每次对其局部的引用,都将导致计数的适当增加或减少。这样可以防止在使用数据结构时释放该结构。不过,当您使用某个采用引用计数的数据结构时,您必须记得运行引用计数函数。另外,内置函数和第三方的库不会知道或者可以使用您的引用计数机制。引用计数也难以处理发生循环引用的数据结构。 要实现引用计数,您只需要两个函数 —— 一个增加引用计数,一个减少引用计数并当计数减少到零时释放内存。 一个示例引用计数函数集可能看起来如下所示:
当使用
在传递使用引用计数的结构的函数中,函数需要遵循以下这些规则:
以下是一个使用引用计数的生动的代码示例:
由于引用计数是如此简单,大部分程序员都自已去实现它,而不是使用库。不过,它们依赖于 在 Perl 等高级语言中,进行内存管理时使用引用计数非常广泛。在这些语言中,引用计数由语言自动地处理,所以您根本不必担心它,除非要编写扩展模块。由于所有内容都必须进行引用计数,所以这会对速度产生一些影响,但它极大地提高了编程的安全性和方便性。以下是引用计数的益处:
不过,它也有其不足之处:
C++ 可以通过使用 智能指针(smart pointers)来容忍程序员所犯的一些错误,智能指针可以为您处理引用计数等指针处理细节。不过,如果不得不使用任何先前的不能处理智能指针的代码(比如对 C 库的联接),实际上,使用它们的后果通实比不使用它们更为困难和复杂。因此,它通常只是有益于纯 C++ 项目。如果您想使用智能指针,那么您实在应该去阅读 Alexandrescu 撰写的 Modern C++ Design 一书中的“Smart Pointers”那一章。 内存池是另一种半自动内存管理方法。内存池帮助某些程序进行自动内存管理,这些程序会经历一些特定的阶段,而且每个阶段中都有分配给进程的特定阶段的内存。例如,很多网络服务器进程都会分配很多针对每个连接的内存 —— 内存的最大生存期限为当前连接的存在期。Apache 使用了池式内存(pooled memory),将其连接拆分为各个阶段,每个阶段都有自己的内存池。在结束每个阶段时,会一次释放所有内存。 在池式内存管理中,每次内存分配都会指定内存池,从中分配内存。每个内存池都有不同的生存期限。在 Apache 中,有一个持续时间为服务器存在期的内存池,还有一个持续时间为连接的存在期的内存池,以及一个持续时间为请求的存在期的池,另外还有其他一些内存池。因此,如果我的一系列函数不会生成比连接持续时间更长的数据,那么我就可以完全从连接池中分配内存,并知道在连接结束时,这些内存会被自动释放。另外,有一些实现允许注册 清除函数(cleanup functions),在清除内存池之前,恰好可以调用它,来完成在内存被清理前需要完成的其他所有任务(类似于面向对象中的析构函数)。 要在自己的程序中使用池,您既可以使用 GNU libc 的 obstack 实现,也可以使用 Apache 的 Apache Portable Runtime。GNU obstack 的好处在于,基于 GNU 的 Linux 发行版本中默认会包括它们。Apache Portable Runtime 的好处在于它有很多其他工具,可以处理编写多平台服务器软件所有方面的事情。要深入了解 GNU obstack 和 Apache 的池式内存实现,请参阅 参考资料部分中指向这些实现的文档的链接。 下面的假想代码列表展示了如何使用 obstack:
基本上,在操作的每一个主要阶段结束之后,这个阶段的 obstack 会被释放。不过,要注意的是,如果一个过程需要分配持续时间比当前阶段更长的内存,那么它也可以使用更长期限的 obstack,比如连接或者全局内存。传递给 使用池式内存分配的益处如下所示:
池式内存的缺点是:
垃圾收集(Garbage collection)是全自动地检测并移除不再使用的数据对象。垃圾收集器通常会在当可用内存减少到少于一个具体的阈值时运行。通常,它们以程序所知的可用的一组“基本”数据 —— 栈数据、全局变量、寄存器 —— 作为出发点。然后它们尝试去追踪通过这些数据连接到每一块数据。收集器找到的都是有用的数据;它没有找到的就是垃圾,可以被销毁并重新使用这些无用的数据。为了有效地管理内存,很多类型的垃圾收集器都需要知道数据结构内部指针的规划,所以,为了正确运行垃圾收集器,它们必须是语言本身的一部分。
Hans Boehm 的保守垃圾收集器是可用的最流行的垃圾收集器之一,因为它是免费的,而且既是保守的又是增量的,可以使用 垃圾收集的一些优点:
其缺点包括:
一切都需要折衷:性能、易用、易于实现、支持线程的能力等,这里只列出了其中的一些。为了满足项目的要求,有很多内存管理模式可以供您使用。每种模式都有大量的实现,各有其优缺点。对很多项目来说,使用编程环境默认的技术就足够了,不过,当您的项目有特殊的需要时,了解可用的选择将会有帮助。下表对比了本文中涉及的内存管理策略。
Web 上的文档
April 18 java像素级图像处理与识别方法转载,挺不错的文章
朋友要求帮忙做一个图片识别的小程序,因为要用在特定的环境下,所以决定采用java语言实现。首先用matlab实现了识别算法的仿真,因为只是对特定的数字组合的识别,所以非常的简单,放弃采用比较复杂的识别算法,采用最普通的像素比较的识别算法。(如果背景噪声比较复杂,可以考虑先滤波后识别)在写java程序的时候发现一些问题,网上关于图片像素级操作的资料不是太多,有的还不是太正确,特此写出自己的成果与大家分享。 源代码 import java.awt.*; private int iw,ih;//图像宽和高 public final static String path="D:\\jyy\\app\\tomcat\\webapps\\userlogon\\a.jpg"; static public void main(String args[]) { //构造函数 for(int k=0;k<4;k++){ x[k]=0; ImageIO.write(newim[k], "JPEG", new File("C:\\test"+k+".gif")); //因为是二值图像,这里的方法将像素读取出来的同时,转换为0,1的图像数组。 } } //图像像素相减取绝对值得到最小熵的结果。 } April 09 随机整数的生成过程 使用Java 2 SDK基础类库产生随机数的方法很多。但是如果你跟不上这些类库的更新脚步,你有可能正在使用的是一种低效的随机数生成机制,更糟糕的是:你有可能得到的不是均匀分布的随机数。本文将向你展示一种较为可靠的随机数生成方法,同时与其他方法进行比较。 自从JDK最初版本发布起,我们就可以使用java.util.Random类产生随机数了。在JDK1.2中,Random类有了一个名为nextInt()的方法: public int nextInt(int n) 给定一个参数n,nextInt(n)将返回一个大于等于0小于n的随机数,即:0 <= nextInt(n) < n。 你所要做的就是先声明一个Random的对象,在调用其nextInt(n)函数以返回随机值。 这里有个示例,下面的代码段将生成很多随机数并输出它们的平均值:
执行了1000000次循环之后,得到的平均值基本上就处于随机数范围的中点(midpoint)。 到目前为止,事情还并不复杂,但是我们会问为什么要使用nextInt(n)?考虑一下的随机数生成方法: (1)使用老的方法nextInt(),没有制定数值范围 (2)用Math.abs()静态函数得到(1)中产生值的绝对值 (3)对(2)的结果进行取模运算(%),得到期望范围类的值 我们说nextInt(n)要比上述方法更好,为什么呢?参考以下的代码段:
不难发现,每次循环都多出了几步运算。事实上,这种随机数生成的方法存在着以下三个问题: 首先,nextInt()返回的值是趋于均匀分布在Integer.MIN_VALUE 和 Integer.MAX_VALUE之间的。如果你取Integer.MIN_VALUE的绝对值,得到的仍然不是一个正数。事实上,Math.abs(Integer.MIN_VALUE)等于Integer.MIN_VALUE。因此,存在着这样一种情况(虽然很少见):rand.nextInt()=Integer.MIN_VALUE,经过取绝对值Math.abs(rand.nextInt())之后,得到是一个负数。这种几率为 1/(2^31),在我们的测试中不太可能发生——循环次数只有1000000次。 其次,当你对nextInt()取模时,你使结果的随机性大打折扣。随机数中较小的值出现的几率更大一些。这就是众所周知的伪随机数生成,因此我们不是用取模的方法。 最后,也可能是最糟糕的:随机数不是均匀分布。如果你执行了上述的两段代码,第一段代码的结果将会大于715,000,000,考虑到数值范围的中点(midpoint)是715,827,882,所以这是一个可以接受的结果。然而,你会吃惊的发现第二段代码得到的平均值肯定不会超过600,000,000。 为何第二段代码的结果会如此的偏差?纠其本质,问题出在数值分布的不均匀。当你进行取模运算时,你将过大的数转换成了较小的。这使得较小的数更容易产生。 使用nextInt(range)将会解决上述的三个问题。 还有一种随机数生成方法——使用Math.random()。这个方法的效果如何?
java中LinkedList 集合类实现栈和队列 栈和队列是两种特殊的线性表,它们的逻辑结构和线性表相同,只是其运算规则较线性表有更多的限制,故又称它们为运算受限的线性表。 LinkedList数据结构是一种双向的链式结构,每一个对象除了数据本身外,还有两个引用,分别指向前一个元素和后一个元素,和数组的顺序存储结构(如:ArrayList)相比,插入和删除比较方便,但速度会慢一些。 栈的定义 栈(Stack)是限制仅在表的一端进行插入和删除运算的线性表。 (1)通常称插入、删除的这一端为栈顶(Top),另一端称为栈底(Bottom)。 (2)当表中没有元素时称为空栈。 (3)栈为后进先出(Last In First Out)的线性表,简称为LIFO表。 栈的修改是按后进先出的原则进行。每次删除(退栈)的总是当前栈中"最新"的元素,即最后插入(进栈)的元素,而最先插入的是被放在栈的底部,要到最后才能删除。 实现代码: package com.gc.list; import java.util.*; public class MyStack { private LinkedList ll=new LinkedList(); private LinkedList ll=new LinkedList(); March 22 了解Java多线程编程之不提倡的方法不提倡使用的方法是为支持向后兼容性而保留的那些方法,它们在以后的版本中可能出现,也可能不出现。Java 多线程支持在版本 1.1 和版本 1.2 中做了重大修订,stop()、suspend() 和 resume() 函数已不提倡使用。这些函数在 JVM 中可能引入微妙的错误。虽然函数名可能听起来很诱人,但请抵制诱惑不要使用它们。 调试线程化的程序 在线程化的程序中,可能发生的某些常见而讨厌的情况是死锁、活锁、内存损坏和资源耗尽。 死锁 死锁可能是多线程程序最常见的问题。当一个线程需要一个资源而另一个线程持有该资源的锁时,就会发生死锁。这种情况通常很难检测。但是,解决方 案却相当好:在所有的线程中按相同的次序获取所有资源锁。例如,如果有四个资源 —A、B、C 和 D — 并且一个线程可能要获取四个资源中任何一个资源的锁,则请确保在获取对 B 的锁之前首先获取对 A 的锁,依此类推。如果“线程 1”希望获取对 B 和 C 的锁,而“线程 2”获取了 A、C 和 D 的锁,则这一技术可能导致阻塞,但它永远不会在这四个锁上造成死锁。 活锁 当一个线程忙于接受新任务以致它永远没有机会完成任何任务时,就会发生活锁。这个线程最终将超出缓冲区并导致程序崩溃。试想一个秘书需要录入一封信,但她一直在忙于接电话,所以这封信永远不会被录入。 内存损坏 如果明智地使用 synchronized 关键字,则完全可以避免内存错误这种气死人的问题。 资源耗尽 某些系统资源是有限的,如文件描述符。多线程程序可能耗尽资源,因为每个线程都可能希望有一个这样的资源。如果线程数相当大,或者某个资源的侯 选线程数远远超过了可用的资源数,则最好使用 资源池。一个最好的示例是数据库连接池。只要线程需要使用一个数据库连接,它就从池中取出一个,使用以后再将它返回池中。资源池也称为 资源库。 调试大量的线程 有时一个程序因为有大量的线程在运行而极难调试。在这种情况下,下面的这个类可能会派上用场:
Java极度性能调整有很多介绍基本的Java应用性能调整的文章。他们都讨论些简单的技术,诸如使用StringBuffer而不用String,使用synchronized关键字的开销等等。 这篇文章不再介绍这些东西。相反,我们关注能帮助你的基于Web的应用更快、可升级型更好的技巧。一些技巧很详细,其他的相对简短,但所有的都很有用。最后以一些你可提供给你的管理者的建议结束。 我写这篇文章的灵感来自于当我的同事和我一起回忆我们的.com(dot-com)时代的时候——我们如何设计能支持成千上万的用户和拥有紧密 代码的系统,我们如何对有侵略性的致命打击。有时在为复用设计和为性能设计之间有一个权衡。基于我的情况,性能每次都获胜。即使你的商务顾客无需理解代码 复用,但是他们知道快速(fast-performing)的系统是怎么回事。让我们开始看看我们的技巧。 如何使用Exception Exception降低性能。一个异常抛出首先需要创建一个新的对象。Throwable接口中的构造器调用名为 fillInStackTrace()的本地方法。这个方法负责巡检栈的整个框架来收集跟踪信息。这样无论何时有异常抛出,它要求虚拟机装载调用栈,因为 一个新的对象在中部被创建。 异常应当仅用于有错误发生时,而不要控制流。 我有机会在一个专门用于无线内容市场的网站(名字故意隐去了)看到一段代码,其中开发者完全可以使用一个简单的对照来查看对象是否为空。相反,他或她跳过了这个检查而实际上抛出Null-PointerException。 不要两次初始化变量 Java通 过调用独特的类构造器默认地初始化变量为一个已知的值。所有的对象被设置成null,integers (byte, short, int, long)被设置成0,float和double设置成0.0,Boolean变量设置成false。这对那些扩展自其它类的类尤其重要,这跟使用一个新 的关键词创建一个对象时所有一连串的构造器被自动调用一样。 对新的关键词使用优选法则 正如前面提到的,通过使用一个新的关键词创建一个类的实例,在这个链中的所有构造器将被调用。如果你需要创建一个类的新实例,你可以使用一个实现了cloneable接口的对象的clone()方法。该clone方法不调用任何类的构造器。 如果你已经使用了设计模式作为你的体系结构的一部分,并且使用了工厂模式创建对象,变化会很简单。下面所列是工厂模式的典型实现。
使用了clone方法的refactored代码看起来可能像下面这样:
以上的思路对实现数组同样有用。 如果你在应用中没有使用设计模式,我建议你停止读这篇文章,赶快跑到(不要走)书店挑一本四人著的《设计模式》。 在任何可能的地方让类为Final 标记为final的类不能被扩展。在《核心Java API》中有大量这个技术的例子,诸如java.lang.String。将String类标记为final阻止了开发者创建他们自己实现的长度方法。 更深入点说,如果类是final的,所有类的方法也是final的。Java编译器可能会内联所有的方法(这依赖于编译器的实现)。在我的测试里,我已经看到性能平均增加了50%。 在任何可能的地方使用局部变量 属于方法调用部分的自变量和声明为此调用一部分的临时变量存储在栈中,这比较快。诸如static,实例(instance)变量和新的对象创建在堆中,这比较慢。局部变量的更深入优化依赖于你正在使用的编译器或虚拟机。 使用Nonblocking I/O 当前的JDK版本不支持nonblocking I/O API,很多应用试图通过创建大量的线程(目光长远得用在池中)来避免阻塞。正如前述,在Java中创建线程有严重的开销。 典型的你可能看到应用中实现的线程需要支持并发I/O流,像Web 服务器,并quote and auction components. JDK1.4介绍了一个nonblocking I/O包(java.nio)。如果你必须保留在较早版本的JDK,有添加了支持nonblocking I/O的第三方包。 :www.cs.berkeley.edu/~mdw/proj/java-nbio/download.html. 停止小聪明 很多开发人员在脑子中编写可复用和灵活的代码,而有时候在他们的程序中就产生额外的开销。曾经或者另外的时候他们编写了类似这样的代码:
他够灵活,但是同时他们也产生了更多的开销。这个主意背后做的事情是操纵一个InputStream,而不是一个文件,因此它应该重写如下:
乘法和除法 我有太多的东东适用于摩尔法则——它声明CPU功率每年成倍增长。"摩尔法则"表明每年由开发者所写的差劲的代码数量三倍增加,划去了摩尔法则的任何好处。 考虑下面的代码: for (val = 0; val < 100000; val +=5) { shiftX = val * 8; myRaise = val * 2; } 如果我们狡猾的利用位移(bit),性能将会六倍增加。这是重写的代码: for (val = 0; val < 100000; val += 5) { shiftX = val << 3; myRaise = val << 1; } 代替了乘以8,我们使用同等效果的左移3位。每一个移动相当于乘以2,变量myRaise对此做了证明。同样向右移位相当于除以2,当然这会使执行速度加快,但可能会使你的东东以后难于理解;所以这只是个建议。 选择一个基于垃圾收集实现的虚拟机 许多人可能会对Java规范不需要实现垃圾收集感到惊讶。设想时代已经是我们都拥有无限内存计算机。总之,垃圾收集器日常事务就是负责发现和抛 出(hence garbage)不再需要的对象。垃圾收集必须发现那些对象不再被程序指向,并且使被对象占用的栈内存被释放掉。它还负责运行任何被释放对象的 finalizer。 垃圾收集故意不允许你释放并非由你分配的内存,从而帮助你确保程序完整,当JVM确定CPU时间的时间表并且当垃圾收集器运行时,这个进程也产生开销。 垃圾收集器有两个不同的步骤执行他们的工作。 实现了定位计算的垃圾收集器在栈中为每一个对象保留一个计数。当一个对象被创建并且对它的一个定位被分配给一个变量,计数增加。当对象越出范 围,定位计数被设置成0并且对象可以被垃圾收集。这个步骤允许参考计数器运行在与程序执行有关的短时间增量内。定位计数在父子彼此拥有定位的应用里运行不 正常。每次一个对象刷新时也会有定位计数增加和减少的开销。 实现了跟踪的垃圾收集器从根节点开始跟踪一列定位。对象发现跟踪是否被标记。在这个过程完成后,知道不可达的任何没标记的对象可以被垃圾收集。 这可能以位图(bitmap)形式实现或者在对象中被设置标志。此技术参考"Mark and Sweep."(reference:定位,翻译成“指向”好像更容易理解,是Java语言对在用对象的一个跟踪指针。译者著) 给你的管理人员提建议 其他方法可被用来使你的基于Web的应用更快并且更可升级。可实现的最简单的技术通常是支持cluster的策略。使用cluster,一组服务器能够一起透明的提供服务。多数应用服务器允许你获得cluster支持而不需要改变你的应用——一个大的胜利。 当然在执行此步骤之前你可能需要考虑来自你使用的应用服务器提供商附加的许可权利。 当看到cluster策略会有许多额外的事情考虑。经常在体系结构中产生的一个缺点是拥有有状态会话。如果cluster中的一个服务器或者进 程当掉,cluster会舍弃整个应用。为防止此类事情发生,cluster必须给cluster中的所有成员不断复制会话Bean的状态。确保你也限制 了存储在会话中的对象的大小和数量,因为这些也需要被复制。 Cluster也允许你分期度量你的Web站点的部分。如果你需要度量静态部分,你可以添加Web服务器。如果你需要度量动态生成的部分,你可以添加应用服务器。 在你已经把你的系统放入cluster后,下一个让你的应用跑得更快的建议步骤是选择一个更好的虚拟机。看看Hotspot虚拟机或者其他的飞速发展中的执行优化的虚拟机。随同虚拟机,看看更好的编译器是一个更好的主意。 如果你使用了几个这儿提到的行业技术插件,并且仍然不能获得你要的可升级性和高可用性,那么我建议一个可靠的调试策略。策略的第一步是为可能的瓶颈检查整个体系结构。通常,这在你的作为单线程组件或者有很多辅助连接线组件的UML流图中很容易识别出来。 最后的步骤是产生一个整个代码的详细性能估价。 确保你的管理人员至少为此安排了整个项目时间的20%;否则不足的时间可能不止危及你整个成功的安全,还会导致你向系统引入新的缺点。 许多组织者在适当的位置没有严格意义的测试基础而归咎于成本考虑也是错误的。确保你的QA环境真实反映你的生产环境,并且你的QA测试考虑以不同的负载测试应用,包括在最大的预期并发用户时一个基于低负载和一个完全负载的测试。 性能测试,有时测试一个系统的稳定性,可能需要在每天,甚至每周的整个时期的不同关节都运行。 关于JAVA内存泄漏问题注意事项Java的一个重要优点就是通过垃圾收集器(Garbage Collection,GC)自动管理内存的回收,程序员不需要通过调用函数来释放内存。因此,很多程序员认为Java不存在内存泄漏问题,或者认为即使有内存泄漏也不是程序的责任,而是GC或 JVM的问题。其实,这种想法是不正确的,因为Java也存在内存泄露,但它的表现与C++不同。 在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点,首先,这些对象是可达的,即在有向图中,存在通路可以与其相连;其 次,这些对象是无用的,即程序以后不会再使用这些对象。如果对象满足这两个条件,这些对象就可以判定为Java中的内存泄漏,这些对象不会被GC所回收, 然而它却占用内存。 在C++中,内存泄漏的范围更大一些。有些对象被分配了内存空间,然后却不可达,由于C++中没有GC,这些内存将永远收不回来。在Java中,这些不可达的对象都由GC负责回收,因此不需要考虑这部分的内存泄露。 注意事项: 1.最基本的建议是尽早释放无用对象的引用。如: …… A a = new A(); //应用a对象 a = null; //当使用对象a之后主动将其设置为空 …。 注:如果a 是方法的返回值,不要做这样的处理,否则你从该方法中得到的返回值永远为空,而且这种错误不易被发现、排除 2.尽量少用finalize函数。它会加大GC的工作量。 3.如果需要使用经常用到的图片,可以使用soft应用类型。它尽可能把图片保存在内存中 4.注意集合数据类型,包括数组、树、图、链表等数据结构,这些数据结构对GC来说,回收更为复杂。 5.尽量避免在类的默认构造器中创建、初始化大量的对象,防止在调用其自类的构造器时造成不必要的内存资源浪费 6.尽量避免强制系统做垃圾内存的回收,增长系统做垃圾回收的最终时间 7.尽量避免显式申请数组空间 8.尽量做远程方法调用类应用开发时使用瞬间值变量,除非远程调用端需要获取该瞬间值变量的值。 9.尽量在合适的场景下使用对象池技术以提高系统性能。 Java入门--讨论String中三种加法的区别String中三种加法的区别 JAVA的确是一种令程序员陷入两难境地的言语, 确切的说是思想. 它提供了如此丰富的library,让程序员能够很容易的写出功能强大的程序. 同时它也封装了如此多的细节, 让程序员能够轻易的写出很拙略的程序. 它所代表的object指向思想设计之初是为了把程序员从纷繁复杂的编程细节中解放出来,来达到对已有程序的利用. 但是在编程 实际上又要求程序员了解其实现的细节而避免写出因调用已有library不正引起的performance慢的,耗费系统资源多的程序.通常成为一个优秀 的JAVA程序员需要较长时间的经验积累,包括从程序的tuning中,或从其他有经验的程序员口中,才知道一定功能需要怎样实现,在程序中需要避免那些 问题.但这往往是比较片面的,知其然而不知其所以然.我想大多数程序员都有类似的经历,举一个简单的例子,下面有三种对String的累加操作. ① String tmp = "a" + "b" + "c"; 有些JAVA程序员在任意的用这三种方法的任一种,无视它们的区别.有 些程序员知道第三种方法好一直在用而不知其为什么好,以至于作为 经验教条的传授给JAVA新手.真正的答案是什么呢?让我们揭开JAVA String Class和StringBuffer Class的封装面纱看看它的内部实现.在JAVA中的String Class是一个不可变类,所有对一个Sting Object的改变都回导致一个新的String Object的生成.那么对tmp+=a中+符号的实现呢?如果你注意一下StringBuffer的Javadoc 会发现.JDK对它的实现是tmp=(new StringBuffer().append(tmp).append("a")).toSting(); 这样我们发现②这种方法在隐性生成了一个StringBuffer Object和一个String Object 再乘3就是六个object 的资源耗费.(还不包括String Class 和StringBuffer Class内部使用的Char[])而方法③只用了两个.也许有些程序员会对这些耗费不以为然,的确也是,不过在一些场景下它会成为perfamence 的瓶颈.再回头看看①这种方法,它会被JAVA编译器编译为tmp=(new StringBuffer().append("a").append("b").append("c")).to String();我们发现做的和③方法是同样的事. 基础:Java中两个特殊变量this和super在Java中 有两个非常特殊的变量:this和super,这两个变量在使用前都是不需要声明的。this变量使用在一个成员函数的内部,指向当前对象,当前对象指的 是调用当前正在执行方法的那个对象。super变量是直接指向超类的构造函数,用来引用超类中的变量和方法。因此它们都是非常有用的变量,下面我想介绍一 下this和super的使用方法。 1、this 让我们先看一段代码吧:
你会发现,在PersonInformation()函数中这个对象的方法提示可以直接访问对象的成员变量的,而且在同一个范围中,定义两个相同的名字的 局部变量是不允许的,如果确实想使类的成员变量和方法的参数或方法自己定义的局部变量同名的话就需要想一种方法使成员变量与更它同名的方法参数或局部变量 区分开来,这就要使用到this变量。下面我想改写一下上面的代码,使PersonInformation类的构造函数的每个参数都有与对象成员变量相同 的名字,而成员变量的初值由参数给出。
由上一例中,我们可以看出,该构造函数中必须使用this,this在方法体重用来指向引用当前正在执行方法的那个对象实例,this变量的类型总是为包 含前执行方法的类,上例中,我们要区别参数name和成员变量name,写成name=name显然是不允许的,在参数或局部变量名与类成员变量同名的时 候,由于参数或局部变量的优先级高,这样在方法体中参数名或局部变量名将隐藏同名的成员变量,因此,为了值名成员变量,你必须使用this显示地指明当前 对象。 有时候会遇到这种情况,我们全面的访问当前对象,而不是访问某一个个别的实例对象,我们也可以使用this,并利用Java中 的toString()方法(它能够返回一个描述这个对象的字符串)如果把任何一个对象传递到System.out.println方法中,这个方法调用 这个对象的toString方法,并打印出结果字符串,所以,我们可以用如下方法System.out.println(this),来打印出任何对方法 固有参数的当前状态。 this还有一个用法,就是构造函数的第一个语句,它的形式是this(参数表),这个构造函数就会调用同一个类的另一个相对的构造函数。请看下面的例子:
如果你调用UserInfor newinfotable = new UserInfo("aaron")后,就会自动调用UserInfo(String name,int number)构造函数。 可见,熟练掌握this在Java程序设计过程中是非常重要的 2、super 在Java中,有时还会遇到子类中的成员变量或方法与超类(有时也称父类)中的成员变量或方法同名,因为子类中的成员变量或方法名优先级高,所 以子类中的同名成员变量和方法就隐藏了超类的成员变量或方法,但是我们如果想要使用超类中的这个成员变量或方法,此使就需要用到super,请看下面的 类。
为了在子类中引用超类中的成员变量name和方法value(),在代码中我们使用了super,super.name和super.value(); 所以显示的结果为 Beijing China 如果我们想要使用超类的构造函数则应当使用super(参数列表)的形式。 告诉你怎样学Java才是硬道理(转贴)这是我在网上看见的一篇文章,觉得说的很有道理,就转贴过来了. 告诉你怎样学Java才是硬道理 做软件开发大概有6年多了,从事JAVA开发大概4年多, 第二、我觉得你应该考虑你是为什么学习软件的。我比较俗气,我是为了更好的待遇才学习软件的。我以前是VB程序员,为什么要学习JAVA,我就
因为当时认为JAVA有钱途(本人绝对没有说VB没有钱途的意思,只要你厉害是一样的,我的意思是平行对比,一个不怎么厉害的JAVA程序员就可以拿到够
高的工资,前几年确实是这样的)本人现在在一家民营企业上班,待遇是10K/月实在惭愧的是工作了这么多年外语还是很烂,如果外语很好,我估计工资提高一
半应该是没有问题的。和我同时来上海 同时进同一个公司的的同事现在一般是年薪200K,他们的优势在于外语。在上海一般工作3年的JAVA程序员的待遇
应该在6K/月,5年以上应该年薪100K是没有问题的,这个待遇不是只外语很好的人,也不是指特别厉害的人我说的只是大众化 正常化的(外语可以是日语
和英语不要说学日语是汉****,这只能说明你浅薄)如果你现在的待遇还达不到我说的,那么你应该把时间放到找工作的技巧上。而不是在这里讨论没有意义的
技术,只有工资上去了其他的才会上去,一个程序员是很难接触到系统分析和架构的,而一个系统分析员和系统架构师工资也很低的公司明显不是你要呆的地方。 第三、当你基础也很好,待遇也满意的时候,那么你就要考虑你自己的职业前景了,所以什么技术好,什么技术差对你不重要了。技术只是一部分,还有很多其他的东西也很重要。 我现在研究SOA方面的东西,JAVA方面我可能更看重EJB一点,我用过SPRING一年多点,觉得也不错。不过我可能想进大的公司,所以更看重EJB一些。一般大公司面试不太会问SPRING,可是问EJB的可能性很大的。特别是事务处理方面的东西。 我强烈建议做为一个程序员,无乱花多大的代价也应该把你的外语搞上去,我指的是可以直接和国外客户进行交流,如果你现在在做日本项目那么就把日 语搞上去,如果你现在不是做日本项目,那么我觉得你应该直接把英语搞上去。外语好待遇高是一回事情,同样职业前景也会更好。以后做欧美的项目会越来越多 的。我希望所有做JAVA的人都能找到一份好的工作,也希望你们的技术水平越来越高。 本人只是谈谈自己的看法,如果你觉得和你的看法不同,就当作没有看过,不要口吐脏话。我的观点只代表我自己,没有要强加给其他人的意思。我们很多在上海程序员应该是从外地过来的,生活压力很大,我只是希望大家能生活得更好,活得更开心。
January 25 正则表达式简记想必很多人都对正则表达式都头疼。今天,我以我的认识,加上网上一些文章,希望用常人都可以理解的表达方式来和大家分享学习经验。 开篇,还是得说说 ^ 和 $ 他们是分别用来匹配字符串的开始和结束,以下分别举例说明: "^The": 开头一定要有"The"字符串; "of despair$": 结尾一定要有"of despair" 的字符串; 那么, "^abc$": 就是要求以abc开头和以abc结尾的字符串,实际上是只有abc匹配。 "notice": 匹配包含notice的字符串。 你可以看见如果你没有用我们提到的两个字符(最后一个例子),就是说 模式(正则表达式) 可以出现在被检验字符串的任何地方,你没有把他锁定到两边。 接着,说说 '*', '+',和 '?', 他们用来表示一个字符可以出现的次数或者顺序。 他们分别表示: "zero or more"相当于{0,}, "one or more"相当于{1,}, "zero or one."相当于{0,1}, 这里是一些例子: "ab*": 和ab{0,}同义,匹配以a开头,后面可以接0个或者N个b组成的字符串("a", "ab", "abbb", 等); "ab+": 和ab{1,}同义,同上条一样,但最少要有一个b存在 ("ab", "abbb", 等。); "ab?":和ab{0,1}同义,可以没有或者只有一个b; "a?b+$": 匹配以一个或者0个a再加上一个以上的b结尾的字符串。 要点, '*', '+',和 '?'只管它前面那个字符。 你也可以在大括号里面限制字符出现的个数,比如 "ab{2}": 要求a后面一定要跟两个b(一个也不能少)("abb"); "ab{2,}": 要求a后面一定要有两个或者两个以上b(如"abb", "abbbb", 等。); "ab{3,5}": 要求a后面可以有2-5个b("abbb", "abbbb", or "abbbbb")。 现在我们把一定几个字符放到小括号里,比如: "a(bc)*": 匹配 a 后面跟0个或者一个"bc"; "a(bc){1,5}": 一个到5个 "bc." 还有一个字符 '│', 相当于OR 操作: "hi│hello": 匹配含有"hi" 或者 "hello" 的 字符串; "(b│cd)ef": 匹配含有 "bef" 或者 "cdef"的字符串; "(a│b)*c": 匹配含有这样多个(包括0个)a或b,后面跟一个c的字符串; 一个点('.')可以代表所有的单一字符,不包括"\n" 如果,要匹配包括"\n"在内的所有单个字符,怎么办? 对了,用'[\n.]'这种模式。 "a.[0-9]": 一个a加一个字符再加一个0到9的数字 "^.{3}$": 三个任意字符结尾 . 中括号括住的内容只匹配一个单一的字符 "[ab]": 匹配单个的 a 或者 b ( 和 "a│b" 一样); 想必很多人都对正则表达式都头疼。今天,我以我的认识,加上网上一些文章,希望用常人都可以理解的表达方式来和大家分享学习经验。 开篇,还是得说说 ^ 和 $ 他们是分别用来匹配字符串的开始和结束,以下分别举例说明: "^The": 开头一定要有"The"字符串; "of despair$": 结尾一定要有"of despair" 的字符串; 那么, "^abc$": 就是要求以abc开头和以abc结尾的字符串,实际上是只有abc匹配。 "notice": 匹配包含notice的字符串。 你可以看见如果你没有用我们提到的两个字符(最后一个例子),就是说 模式(正则表达式) 可以出现在被检验字符串的任何地方,你没有把他锁定到两边。 接着,说说 '*', '+',和 '?', 他们用来表示一个字符可以出现的次数或者顺序。 他们分别表示: "zero or more"相当于{0,}, "one or more"相当于{1,}, "zero or one."相当于{0,1}, 这里是一些例子: "ab*": 和ab{0,}同义,匹配以a开头,后面可以接0个或者N个b组成的字符串("a", "ab", "abbb", 等); "ab+": 和ab{1,}同义,同上条一样,但最少要有一个b存在 ("ab", "abbb", 等。); "ab?":和ab{0,1}同义,可以没有或者只有一个b; "a?b+$": 匹配以一个或者0个a再加上一个以上的b结尾的字符串。 要点, '*', '+',和 '?'只管它前面那个字符。 你也可以在大括号里面限制字符出现的个数,比如 "ab{2}": 要求a后面一定要跟两个b(一个也不能少)("abb"); "ab{2,}": 要求a后面一定要有两个或者两个以上b(如"abb", "abbbb", 等。); "ab{3,5}": 要求a后面可以有2-5个b("abbb", "abbbb", or "abbbbb")。 现在我们把一定几个字符放到小括号里,比如: "a(bc)*": 匹配 a 后面跟0个或者一个"bc"; "a(bc){1,5}": 一个到5个 "bc." 还有一个字符 '│', 相当于OR 操作: "hi│hello": 匹配含有"hi" 或者 "hello" 的 字符串; "(b│cd)ef": 匹配含有 "bef" 或者 "cdef"的字符串; "(a│b)*c": 匹配含有这样多个(包括0个)a或b,后面跟一个c的字符串; 一个点('.')可以代表所有的单一字符,不包括"\n" 如果,要匹配包括"\n"在内的所有单个字符,怎么办? 对了,用'[\n.]'这种模式。 "a.[0-9]": 一个a加一个字符再加一个0到9的数字 "^.{3}$": 三个任意字符结尾 . 中括号括住的内容只匹配一个单一的字符 "[ab]": 匹配单个的 a 或者 b ( 和 "a│b" 一样); 现在, 用户名的开始和结束都不能是句点。 服务器也是这样。 还有你不能有两个连续的句点他们之间至少存在一个字符,好现在我们来看一下怎么为用户名写一个匹配模式: ^[_a-zA-Z0-9-]+$ 现在还不能允许句号的存在。 我们把它加上: ^[_a-zA-Z0-9-]+(\.[_a-zA-Z0-9-]+)*$ 上面的意思就是说: "以至少一个规范字符(除了。)开头,后面跟着0个或者多个以点开始的字符串。" 简单化一点, 我们可以用 eregi()取代 ereg()。eregi()对大小写不敏感, 我们就不需要指定两个范围 "a-z" 和 "A-Z" ? 只需要指定一个就可以了: ^[_a-z0-9-]+(\.[_a-z0-9-]+)*$ 后面的服务器名字也是一样,但要去掉下划线: ^[a-z0-9-]+(\.[a-z0-9-]+)*$ 好。 现在只需要用“@”把两部分连接: ^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*$ 这就是完整的email认证匹配模式了,只需要调用 eregi(‘^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*$ ’,$eamil) 就可以得到是否为email了。 正则表达式的其他用法 提取字符串 ereg() and eregi() 有一个特性是允许用户通过正则表达式去提取字符串的一部分(具体用法你可以阅读手册)。 比如说,我们想从 path/URL 提取文件名 ? 下面的代码就是你需要: ereg("([^\\/]*)$", $pathOrUrl, $regs); echo $regs[1]; 高级的代换 ereg_replace() 和 eregi_replace()也是非常有用的: 假如我们想把所有的间隔负号都替换成逗号: ereg_replace("[ \n\r\t]+", ",", trim($str)); 最后,我把另一串检查EMAIL的正则表达式让看文章的你来分析一下。 "^[-!#$%&\'*+\\./0-9=?A-Z^_`a-z{|}~]+'.'@'.'[-!#$%&\'*+\\/0-9=?A-Z^_`a-z{|}~]+\.'.'[-!#$%&\'*+\\./0-9=?A-Z^_`a-z{|}~]+$" 如果能方便的读懂,那这篇文章的目的就达到了。 |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|