《Java编程思想》学习笔记


1——面向对象和JVM基础 

1.java中的4种访问制权限:

(1).public:最大访问控制权限,对所有的类都可见。

(2).protect:同一包可见,不在同一个包的所有子类也可见。

(3).default:包访问权限,即同一个包中的类可以可见。默认不显式指定访问控制权限时就是default包访问控制权限。

(4).private:最严格的访问控制权限,仅该类本身可见,对外一切类都不可以访问(反射机制可以访问)。

2.面向对象编程中两种对象组合方式——is-a 和 has-a:

(1).is-a组合:一个类继承具有相似功能的另一个类,根据需要在所继承的类基础上进行扩展。

优点:具有共同属性和方法的类可以将共享信息抽象到父类中,增强代码复用性,同时也是多态的基础。

缺点:子类中扩展的部分对父类不可见,另外如果共性比较少的时候使用继承会增加冗余代码。

(2).has-a组合:has-a组合是在一个类中引用另一个类作为其成员变量。

优点:可扩展性和灵活性高。在对象组合关系中应优先考虑has-a组合关系。

缺点:具有共性的类之间看不到派生关系。

3.多态:

在面向对象编程中,子类中拥有和父类相同方法签名的方法称为子类方法覆盖父类方法,当调用子类方法的某个操作时,不必明确知道子类的具体类型,只需要将子类类型看作是父类的引用调用其操作方法,在运行时,JVM会根据引用对象的具体子类类型而调用应该的方法,这就是多态。

多态的基础是java面向对象编程的晚绑定机制。编程中有如下两种绑定机制:

(1).早绑定:一般在非面向对象编程语言中使用,在程序编译时即计算出具体调用方法体的内存地址。

(2).晚绑定:面向对象编程语言中经常使用,在程序编译时无法计算出具体调用方法体的内存地址,只进行方法参数类型和返回值类型的校验,在运行时才能确定具体要调用方法体的内存地址。

4.java单继承的优点:

相比于C++的多继承,java只支持类的单继承,java中的所有类的共同基类是Object类,Object类java类树的唯一根节点,这种单继承有以下好处:

(1).单继承可以确保所有的对象拥有某种共同的特性,这样对于JVM虚拟机对所有的类进行系统级的操作将提供方便,所有的java对象可以方便地在内存堆栈中创建,传递参数也变的更加方便简单。

(2).java的单继承使得实现垃圾回收器功能更加容易,因为可以确保JVM知道所有对象的类型信息。

5.选择容器对象两个原则:

(1).容器所能提供不同的类型的接口和外部行为是否能够满足需求。

(2).不同容器针对不同的操作效率不同。

6.类型转换:

Java中有两种常见的类型转换:向上类型转换(upcast)和向下类型转换(downcast):

(1).向上类型转换(upcast):

向上类型转换是将子类对象强制类型转换为父类类型,经典用法是面向对象的多态特性。向上类型转换时,子类对象的特性将不可见,只有子类从父类继承的特性仍然保持可见,向上类型转换时编译器会自动检查是否类型兼容,通常是安全的。

(2).向下类型转换:

向下类型转换是将父类类型强制转换为子类类型,转换过后父类中不可见的子类特性又恢复可见性,向下类型转换时,编译器无法自动检测是否类型兼容,往往会产生类型转换错误的运行时异常,通常不安全。

7.java中5个存放数据的地方:

(1).寄存器(Registers):位于CPU内部,是速度最快的存储区,但是数量和容量有限。在java中不能直接操作寄存器。

(2).栈(Stack):栈位于通用随机访问存储器 (General random-access memory,RAM,内存) 中,通过处理器的栈指针访问,栈指针从栈顶向栈底分配内存,从栈底向栈顶释放内存。栈是仅次于寄存器的速度第二快的存储器,在java程序中,一般的8种 基本类型数据和对象的引用通常存放在栈内存中,不通过new关键字的字符串对象也是存放在栈的字符串池中。栈的优势是,存取速度比堆要快,仅次于寄存器, 栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。

(3).堆(Heap):也是位于通用随机访问存储器 (General random-access memory,RAM,内存) 中的共享内存池。Java的堆是一个运行时数据区,类的对象从中分配空间,凡是通过new关键字创建的对象都存放在堆内存中,它们不需要程序代码来显式的 释放。堆是由垃圾回收来负责的,堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的,Java的垃圾收集器 会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。

(4).常量存储器(Constant storage):java中的常量是存放在系统内嵌的只读存储器中(read-only memory,ROM)的。

(5).非随机存储器(Non-RAM storage):对于流对象和持久化对象,通常存放在程序外的存储器,如硬盘。

8.javadoc只处理public和protected访问控制权限的文档注释,private和default权限的稳定注释将被忽略。

9.java中赋值运算:

基本类型赋值是直接复制值,赋值操作后,相互不影响。

引用类型赋值是复制引用值,相当于给对象取一个别名,赋值之后两个引用指向同一个引用对象,相互之间有影响。

在Java中,向方法传递引用类型参数会改变参数的值,不让参数受到影响的解决方法:在方法内首先先将引用克隆一份,然后操作克隆的对象。

10.移位运算:

左移运算符<<:将比特位左移指定位数,右边部分补0,左移一位相当于乘2。

右移运算符>>:将比特位右移指定位数,如果是正数,左边第一位(符号位)补0,其余位补0,如果是负数,左边第一位补1,其余位补0。右移一位相当于除2。

无符号右移运算符>>>:将比特位右移指定位数,不论是正数或者负数,左边移除位统统补0。

11.java中,比int类型小的原始类型(char、byte、short)进行数学运算或者位运算时,数据类型首先转换成int类型,然后进行相应的运算。

12.方法重载(overloading):方法同名,参数列表不同称为方法重载,注意方法的返回值类型不同不能作为方法重载。

13.java中的析构函数:

Java中没有像C/C++的析构函数,用来销毁不用的对象是否内存空间,只有以下三个方法用于通知垃圾回收器回收对象。

(1).finalize( )只是通知JVM的垃圾收集器当前的对象不再使用可以被回收了,但是垃圾回收器根据内存使用状况来决定是否回收。

finalize()最有用的地方是在JNI调用本地方法时(C/C++方法),调用本地方法的析构函数消耗对象释放函数。

(2). System.gc()是强制析构,显式通知垃圾回收器释放内存,但是垃圾回收器也不一定会立即执行,垃圾回收器根据当前内存使用状况和对象的生命周期自行决定是否回收。

(3).RunTime.getRunTime().gc()和System.gc()类似。

注意:这三个函数都不能保证垃圾回收器立即执行,推荐不要频繁使用。

14.垃圾回收器原理:

(1).引用计数(ReferenceCounting)垃圾回收算法

一种简单但是速度较慢的垃圾回收算法,每个对象拥有一个引用计数器(Reference Counter),当每次引用附加到这个对象时,对象的引用计数器加1。当每次引用超出作用范围或者被设置为null时,对象的引用计数器减1。垃圾回收 器遍历整个对象列表,当发现一个对象的引用计数器为0时,将该对象移出内存释放。

引用计数算法的缺点是,当对象环状相互引用时,对象的引用计数器总不为0,要想回收这些对象需要额外的处理。

引用计数算法只是用来解释垃圾回收器的工作原理,没有JVM使用它实现垃圾回收器。

引用计数的改进算法:

任何存活的对象必须被在静态存储区或者栈(Stack)中的引用所引用,因此当遍历全部静态存储区或栈中的引用时,即可以确定所有存活的对象。每当 遍历一个引用时,检查该引用所指向的对象,同时检查该对象上的所有引用,没有引用指向的对象和相互自引用的对象将被垃圾回收器回收。

(2).暂停复制(stop-and-copy)算法:

垃圾回收器的收集机制基于:任何一个存活的对象必须要被一个存储在栈或者静态存储区的引用所引用。

暂停复制的算法是:程序在运行过程中首先暂停执行,把每个存活的对象从一个堆复制到另一个堆中,已经不再被使用的对象被回收而不再复制。

暂停复制算法有两个问题:

a.必须要同时维护分离的两个堆,需要程序运行所需两倍的内存空间。JVM的解决办法是在内存块中分配堆空间,复制时简单地从一个内存块复制到另一个内存块。

b.第二个问题是复制过程的本身处理,当程序运行稳定以后,只会产生很少的垃圾对象需要回收,如果垃圾回收器还是频繁地复制存活对象是非常低性能的。JVM的解决方法是使用一种新的垃圾回收算法——标记清除(mark-and-sweep)。

一般来说标记清除算法在正常的使用场景中速度比较慢,但是当程序只产生很少的垃圾对象需要回收时,该算法就非常的高效。

(3).标记清除(mark-and-sweep)算法:

和暂停复制的逻辑类似,标记清除算法从栈和静态存储区开始追踪所有引用寻找存活的对象,当每次找到一个存活的对象时,对象被设置一个标记并且不被回收,当标记过程完成后,清除不用的死对象,释放内存空间。

标记清除算法不需要复制对象,所有的标记和清除工作在一个内存堆中完成。

注意:SUN的文档中说JVM的垃圾回收器是一个后台运行的低优先级进程,但是在早期版本的JVM中并不是这样实现的,当内存不够用时,垃圾回收器先暂停程序运行,然后进行垃圾回收。

(4).分代复制(generation-copy)算法:

一种对暂停复制算法的改进,JVM分配内存是按块分配的,当创建一个大对象时,需要占用一块内存空间,严格的暂停复制算法在释放老内存堆之前要求把每个存活的对象从源堆拷贝到新堆,这样做非常的消耗内存。

通过内存堆,垃圾回收器可以将对象拷贝到回收对象的内存堆中,每个内存块拥有一个世代计数(generation count)用于标记对象是否存活。每个内存块通过对象被引用获得世代计数,一般情况下只有当最老的内存块被回收时才会创建新的内存块,这主要用于处理大 量的短存活周期临时对象回收问题。一次完整的清理过程中,内存块中的大对象不会被复制,只是根据引用重新获得世代计数。

JVM监控垃圾回收器的效率,当发现所有的对象都是长时间存活时,JVM将垃圾回收器的收集算法调整为标记清除,当内存堆变得零散碎片时,JVM又重新将垃圾回收器的算法切换会暂停复制,这就是JVM的自适应分代暂停复制标记清除垃圾回收算法的思想。

15.java即时编译技术(JIT):

Java的JIT是just-in-timecomplier技术,JIT技术是java代码部分地或全部转换成本地机器码程序,不再需要JVM解释,执行速度更快。

当一个”.class”的类文件被找到时,类文件的字节码被调入内存中,这时JIT编译器编译字节码代码。

JIT有两个不足:

(1).JIT编译转换需要花费一些时间,这些时间贯穿于程序的整个生命周期。

(2).JIT增加了可执行代码的size,相比于压缩的字节码,JIT代码扩展了代码的size,这有可能引起内存分页,进而降低程序执行速度。

对JIT不足的一种改进技术是延迟评估(lazy evaluation):其基本原理是字节码并不立即进行JIT编译除非必要,在最近的JDK中采用了一种类似延迟JIT的HotSpot方法对每次执行的代码进行优化,代码执行次数越多,速度越快。

16.非内部类的访问控制权限只能是默认的包访问权限或者是public的,不能是protected和private的。内部类的访问控制权限可以是protected和private。

17.Java中的高精度数值类型:

BigInteger和BigDecimal是java中的高精度数值类型,由于它们是用于包装java的基本数据类型,因此这两个高精度数值类型没有对应的原始类型。

(1).BigInteger支持任意精度的整数,即使用BigInteger可以表示任意长度的整数值而在运算中不会因为范围溢出丢失信息。

(2).BigDecimal支持任意精度的固定位数浮点数,可以用来精确计算货币等数值。

普通的float和double型浮点数因为受到小数点位数限制,在运算时不能准确比较,只能以误差范围确定是否相等,而BigDecimal就可以支持固定位数的浮点数并进行精确计算。

18.Java只处理public和protected访问控制权限成员的文档注释,private和默认的包访问控制权限成员的文档注释将被忽略。

19.Java中赋值运算:

Java中赋值运算是把赋值运算符”=”右边的值简称右值拷贝到赋值运算符左边的变量,如a=b,即把b代表的变量或常量值复制给变量a,切记a只能是变量,不能说常量值。

(1).原始类型赋值运算:

Java中8种原始数据类型赋值运算是将赋值运算符右边的值拷贝到赋值运算符左边的变量中。

原始类型赋值运算后,无论改变赋值运算符那一边的值,都不会影响赋值运算符另一边的值。

(2).引用类型的赋值运算:

Java中除了8中原始数据类型外,所有的数据类型都是对象类型,对象类型的赋值运算是操作引用,如a=b,把b引用赋值给a引用,即原本b引用指向的对象现在由a和b引用同时指向。

引用赋值运算符又叫别名运算符,即它相当于给引用对象取了一个别名,其实引用的还是同一个对象。

引用类型的赋值运算,如果赋值运算符任意一边的引用改变了被引用对象的值,赋值运算符另一边的引用也会受影响,因为两个引用指向的是同一个被引用的对象。

2——对象初始化和面向对象特性 

1.java类的初始化顺序:

(1).在一个类中,初始化顺序由变量在类中的声明定义顺序决定,成员变量(非set方法和构造方法的初始化)的初始化发生在方法调用之前,包括构造方法。

(2).静态变量在整个存储区只保留一份拷贝,本地变量不能使用静态关键字,基本类型的静态变量不需要初始化,它会根据类型获得初始化值,引用类型的静态变量默认初始化为null。

静态变量的初始化发送在需要使用的时候,一旦被初始化之后,静态变量就不会再初始化。

(3).静态初始化块和静态变量类似的执行也在构造方法之前,并且仅执行一次。

(4).动态初始化块(与静态初始化块类似,只是没有static关键字,即放在一对大括号中的代码块)在静态初始化块初始化结束后执行,动态初始化块每次创建新对象都会初始化一次。

(5).构造方法执行时,先执行父类的构造方法,后执行子类的构造方法。

(6).本地变量初始化最晚,在方法中初始化。

综述,类的初始化顺序依次为:

a.父类的静态变量/静态初始化块;

b.子类类的静态变量/静态初始化块;

c.父类的动态初始化块、非构造方法和set方法的成员变量初始化

d.子类的动态初始化块、非构造方法和set方法的成员变量初始化

e.父类的构造方法。

f.子类的构造方法。

g.父类本地变量。

h.子类的本地变量。

2.数组初始化:

Java中数组初始化有以下3中方式:

(1).数组声明时直接初始化,如:

int[] a = {1,2,3};

(2).动态数组初始化,如:

int[] a = new int[]{1,2,3};

注意:动态数组初始化时,不能在new()操作符中指定数组的大小,即int a = new int[3]{1,2,3}的写法是错误的,数组的大小由初始化数组元素个数决定。

(3).固定长度数组初始化,如:

int[] a = new int[3];

a[1] = 0;

a[2] = 1;

a[3] = 2;

注意:固定长度大小的数组初始化时不能大于所声明的数组长度,没有声明的数组元素使用其默认值,如int默认为0,对象类型的值为引用,默认为null.

3.java代码重用4中方式:

java面向对象编程中提供了如下4中代码重用的方式:

(1).组合:

面向对象编程中最常用的代码复用方式,具体的方式是在一个对象中将另一个对象引用最为成员变量,其最大的优点是既实现松散耦合,有可能提高代码复用率。

(2).继承:

面向对象编程中常用的提高代码复用率的方法之一,适用于子类和父类是同一种抽象类型,具有共同的属性情况。

使用继承,子类可以复用父类除private私有房屋控制权限以为的所有属性和方法,编译器将父类封装为子类对象内部的一个对象。

需要注意的是:调用子类初始化构造方法时,编译器会确保首先调用父类的构造方法初始化父类,然后才初始化子类,如果父类中没有默认的构造方法,即需要显式传入参数的构造方法时,子类必须通过super关键字显式传入参数调用父类的构造方法。

(3).委派:

Java中不支持委派方式的代码复用,但是开发人员可以使用委派机制实现代码的重用。

委派是指,java对象的所有方法其实都是委派调用另一个类的方法实现,但是当前类又不是所委派类的类型,因此使用继承不太合适,解决方式和组合类似,将被委派类作为委派类的成员变量,委派类的方法直接调用被委派类对象应用的方法。

如:

[java] view plaincopy

1. //委派类  

2. public Class A{  

3.     //被委派类  

4.     private B b = new Class B();  

5.     public void method1(){  

6.         b.method1();  

7. }  

8. public void method2(){  

9.         b.method2();  

10. }  

11. ……  

12. }  

 

(4).联合使用组合和继承方式:

因为java中不允许多继承,如果某种情况下,一个java类需要使用多个其他类功能,且该类和其中某个类具有很多共同属性,即可以看作同一类,则可以使当前类继承具体共同属性的类,同时将其他类作为成员变量组合引用。

4.组合和继承的区别:

组合和继承都可以复用代码,很多java程序员,甚至是架构师都分不清楚什么情况下该使用组合,什么情况下应该使用继承,具体的区别如下:

(1).组合:

组合通常是在一个类中想使用另一个类已有的特性,但是却不想使用其接口。

使用组合可以可以将一个类作为当前类的内嵌对象,这样在当前类中就可以显式地使用内嵌类已经实现的功能,与此同时又不会影响当前类的调用接口。

(2).继承:

继承是隐式地使用被继承类的功能,相当于提供了一个新版本的父类实现。

使用继承,子类不但可以复用父类的功能,同时还复用了父类的接口,子类和父类的对外调用接口相同的情况下适合使用继承。

使用继承时,很多情况下需要向上类型转换,即将子类看作其父类。在编程时到底选用组合方式还是继承方式,一个简单的判断依据是:是否需要向上类型转换,如果需要就使用继承,如果不需要,则选择组合。

5.final方法:

Java中使用final类型的方法有以下两种原因:

(1).设计原因:

final类型的方法不允许其子类修改方法,即不允许子类覆盖父类的final方法。

(2).效率原因:

在早期的java实现中,如果方法被声明为final,编译器将final方法调用编译为内联调用。

正常的方法调用是:如果方法调用时,将当前的方法上下文保持到栈中,调用被调用的方法,然后在将调用上下文出栈恢复调用现场。

内联调用是:如果方法调用时,将被调用方法体拷贝到当前调用的地方合并成一个方法体,这样就避免因需要保存方法调用线程而进行的进栈和出栈操作,可以提高效率。

新版的使用hotspot技术的java虚拟机可以探测方法调用情况而做效率优化,final方法不再作为提高效率的手段,唯一的作用是确保方法不被子类覆盖。

注意:任何private的方法都是隐式的final类型,同final方法类似,private方法不能被子类所覆盖,但是private比final更严格,基类的private方法对子类不可见,private方法不再是接口的一部分。

6.多态性:

面向对象编程中的多态和继承往往是一起发挥作用的,使用继承,所有的子类和父类使用相同的对外接口,而多态的基础是晚绑定或动态绑定或运行时绑定, 即对象引用使用基类类型,在编译时编译器无法确切知道到底调用哪一个具体类,只有在运行时,java虚拟机才通过类型检查确定调用对象的具体类型。

Java中默认对象引用全部是晚绑定,只有static和final类型的引用时早绑定或编译时绑定。

多态的优势是:程序的可扩张性好,无论添加多少个子类,基类的接口都不用改变,只需要在子类对应方法中提供具体实现即可,也就是所谓的将程序变化的部分和程序保持不变的部分分离。

注意:只有正常的方法可以使用多态,字段和静态方法没有多态机制。

         构造方法也不支持多态机制,构造方法是隐式的static声明。

 

3——内部类 

1.java中,可以将一个类的定义放在另一个类的内部,这种叫做内部类。

内部类允许编程人员将逻辑上相关的类组织在一起,并且控制内部类对其他类的可见性。

2.在外部类的非静态方法中创建内部类的对象语法:

外部类类名.内部类类名 对象名 = 外部类对象.new 内部类类名();

如:

[java] view plaincopy

1. public class Outter{  

2.     class inner{  

3. }  

4. }  

5. Outter out = new Outter();  

6. Outter.Inner inner = out.new Inner();  

 

注意:非静态的内部类必须要有外部类对象之后才能创建,因为外部类对象持有内部类的引用,如果内部类是静态的,则不需要外部类对象引用内部类对象。

3.内部类对外部类对象的引用:

外部类中所有的元素对内部类都是可见的,内部类持有对外部类对象引用的语法:外部类名称.this。如:

[java] view plaincopy

1. public class Outter{  

2.     void f(){  

3. System.out.println(“Outter f() method”);  

4. }  

5.     class Inner{  

6.     public Outter outer(){  

7.         return Outter.this;  

8. }  

9. }  

10.     public Inner inner(){  

11.         return new Inner();  

12. }  

13. }  

 

4.方法内部类:

除了最常见的最为成员变量的内部类以外,内部类还可以定义在方法中,如:

[java] view plaincopy

1. public class Outter{  

2.     public Inner getInner(){  

3.         class Inner{  

4.             public void f(){  

5.     System.out.println(“Method inner class”);  

6. }  

7. }  

8. return new Inner();  

9. }  

10. public void fout(){  

11.     Inner inner = getInner();  

12.     inner.f();  

13. }  

14. }  

 

5.匿名内部类:

Java中匿名内部类的应用十分广泛,所谓的匿名内部类就是指所创建的内部类没有类名称,也就是不知道内部类的具体类型,如:

[java] view plaincopy

1. public class Outter{  

2.     public Inner getInner(){  

3.         return new Inner(){  

4.             private String name = “inner”;  

5.             public String getName(){  

6.     return name;  

7. }  

8. };  

9. }  

10. }  

 

上面的return newInner{……};就是一个匿名内部类,这个匿名内部类继承了Inner类,即其基类是Inner,但是其具体类型不清楚,该匿名内部类等效于下面的内部类写法:

[java] view plaincopy

1. public class Outter{  

2.     class MyInner implement Inner{  

3.         private String name = “Inner class”;  

4.         public String getName(){  

5.             return name;  

6. }  

7. }  

8. public Inner getInner(){  

9.     return new MyInner();  

10. }  

11. }  

 

注意:匿名内部类是实现了new关键字之后的接口或继承了类,只是没有具体的类名称。

6.内名内部类传递final参数:

如果在创建匿名内部类,需要外部类传递参数时,参数必须是final类型的,否则,编译时会报错。如:

[java] view plaincopy

1. public class Outter{  

2.     public Inner getInner(final String name){  

3.         return new Inner(){  

4.             public name getName(){  

5.                 return name;  

6. }  

7. };  

8. }  

9. }  

 

注意:如果外部类传递的参数在内部类中使用,则必须是final类型,如果没有在内部类中使用(如,仅在基类中使用),则可以不用是final类型的。

7.静态内部类:

静态内部类又称为嵌套类,静态内部类和普通内部类的区别:

(1).对于非静态的内部类来说,内部类和外部类必须保持对象引用,内部类可以访问外部类的任何元素.

(2).静态内部类不需要和外部类保持对象引用,静态内部类只能访问外部类的静态元素。

8.为什么要使用内部类:

使用内部类主要有以下两个原因:

(1).解决java中类不能多继承的问题:

Java中的继承是单继承,在某些情况下接口的多继承可以解决大部分类似C++中多继承的问题,但是如果一个类需要继承两个父类而不是接口,在java中是没法实现这种功能的,内部类可以帮助我们部分解决这种问题,如:

[java] view plaincopy

1. abrstact class A{  

2.     abstract public void f();  

3. }  

4. abstract class B{  

5. abstract public void g();  

6. }  

7. public class D extends A{  

8.     public void f(){}  

9.     public B makeB(){  

10.         return new B(){  

11.             public void g(){};  

12. }  

13. }  

14. }  

 

这样既继承了A类,有在makeB方法中使用匿名内部类继承了B类。

(2).闭包方法问题:

在面向对象中有个术语叫闭包,所谓闭包是指一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。Java中不支持闭包,但是 java的内部类可以看作是对闭包的一种解决方案,因为外部类的所有元素对于普通的内部类来说都是可见的,并且内部类还包含一个指向外部类的对象引用,因 此在作用域内,内部类有权操作外部类的所有成员包括private成员。如:

[java] view plaincopy

1. <pre name="code" class="java">interface Incrementable  {    

2.     void increment();    

3. }    

4.   

5. class Callee1 implements Incrementable  {    

6. private int i=0;    

7. public void increment()  {    

8. i++;    

9. System.out.println(i);    

10. }    

11. }    

12.   

13. class MyIncrement  {    

14. void increment()  {    

15. System.out.println("other increment");    

16. }    

17. static void f(MyIncrement mi)  {    

18.         mi.increment();    

19. }    

20. }    

21.   

22. class Callee2 extends MyIncrement  {    

23. private int i=0;    

24. private void incr()  {    

25. i++;    

26. System.out.println(i);    

27. }    

28. //闭包内部类  

29. private class Closure implements Incrementable {    

30. public void increment() {    

31. incr();    

32. }    

33. }    

34. //回调函数  

35. Incrementable getCallbackReference()  {   

36. //新建内部类     

37. return new Closure();   

38. }    

39. }    

40.   

41. class Caller {    

42. private Incrementable callbackRefference;    

43. Caller(Incrementable cbh)  {    

44. callbackRefference = cbh;    

45. }    

46. void Go() {    

47. //调用increment()方法  

48. callbackRefference.increment();   

49. }    

50. }    

51.   

52. public class Callbacks  {    

53. public static void main(String [] args) {    

54. Callee1 c1=new Callee1();    

55. Callee2 c2=new Callee2();    

56. MyIncrement.f(c2);    

57. Caller caller1 =new Caller(c1);    

58. //将内部类中的Closure赋给Caller    

59. Caller caller2=new Caller(c2.getCallbackReference());  

60. caller1.go();    

61. caller1.go();    

62. caller2.go();    

63. caller2.go();    

64. }    

65. }   

66.  

67.   

68. 输出:  

69. other increment  

70.  1  

71.  2  

72.  1  

73.  2  

74. Callee2 通过使用内名内部类提供了一个访问自身increment方法的钩子,通过getCallbackReference()方法获取内部类对象的引用,进而通 过内部类的increment方法调用外部类的回调方法,这就是java中用于实现回调方法的闭包。 

75.  

 

 

 

4——集合容器 

1.集合中添加另一个集合的方法:

(1).Collection.addAll(被添加的Collection对象)方法:

如:list1.addAll(list2);

(2).Collections.addAll(添加到的目标Collection对象,可变参数的集合或者对象)方法:

如:Collections.addAll(list1, new Object1(), new Object2()…);

       Collectionns.addAll(list1, list2…);

注意:Collections是java集合容器的工具类,相比于(1),使用Collections的(2)更灵活。

2.Java集合中常用的交集、并集和差集操作:

并集:collection对象1.addAll(collection对象2);

交集:collection对象1. retainAll(collection对象2);

差集:collection对象1. removeAll(collection对象2);

注意:上述的集合操作时,集合元素的equals方法会影响操作结果。

3.将其他类型集合转换为List:

Arrays.asList(非List类型的集合对象/可变参数的对象);方法可以将传递的参数转变为List集合。如:Arrays.asList(new Object1(),new Object2(),…);

Arrays和Collections类似,是Array数组类型集合的工具类。

注意:Arrays.asList()方法转换后的List对象是一个size不能改变的对象,如果对该对象做增加或者删除元素操作时,将会报不支持的操作异常。

4.List集合:

List集合主要有两种具体的集合容器:ArrayList和LinkedList。

(1).ArrayList:底层实现是数组,提供了根据数组下标快速随机访问的能力,但是增加和删除元素时因为需要引动数组的元素,因此比较慢。

(2).LinkedList:底层实现是链表,链表访问元素时必须从链表头至链表尾挨个查找,因此只能顺序访问,速度比随机访问要慢。但是增加和删除元素时,只需要修改链表的指针而不需要移动元素,因此速度比较快。

5.LinkedList:

LinkedList除了实现了基本的List接口以外,还提供了一些特定的方法,使得LinkedList可以方便地实现Stack、Queue以及双端Queue的功能。

LinkedList提供的非List接口方法:

(1).getFirst():获取并且不移除LinkedList集合中第一个元素。如果集合为空,抛出NoSuchElementException异常。

(2).element():获取并且不移除LinkedList集合中第一个元素。如果集合为空,抛出NoSuchElementException异常。

(3).peek():获取并且不移除LinkedList集合中第一个元素。如果集合为空,则返回null。

(4).removeFirst():获取并且移除LinkedList集合中第一个元素。如果集合为空,抛出NoSuchElementException异常。

(5).remove():获取并且移除LinkedList集合中第一个元素。如果集合为空,抛出NoSuchElementException异常。

(6).poll():获取并且移除LinkedList集合中第一个元素。如果集合为空,则返回null。

(7).addFirst():向LinkedList集合的头部插入一个元素。

(8).add():向LinkedList集合的尾部插入一个元素。

(9).offer():向LinkedList集合的尾部插入一个元素。

(10).removeLast():获取并且移除LinkedList集合中最后一个元素。如果集合为空,抛出NoSuchElementException异常。

6.Iterator:

Iterator迭代器在java集合容器中应用比较广泛,对于List类型的集合,可以通过下标索引值获取到指定的元素,而对于Set类型的集合,因为Set是没有索引的,因此只能通过迭代器来遍历。

Iterator迭代器是一个顺序选择和遍历集合元素的对象,使用者不需要关心其底层的数据结构和实现方式。Java中的Iterator迭代器是单向的。

Iterator的常用方法如下:

(1).collection对象.iterator()方法:将集合对象转换为Iterator迭代器。

(2).iterator对象.hasNext()方法:判断迭代器中是否还有元素。

(3).iterator对象.next()方法:获取迭代器中下一个元素。
(4).iterator对象.remove()方法:删除迭代器中当前元素。

注意:使用迭代器的好处是,当数据结构从List变为Set之后,迭代集合的相关代码一点都不用改变。

7.ListIterator:

ListIterator是Iterator的子类,它只能有List类型的集合产生,ListIterator是一个双向的迭代器,即它可以向前和向后双向遍历集合。ListIterator的常用方法如下:

(1).list类型对象.listIterator():将List类型的集合转换为ListIterator迭代器。

(2).list类型对象.listIterator(int n):将List类型的集合转换为ListIterator迭代器,同时指定迭代器的起始元素为第n个元素。

(3).listIterator对象.hasNext():判断迭代器中是否还有下一个元素。

(4).listIterator对象.next():获取迭代器中的下一个元素。

(5).listIterator对象.hasPrevious():判断迭代器中是否还有前一个元素。

(6).listIterator对象.previous():获取迭代器中的前一个元素。

(7).listIterator对象.set(元素对象):将当前迭代到的元素设置为另一个值。

8.Map遍历3中方法:

Map<String, Object>map = new HashMap<String, Object>();

map.put(“test1”, object1);

……

map.put(“testn” , objectn);

(1).Map的values()方法可以获取Map值的集合:

[java] view plaincopy

1. Iterator it = map.values().iterator();  

2. while(it.hasNext()){  

3.     Object obj = it.next();  

4. }  

 

(2).Map的keySet方法可以获取Map键的Set集合:

[java] view plaincopy

1. Set<String> keys = map.keySet();  

2. for(Iterator it = key.iterator(); it.hasNext(); ){  

3.     String key = it.next();  

4.     Object obj = map.get(key);  

5. }  

 

(3).通过使用Entry来得到Map的key和value:

[java] view plaincopy

1. Set<Map.Entry<String, Object>> entrySet = map.entrySet();  

2. for(Iterator <Map.Entry<String, Object>> it = entrySet.iterator(); it.hasNext(); ){  

3.     Map.Entry<String, Object> entry = it.next();  

4.     String key = entry.getKey();  

5.     Object value = entry.getValue();  

6. }  

 

9.Collection和Iterator:

Collection是java中除了Map以外的集合容器的通用接口,如果想自定义一种集合容器类型的类,可以选择实现Collection接口或者继承Collection的子类。

实现Collection接口或者继承Collection子类的时候,必须实现Collection接口的所有方法,而Iterator为定义自 定义集合容器类型提供了另一种方便,Iterator是一种轻量级的接口,只需要实现hasNext(),next()方法即可,remove()方法是 可选方法。

注意:Foreach循环支持所有实现了Iterable接口的集合容器(Collection接口的父接口是Iterable),Map集合没有实现Iterable接口,因此不支持Foreach循环。

10.java集合容器框架图:

 

 

 

5——正则表达式量词匹配 

Java正则表达式有3中量词匹配模式:

1.贪婪量词:

先看整个字符串是否匹配,如果没有发现匹配,则去掉最后字符串中的最后一个字符,并再次尝试,如果还是没有发现匹配,那么,再次去掉最后一个字符串的最后一个字符,整个匹配过程会一直重复直到发现一个匹配或者字符串不剩任何字符。简单量词都是贪婪量词。

贪婪量词匹配时,首先将整个字符串作为匹配的对象,然后逐步从后向前移除不匹配的字符,尽可能找到最多的匹配。

2.惰性量词:

先看字符串中的第一个字符是否匹配,如果单独一个字符不够,则读入下一个字符,组成两个字符的字符串,如果还没有发现匹配,惰性量词继续从字符串中添加字符直到发现一个匹配或者整个字符串全部检查完都不匹配。惰性量词和贪婪量词工作方式恰好相反。

惰性量词匹配时,只匹配第一个字符,然后依次添加字符,尽可能找到最少匹配。

3.支配量词:

只尝试匹配整个字符串,如果整个字符串不能产生匹配,则不进行进一步尝试。

支配量词目前只有java中支持,支持量词是贪婪量词第一次匹配不成功时,阻止正则表达式继续匹配,使得正则表达式效率更高。

贪婪量词        惰性量词        支配量词         描述

X?              X??              X?+             X出现0次或者1次

X*              X*?              X*+             X出现0次或者多次

X+              X+?              X++             X出现1次或者多次

X{n}            X{n}?            X{n}+           X只出现n次

X{n,}           X{n,}?           X{n,}+          X至少出现n次

X{n,m}          X{n,m}?          X{n,m}+         X至少出现n次,至多不超过m次

 

 

6——Java动态代理 

代理是一种常用的程序设计模式,如同网络代理一样,代理是介于调用者和真正调用目标对象之间的中间对象,代理在调用真正目标对象时提供一些额外或者不同的操作,真正的对目标对象的操作还是通过代理调用目标对象来完成。

    简单的代理例子如下:

[java] view plaincopy

1. //接口  

2. interface Interface{  

3.     void doSomething();  

4.     void somethingElse(String arg);  

5. }  

6. //目标对象  

7. class RealObject implement Interface{  

8.     public void doSomething(){  

9.         System.out.println(“RealObject doSomething”);  

10. }  

11. public void somethingElse(String arg){  

12.     System.out.println(“RealObject somethingElse ” + arg);  

13. }  

14. }  

15. //简单代理对象  

16. class SimpleProxy implements Interface(  

17.     private Interface proxied;  

18.     public SimpleProxy(Interface proxied){  

19.         this.proxied = proxied;  

20. }  

21. public void doSomething(){  

22.     System.out.println(“SimpleProxy doSomething”);  

23.     proxied.doSomething();  

24. }  

25. public void somethingElse(String arg){  

26.     System.out.println(“SimpleProxy somethingElse ” + arg);  

27.     proxied.somethingElse(arg);  

28. }  

29. )  

30. Class SimpleProxyDemo{  

31.     public static void consumer(Interface iface){  

32.         iface.doSomething();  

33.         iface.somethingElse(“TestProxy”);  

34. }  

35. public static void main(String[] args){  

36. //不是用代理  

37.     cosumer(new RealObject());  

38.     //使用代理  

39.     cosumer(new SimpleProxy(new RealObject()));  

40. }  

41. }  

 

输出结果为:

RealObject doSomething

RealObjectsomethingElse TestProxy

SimpleProxy doSomething

RealObject doSomething

SimpleProxy somethingElse TestProxy

RealObject somethingElse TestProxy

上面例子可以看出代理SimpleProxy在调用目标对象目标方法之前做了一些额外的操作。

Java中的代理是针对接口的动态代理,当然java也可以使用第三方的CGLIB实现针对类的代理,但是JDK中只支持针对接口的动态代理,我们只分析JDK的动态代理。

JDK动态代理的要素:

(1).实现了InvocationHandler的代理处理类,实现其invoke方法,该方法是代理调用目标对象方法以及提供额外操作的方法。

(2).使用Proxy.newProxyInstance(类加载器, 代理接口列表,InvocationHandler对象);方法创建实现了指定接口的动态代理。

JDK的代理例子如下:

[java] view plaincopy

1. //接口  

2. interface Interface{  

3.     void doSomething();  

4.     void somethingElse(String arg);  

5. }  

6. //目标对象  

7. class RealObject implement Interface{  

8.     public void doSomething(){  

9.         System.out.println(“RealObject doSomething”);  

10. }  

11. public void somethingElse(String arg){  

12.     System.out.println(“RealObject somethingElse ” + arg);  

13. }  

14. }  

15. //代理处理类  

16. class DynamicProxyHandler implements InvocationHandler{  

17.     provate Object proxied;  

18.     public DynamicProxyHandler(Object proxied){  

19.         this.proxied = proxied;  

20. }  

21. //动态代理调用目标对象的方法  

22. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{  

23.     System.out.println(“Dynamic proxy invoke”);  

24.     return method.invoke(proxied, args);  

25. }  

26. }  

27. class SimpleDynamicProxy{  

28.     public static void consumer(Interface iface){  

29.         iface.doSomething();  

30.         iface.somethingElse(“DynamicProxy”);  

31. }  

32. public static void main(String[] args){  

33.     RealObject real = new RealObject();  

34.     //不是用代理  

35.     consumer(real);  

36.     //创建动态代理  

37.     Interface proxy = (Interface) Proxy.newProxyInstance(  

38.                     Interface.class.getClassLoader(),  

39.                     new Class[]{Interface.class},  

40.                     new DynamicProxyHandler(real)  

41. );  

42.        cosumer(proxy);  

43. }   

44. }  

 

输出结果为:

RealObject doSomething

RealObject somethingElse DynamicProxy

Dynamic proxy invoke

RealObject doSomething

Dynamic proxyinvoke        

RealObject somethingElse DynamicProxy

 

 

 

7——泛型编程基础 

一般的类和方法都是针对特定数据类型的,当写一个对多种数据类型都适用的类和方法时就需要使用泛型编程,java的泛型编程类似于C++中的模板, 即一种参数化类型的编程方法,具体地说就是将和数据类型相关的信息抽象出来,主要提供通用的实现和逻辑,和数据类型相关的信息由使用时参数决定。

1.泛型类/接口:

(1).泛型接口:

如一个提供产生指定类的接口:

[java] view plaincopy

1. public interface Gernerator<T>{  

2.     T next() ;  

3. }  

4. public class A implement Generator<A>{  

5.     A next(){  

6.         return new A();  

7. }  

8. }  

 

(2).泛型类:

一个使用泛型实现的栈数据结构如下:

[java] view plaincopy

1. public class LinkedListStack<T>{  

2.     //节点内部类  

3.     private static class Node<U>{  

4.         U item;  

5.         Node<U> next;  

6.         Node(){  

7.             item = null;  

8.             next = null;  

9. }  

10.         Node(U item, Node<U> next){  

11.     this.item = item;  

12.     this.next = next;  

13. }  

14. Boolean end(){  

15.     return item == null && next == null;  

16. }  

17. }  

18. private Node<T> top = new Node<T>();  

19. public void push<T>(T item){  

20.     top = new Node<T>(item, top);  

21. }  

22. public T pop(){  

23.     T result = top.item;  

24.     if(!top.end()){  

25.         top = top.next();  

26. }  

27. return result;  

28. }   

29. }  

 

使用这个使用泛型实现的栈,可以操作各种数据类型。

2.泛型方法:

例如:

[java] view plaincopy

1. public class GenericMethods{  

2.     public <T> void f(T x){  

3.         System.out.println(x.getClass().getName()) ;  

4. }  

5. public static void main(String[] args){  

6.     GenericMethods gm = new GenericMethods();  

7.     gm.f(“”);  

8.     gm.f(1);  

9.     gm.f(1.0);  

10.     ……  

11. }   

12. }  

 

输出结果为:

java.lang.String

java.lang.Integer

java.lang.Double

3.泛型集合:

Java中泛型集合使用的非常广泛,在Java5以前,java中没有引入泛型机制,使用java集合容器时经常遇到如下两个问题:

a.       java容器默认存放Object类型对象,如果一个容器中即存放有A类型对象,又存放有B类型对象,如果用户将A对象和B对象类型弄混淆,则容易产生转换错误,会发生类型转换异常。

b.       如果用户不知道集合容器中元素的数据类型,同样也可能会产生类型转换异常。

鉴于上述的问题,java5中引入了泛型机制,在定义集合容器对象时显式指定其元素的数据类型,在使用集合容器时,编译器会检查数据类型是否和容器指定的数据类型相符合,如果不符合在无法编译通过,从编译器层面强制保证数据类型安全。

(1).java常用集合容器泛型使用方法:

如:

[java] view plaincopy

1. public class New{  

2.     public static <K, V> Map<K, V> map(){  

3.         return new HashMap<K, V>();  

4. }  

5. public static <T> List<T> list(){  

6.     return new ArrayList<T>() ;  

7. }  

8. public static <T> LinkedList<T> lList(){  

9.     return new LinkedList<T>();  

10. }  

11. public static <T> Set<T> set(){  

12.     return new HashSet<T>();  

13. }  

14. public static <T> Queue<T> queue(){  

15.     return new LinkedList<T>() ;  

16. }  

17. ;public static void main(String[] args){  

18.     Map<String, List<String>> sls = New.map();  

19.     List<String> ls = New.list();  

20.     LinkedList<String> lls = New.lList();  

21.     Set<String> ss = New.set();  

22.     Queue<String> qs = New.queue();  

23. }  

24. }  

 

(2).Java中的Set集合是数学上逻辑意义的集合,使用泛型可以很方便地对任何类型的Set集合进行数学运算,代码如下:

[java] view plaincopy

1. public class Sets{  

2.     //并集  

3.     public static <T> Set<T> union(Set<T> a, Set<T> b){  

4.         Set<T> result = new HashSet<T>(a);  

5.         result.addAll(b);  

6.         return result;  

7. }  

8. //交集  

9. public static <T> Set<T> intersection(Set<T> a, Set<T> b){  

10.     Set<T> result = new HashSet<T>(a);  

11.     result.retainAll(b);  

12.     return result;  

13. }  

14. //差集  

15. public static <T> Set<T> difference(Set<T> a, Set<T> b){  

16.     Set<T> result = new HashSet<T>(a);  

17.     result.removeAll(b);  

18.     return Result;  

19. }  

20. //补集  

21. public static <T> Set<T> complement(Set<T> a, Set<T> b){  

22.     return difference(union(a, b), intersection(a, b));  

23. }  

24. }  

 

 

8——泛型编程高级 

1.泛型边界:

Java泛型编程时,编译器忽略泛型参数的具体类型,认为使用泛型的类、方法对Object都适用,这在泛型编程中称为类型信息檫除。

例如:

[java] view plaincopy

1. class GenericType{  

2.     public static void main(String[] args){  

3.         System.out.println(new ArrayList<String>().getClass());  

4.         System.out.println(new ArrayList<Integer>().getClass());  

5. }  

6. }  

 

输出结果为:

java.util.ArrayList

java.util.ArrayList

泛型忽略了集合容器中具体的类型,这就是类型檫除。

但是如果某些泛型的类/方法只想针对某种特定类型获取相关子类应用,这时就必须使用泛型边界来为泛型参数指定限制条件。

例如:

[java] view plaincopy

1. interface HasColor{  

2.     java.awt.Color getColor();  

3. }  

4. class Colored<T extends HasColor>{  

5.     T item;  

6.     Colored(T item){  

7.         this.item = item;  

8. }  

9. java.awt.Color color(){  

10.     //调用HasColor接口实现类的getColor()方法  

11.     return item.getColor();  

12. }  

13. }  

14. class Dimension{  

15.     public int x, y, z;  

16. }  

17. Class ColoredDimension<T extends Dimension & HasColor>{  

18.     T item;  

19.     ColoredDimension(T item){  

20.         this.item = item;  

21. }  

22. T getItem(){  

23.     return item;  

24. }  

25. java.awt.Color color(){  

26.     //调用HasColor实现类中的getColor()方法  

27.     return item.getColor();  

28. }  

29. //获取Dimension类中定义的x,y,z成员变量  

30. int getX(){  

31.     return item.x;  

32. }  

33. int getY(){  

34.     return item.y;  

35. }  

36. int getZ(){  

37.     return item.z;  

38. }  

39. }  

40. interface Weight{  

41.     int weight();  

42. }  

43. class Solid<T extends Dimension & HasColor & Weight>{  

44.     T item;  

45.     Solide(T item){  

46.         this.item = item;  

47. }  

48. T getItem(){  

49.     return item;  

50. }  

51. java.awt.Color color(){  

52.     //调用HasColor实现类中的getColor()方法  

53.     return item.getColor();  

54. }  

55. //获取Dimension类中定义的x,y,z成员变量  

56. int getX(){  

57.     return item.x;  

58. }  

59. int getY(){  

60.     return item.y;  

61. }  

62. int getZ(){  

63.     return item.z;  

64. }  

65. int weight(){  

66.     //调用Weight接口实现类的weight()方法  

67.     return item.weight();  

68. }  

69. }  

70. class Bounded extends Dimension implements HasColor, Weight{  

71.     public java.awt.Color getColor{  

72.         return null;  

73. }  

74. public int weight(){  

75.     return 0;  

76. }  

77. }  

78. public class BasicBounds{  

79.     public static void main(String[] args){  

80.         Solid<Bounded> solid = new Solid<Bounded>(new Bounded());  

81.         solid.color();  

82.         solid.getX();  

83.         solid.getY();  

84.         solid.getZ();  

85.         solid.weight();  

86. }  

87. }  

 

Java泛型编程中使用extends关键字指定泛型参数类型的上边界(后面还会讲到使用super关键字指定泛型的下边界),即泛型只能适用于extends关键字后面类或接口的子类。

Java泛型编程的边界可以是多个,使用如<T extends A & B & C>语法来声明,其中只能有一个是类,并且只能是extends后面的第一个为类,其他的均只能为接口(和类/接口中的extends意义不同)。

使用了泛型边界之后,泛型对象就可以使用边界对象中公共的成员变量和方法。

2.泛型通配符:

泛型初始化过程中,一旦给定了参数类型之后,参数类型就会被限制,无法随着复制的类型而动态改变,如:

[java] view plaincopy

1. class Fruit{  

2. }  

3. class Apple extends Fruit{  

4. }  

5. class Jonathan extends Apple{  

6. }  

7. class Orange extends Fruit{  

8. }  

9. 如果使用数组:  

10. public class ConvariantArrays{  

11.     Fruit fruit = new Apple[10];  

12.     Fruit[0] = new Apple();  

13.     Fruit[1] = new Jonathan();  

14.     try{  

15.         fruit[0] = new Fruit();  

16. }catch(Exception e){  

17.     System.out.println(e);  

18. }  

19. try{  

20.         fruit[0] = new Orange();  

21. }catch(Exception e){  

22.     System.out.println(e);  

23. }  

24. }  

 

编译时没有任何错误,运行时会报如下异常:

java.lang.ArrayStoreException:Fruit

java.lang.ArrayStoreException:Orange

为了使得泛型在编译时就可以进行参数类型检查,我们推荐使用java的集合容器类,如下:

[java] view plaincopy

1. public class NonConvariantGenerics{  

2.     List<Fruit> flist = new ArrayList<Apple>();  

3. }  

 

很不幸的是,这段代码会报编译错误:incompatible types,不兼容的参数类型,集合认为虽然Apple继承自Fruit,但是List的Fruit和List的Apple是不相同的,因为泛型参数在声 明时给定之后就被限制了,无法随着具体的初始化实例而动态改变,为解决这个问题,泛型引入了通配符”?”。

对于这个问题的解决,使用通配符如下:

[java] view plaincopy

1. public class NonConvariantGenerics{  

2.     List<? extends Fruit> flist = new ArrayList<Apple>();  

3. }  

 

泛型通配符”?”的意思是任何特定继承Fruit的类,java编译器在编译时会根据具体的类型实例化。

另外,一个比较经典泛型通配符的例子如下:

public class SampleClass < T extendsS> {…}

假如A,B,C,…Z这26个class都实现了S接口。我们使用时需要使用到这26个class类型的泛型参数。那实例化的时候怎么办呢?依次写下

SampleClass<A> a = new SampleClass();

SampleClass<B> a = new SampleClass();

SampleClass<Z> a = new SampleClass();

这显然很冗余,还不如使用Object而不使用泛型,使用通配符非常方便:

SampleClass<? Extends S> sc = newSampleClass();

3.泛型下边界:

在1中大概了解了泛型上边界,使用extends关键字指定泛型实例化参数只能是指定类的子类,在泛型中还可以指定参数的下边界,是一super关键字可以指定泛型实例化时的参数只能是指定类的父类。

例如:

[java] view plaincopy

1. class Fruit{  

2. }  

3. class Apple extends Fruit{  

4. }  

5. class Jonathan extends Apple{  

6. }  

7. class Orange extends Fruit{  

8. }  

9. public superTypeWildcards{  

10.     public static void writeTo(List<? super Apple> apples){  

11.         apples.add(new Apple());  

12.         apples.add(new Jonathan());  

13. }  

14. }  

 

通过? Super限制了List元素只能是Apple的父类。

泛型下边界还可以使用<?super T>,但是注意不能使用<Tsuper A>,即super之前的只能是泛型通配符,如:

[java] view plaincopy

1. public class GenericWriting{  

2.     static List<Apple> apples = new ArrayList<Apple>();  

3.     static List<Fruit> fruits = new ArrayList<Fruit>();  

4.     static <T> void writeExact(List<T> list, T item){  

5.         list.add(item);  

6. }  

7. static <T> void writeWithWildcards(List<? super T> list, T item){  

8.     list.add(item);  

9. }  

10. static void f1(){  

11.     writeExact(apples, new Apple());  

12. }  

13. static void f2(){  

14. writeWithWildcards(apples, new Apple());  

15.     writeWithWildcards(fruits, new Apple());  

16. }  

17. public static void main(String[] args){  

18.     f1();  

19.     f2();  

20. }  

21. }  

 

4.无边界的通配符:

泛型的通配符也可以不指定边界,没有边界的通配符意思是不确定参数的类型,编译时泛型檫除类型信息,认为是Object类型。如:

[java] view plaincopy

1. public class UnboundedWildcard{  

2.     static List list1;  

3.     static List<?> list2;  

4.     static List<? extends Object> list3;  

5.     static void assign1(List list){  

6.         list1 = list;  

7.         list2 = list;  

8.         //list3 = list; //有未检查转换警告  

9. }   

10. static void assign2(List<?> list){  

11.         list1 = list;  

12.         list2 = list;  

13.     list3 = list;  

14. }  

15. static void assign3(List<? extends Object> list){  

16.         list1 = list;  

17.         list2 = list;  

18.     list3 = list;  

19. }  

20. public static void main(String[] args){  

21.     assign1(new ArrayList());  

22. assign2(new ArrayList());  

23. //assign3(new ArrayList()); //有未检查转换警告  

24. assign1(new ArrayList<String>());  

25. assign2(new ArrayList<String>());  

26. assign3(new ArrayList<String>());   

27. List<?> wildList = new ArrayList();  

28. assign1(wildList);  

29. assign2(wildList);  

30. assign3(wildList);   

31. }  

32. }  

 

List和List<?>的区别是:List是一个原始类型的List,它可以存放任何Object类型的对象,不需要编译时类型检 查。List<?>等价于List<Object>,它不是一个原始类型的List,它存放一些特定类型,只是暂时还不确定是什 么类型,需要编译时类型检查。因此List的效率要比List<?>高。

5.实现泛型接口注意事项:

由于泛型在编译过程中檫除了参数类型信息,所以一个类不能实现以泛型参数区别的多个接口,如:

[java] view plaincopy

1. interface Payable<T>{  

2. }  

3. class Employee implements Payable<Employee>{  

4. }  

5. class Hourly extends Employee implements Payable<Hourly>{  

6. }  

 

类Hourly无法编译,因为由于泛型类型檫除,Payable<Employee>和Payable<Hourly>在编译时是同一个类型Payable,因此无法同时实现一个接口两次。

6.泛型方法重载注意事项:

由于泛型在编译时将参数类型檫除,因此以参数类型来进行方法重载在泛型中要特别注意,如:

[java] view plaincopy

1. public class GenericMethod<W,T>{  

2.     void f(List<T> v) {  

3. }  

4. void f(List<W> v){  

5. }  

6. }  

 

无法通过编译,因为泛型檫除类型信息,上面两个方法的参数都被看作为Object类型,使用参数类型已经无法区别上面两个方法,因此无法重载。

7.泛型中的自绑定:

通常情况下,一个类无法直接继承一个泛型参数,但是你可以通过继承一个声明泛型参数的类,这就是java泛型编程中的自绑定,如:

[java] view plaincopy

1. class SelfBounded<T extends SelfBounded<T>>{  

2.     T element;  

3.     SelfBounded<T> set(T arg){  

4.         Element = arg;  

5.         return this;  

6. }   

7. T get(){  

8.     return element;  

9. }  

10. }  

11. class A extends SelfBounded<A>{  

12. }  

13. class B extends SelfBounded<A>{  

14. }  

15. class C extends SelfBounded<C>{  

16.     C setAndGet(C arg){  

17.         set(arg);  

18.         return get();  

19. }  

20. }  

21. public class SelfBounding{  

22.     public static void main(String[] args){  

23.         A a = new A();  

24.         a.set(new A());  

25.         a = a.set(new A()).get();  

26.         a = a.get();  

27.         C c = new C();  

28.         C = c.setAndGet(new C());  

29. }  

30. }  

 

泛型的自绑定约束目的是用于强制继承关系,即使用泛型参数的类的基类是相同的,强制所有人使用相同的方式使用参数基类。

9——集合容器高级 

1.Arrays.asList()方法产生的List是一个固定长度的数组,只支持不改变长度的操作,任何试图改变其底层数据结构长度的操作(如,增加,删除等操作)都会抛出UnsupportedOperationException异常。

为了使Arrays.asList()方法产生的List集合长度可变,可以将其作为集合容器的构造方法参数,如:

Set set = new HashSet(Arrays.asList(newint[]{1,23}));

或者将其作为Collections.addAll()方法的参数,如:

Collections.addAll(Arrays.asList(new int[]{1,23}));

2.SortedSet是一个对其元素进行排序了的Set,SortedSet接口有以下方法:

(1).Comparator comparator():

返回此Set中元素进行排序的比较器,如果该方法返回null,则默认使用自然排序。

(2).Object first():

返回Set中第一个(最低)元素。

(3).Object last():

返回Set中最后一个(最高)元素。

(4).SortedSet subset(fromElement, toElement):

返回此Set中从fromElement(包括)到toElement(不包括)的子Set。

(5).SortedSet headset(toElement):

返回此Set中元素严格小于toElement的子Set。

(6).SortedSet tailSet(fromElement):

返回此Set中元素大于等于fromElement的子Set。

3.SortedMap是一个根据Key排序的Map,SortedMap接口有以下方法:

(1).Comparator comparator():

返回此Map中key进行排序的比较器,如果返回的是null,则该Map的key使用自然排序。

(2).T firstKey():

返回此Map中第一个(最低)key。

(3).T lastKey();

返回此Map中最后一个(最高)key。

(4).SortedMap subMap(fromKey, toKey):

返回此Map中key从fromKey(包括)到toKey(不包括)的子Map。

(5).SortedMap headMap(toKey):

返回此Map中key严格小于toKey的子Map。

(6).SortedMap tailMap(fromKey):

返回此Map中key大于等于fromKey的Map。

4.HashMap/HashSet等Hash算法集合重写equals方法和hashCode方法:

HashSet,HashMap以及它们的子类等这些使用Hash算法的集合存放对象时,如果元素不是java中的8种基本类型(即元素都是对象类型),则必须重写对象的equals和hashCode方法,否则会产生一些错误,例如:

[java] view plaincopy

1. Class A{  

2.     int  i;  

3.     public A(int i){  

4.     this.i = i;  

5. }  

6. Public static void main(String args[]){  

7.         Map map = new HashMap();  

8.     map.put(new A(1), “First”);  

9.     map.put(new A(2), “Second”);  

10.     A a = new A(1);  

11.     boolean b = map.containsKey(a);  

12.     System.out.println(b);  

13. }  

14. }  

 

输出的结果是:false。

Map中有Key为A(1)对象,但是却没有找到,这是因为HashMap使用Hash算法根据对象的hashCode值来查找给定的对象,如果没 有重写hashCode和equals方法,则对象默认使用Object的hashCode方法,Object默认hashCode方法使用对象的内存地 址作为hashCode值。

为了使Hash算法集合存放对象类型数据符合用户的期望,必须重写对象的hashCode和equals方法,其中hashCode方法用于Hash算法的查找,equals方法用于对象比较,Hash算法中还要用到equals方法如下:

因为Hash算法所使用的hashCode可能会产生碰撞(不相等对象的hashCode值相同),当Hash算法产生碰撞时,就需要再次 Hash(即再次通过其他方法计算hashCode值),所以一个对象使用Hash算法时,其对应的HashCode值可能不止一个,而是一组,当产生碰 撞时就选择另一个hashCode值。当hashCode值产生碰撞时,还必须使用equals方法方法对象是否相等。

注意:由于Hash算法有可能会产生碰撞(不相等的对象hashCode值相同),所以hashCode和equals方法有如下关系:

(1).equals方法相等的对象,hashCode方法值一定相同。

(2).hashCode方法相同的对象,equals不一定相等。

5.创建只读集合容器:

List,Set和Map类型的集合容器都可以通过下面的方法创建为只读,即只可以访问,不能添加,删除和修改。

[java] view plaincopy

1. static Collection<String> data = new ArrayList<String>();  

2. data.add(“test”);  

3. static Map<String, String> m = new HashMap<String, String>();  

4. m.put(“key”, “value”);  

 

(1).只读集合:

[java] view plaincopy

1. Collection<String> c = Collections.unmodifiableCollection(new ArrayList<String>(data));  

2. System.out.println(c); //可以访问  

3. //c.add(“test2”);只读,不可添加  

 

(2).只读List:

[java] view plaincopy

1. List<String> list = Collections.unmodifiableList(new ArrayList<String>(data));  

2. System.out.println(list.get(0)); //可以访问  

3. //list.remove(0);只读,不可删除  

 

(3).只读Set:

[java] view plaincopy

1. Set<String> set = Collections.unmodifiableSet(new HashSet<String>(data));  

2. System.out.println(set.Iterator().next()) //可以访问  

3. //set.add(“test”);只读,不可添加  

 

(4).只读Map:

[java] view plaincopy

1. Map<String, String> map = Collections.unmodifiableMap(new HashMap<String, String>(m));  

2. System.out.println(map.get(“key”)); //可以访问  

3. //map.put(“key2”, “value2”);只读,不可添加  

 

只读集合容器会在编译时检查操作,如果对只读集合容器进行增删等操作时,将会抛出UnSupportedOperationException异常。

只读集合容器类似于将集合对象访问控制修饰符设置为private,不同之处在于,其他类可以访问,只是不能修改。

6.线程同步集合容器:

Java集合容器中,Vector,HashTable等比较古老的集合容器是线程安全的,即处理了多线程同步问题。

而Java2之后对Vector和HashTable的替代类ArrayList,HashSet,HashMap等一些常用的集合容器都是非线程安全的,即没有进行多线程同步处理。

Java中可以通过以下方法方便地将非线程安全的集合容器进行多线程同步:

(1).线程同步集合:

Collection<String> c= Collections.synchronizedCollection(newArrayList<String>());

(2).线程同步List:

List<String> c= Collections.synchronizedList(newArrayList<String>());

(3).线程同步Set:

Set<String> c= Collections.synchronizedSet(newHashSet<String>());

(4).线程同步Map:

Map<String> c= Collections.synchronizedMap(newHashMap<String, String>());

7.对象的强引用、软引用、弱引用和虚引用:

JDK1.2以前版本中,只存在一种引用——正常引用,即对象强引用,如果一个对象被一个引用变量所指向,则该对象是可触及(reached)的状态,JVM垃圾回收器不会回收它,弱一个对象不被任何引用变量指向,则该对象就处于不可触及的状态,垃圾回收器就会回收它。

从JDK1.2版本之后,为了更灵活的控制对象生命周期,引入了四种引用:强引用(java.lang.ref.Reference)、软引用 (java.lang.ref.SoftReference)、弱引用(java.lang.ref.WeakReference)和虚引用 (java.lang.ref.PhantomReference):

(1). 强引用(java.lang.ref.Reference):

    即Java程序中普遍使用的正常对象引用,存放在内存中得对象引用栈中,如果一个对象被强引用,则说明程序还在使用它,垃圾回收器不会回收,当内存不足时,JVM抛出内存溢出异常使程序异常终止。

(2). 软引用(java.lang.ref.SoftReference):

如果一个对象只具有软引用,则内存空间足够,垃圾回收器也不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。

软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,JVM就会把这个软引用加入到与之关联的引用队列中。

(3). 弱引用(java.lang.ref.WeakReference):

弱引用用来实现内存中对象的标准映射,即为了节约内存,对象的实例可以在一个程序内多处使用。

弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程 中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的后台线程,因此不一定会很快发 现那些只具有弱引用的对象。

弱引用也可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。

(4). 虚引用(java.lang.ref.PhantomReference):

“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。

虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。

ReferenceQueue queue = new ReferenceQueue (); 

PhantomReference pr = new PhantomReference (object, queue); 

   JVM中,对象的引用往往都是很复杂的,各个对象之间相互引用形成一个内存引用树,java中某个对象是否可触及,有以下两条判断原则:

a.单条引用路径可及性判断:在这条路径中,最弱的一个引用决定对象的可及性。

b.多条引用路径可及性判断:几条路径中,最强的一条的引用决定对象的可及性。

软引用,弱引用,虚引用例子如下:

[java] view plaincopy

1. package com.test.reference;  

2. //测试对象  

3. class VeryBig {  

4.     private static final int SIZE = 10000;  

5.     private long[] la = new long[SIZE];  

6.     private String ident;  

7.   

8.     public VeryBig(String id) {  

9.         ident = id;  

10.     }  

11.   

12.     public String toString() {  

13.         return ident;  

14.     }  

15.   

16.     protected void finalize() {  

17.         System.out.println("Finalizing " + ident);  

18.     }  

19. }  

20.   

21. package com.test.reference;  

22.   

23. import java.lang.ref.PhantomReference;  

24. import java.lang.ref.Reference;  

25. import java.lang.ref.ReferenceQueue;  

26. import java.lang.ref.SoftReference;  

27. import java.lang.ref.WeakReference;  

28. import java.util.LinkedList;  

29.   

30. public class TestReferences {  

31.     //引用队列  

32.     private static ReferenceQueue<VeryBig> rq = new ReferenceQueue<VeryBig>();  

33.     //检查引用队列是否为空  

34.     public static void checkQueue() {  

35.         //强引用,轮询引用队列,看是否有可以的对象引用  

36.         Reference<? extends VeryBig> inq = rq.poll();  

37.         if (inq != null)  

38.             //如果有可以使用的对象引用,则打印出该引用指向的对象  

39.             System.out.println("In queue: " + inq.get());  

40.     }  

41.   

42.     public static void main(String[] args) {  

43.         int size = 2;  

44.         //创建存放VeryBig对象的软引用集合  

45.         LinkedList<SoftReference<VeryBig>> sa = new LinkedList<SoftReference<VeryBig>>();  

46.         for (int i = 0; i < size; i++) {  

47.             //将对象和软引用添加到引用队列中  

48.             sa.add(new SoftReference<VeryBig>(new VeryBig("Soft " + i), rq));  

49.             System.out.println("Just created: " + sa.getLast());  

50.             checkQueue();  

51.         }  

52. //创建存放VeryBig对象的弱引用集合  

53.         LinkedList<WeakReference<VeryBig>> wa = new LinkedList<WeakReference<VeryBig>>();  

54.         for (int i = 0; i < size; i++) {  

55.             //将对象和弱引用添加到引用队列中  

56.             wa.add(new WeakReference<VeryBig>(new VeryBig("Weak " + i), rq));  

57.             System.out.println("Just created: " + wa.getLast());  

58.             checkQueue();  

59.         }  

60.         SoftReference<VeryBig> s = new SoftReference<VeryBig>(new VeryBig(  

61.                 "Soft"));  

62.         WeakReference<VeryBig> w = new WeakReference<VeryBig>(new VeryBig(  

63.                 "Weak"));  

64.         //垃圾回收器回收,在回收之前调用对象的finalize()方法  

65.         System.gc();  

66. //创建存放VeryBig对象的虚引用集合  

67.         LinkedList<PhantomReference<VeryBig>> pa = new LinkedList<PhantomReference<VeryBig>>();  

68.         for (int i = 0; i < size; i++) {  

69.             //将对象和虚引用添加到引用队列中  

70.             pa.add(new PhantomReference<VeryBig>(new VeryBig("Phantom " + i),  

71.                     rq));  

72.             System.out.println("Just created: " + pa.getLast());  

73.             checkQueue();  

74.         }  

75.     }  

76. }  

 

输出结果为:

Just created:java.lang.ref.SoftReference@757aef

Just created:java.lang.ref.SoftReference@d9f9c3

Just created:java.lang.ref.WeakReference@9cab16

Just created:java.lang.ref.WeakReference@1a46e30

In queue: null

Finalizing Weak 0

Finalizing Weak

Finalizing Weak 1

Just created:java.lang.ref.PhantomReference@3e25a5

In queue: null

Just created:java.lang.ref.PhantomReference@19821f

注意:由于System.gc()只是通知JVM虚拟机可以进行垃圾回收器可以进行垃圾回收了,但是垃圾回收器具体什么时候允许说不清楚,所以这个输出结果只是个参考,每次运行的结果Finalize方法的执行顺序不太一样。

从程序可以看出,尽管对象被引用,垃圾回收器还是回收了被引用对象,引用队列总是创建一个包含null对象的引用。

8.WeakHashMap:

WeakHashMap专门用于存放弱引用,WeakHashMap很容易实现弱引用对象标准映射功能。

在WeakHashMap中,只存储一份对象的实例及其值,当程序需要对象实例值时,WeakHashMap从现有的映射中找出已存在的对象值映射。

由于弱引用节约内存的技术,WeakHashMap允许垃圾回收器自动清除器存放的key和value。WeakHashMap自动将其中存放的key和value包装为弱引用,当key不再被使用时,垃圾回收器自动回收该key和value。

 

10——文件和目录常用操作 

1.文件目录的List操作:

Java中,File类其实代表文件的路径,它既可以代表一个特定文件的文件,也可以代表一个包含多个文件的目录名称。如果File代表目录,可以使用List列出目录中文件。

[java] view plaincopy

1. import  java.util.regex.*;  

2. import  java.io.*;  

3. import  java.util.*;  

4. public class DirList{  

5.     public static void main(String[] args){  

6.         //当前目录  

7.         File path = new File(“.”);  

8.         String[] list;  

9.         //如果没有指定参数,则将目录中文件全部列出  

10.         if(args.length == 0){  

11.     list = path.list();  

12. }  

13. //指定了参数,则根据指定文件名过滤符合条件的文件  

14. else{  

15.     list = path.list(new DirFilter(args[0]));  

16. }  

17. Arrays.sort(list, String.CASE_INSENSITIVE_ORDER);  

18. for(String dirItem : list){  

19.     System.out.println(dirItem);  

20. }  

21. }  

22. }  

23. class DirFilter implements FilenameFilter{  

24.     private Pattern pattern;  

25.     public DirFilter(String regex){  

26.         //将输入的命令行参数编译为正则表达式的模式串  

27.         pattern = Pattern.compile(regex);  

28. }  

29. //File的List方法回调方法  

30. public boolean accept(File dir, String name){  

31.     //使用正则表达式匹配给定目录下的文件名  

32.     return pattern.matcher(name).matches();  

33. }   

34. }  

 

命令行输入参数:“*\.java”

输出结果如下:

DirFilter.java

DirList.java

2.java中除了File类可以表示文件的路径外,还可以表示目录的路径,可以通过File的isDirectory判断File对象是一个文件还是一个目录。

如下的例子通过local()方法列出给定目录中符合条件的文件/目录名称,walk()方法遍历给定的目录:

[java] view plaincopy

1. import java.util.regex.*;  

2. import java.io.*;  

3. import java.util.*;  

4.   

5. public final class Directory{  

6.     //列出目录中符合条件的文件名  

7.     public static File[] local(File dir, final String regex){  

8.         return dir.listFiles(new FilenameFilter(){  

9.             private Pattern pattern = Pattern.compile(regex);  

10.             public Boolean accept(File dir, String name){  

11.                 return pattern.matcher(new File(name).getName()).matches();  

12. }  

13. });  

14. }  

15. //重载列出目录下符合条件的文件名方法  

16. public static File[] local(String path, final String regex){  

17.     return local(new File(path), regex);  

18. }   

19. //代表文件树信息的静态内部类  

20. public static class TreeInfo implements Iterable<File>{  

21.     public List<File> files = new ArrayList<File>();  

22.     public List<File> dirs = new ArrayList<File>();  

23.     //默认的迭代器方法,跌倒文件树元素对象  

24.     public Iterator<File> iterator(){  

25.         return files.iterator();  

26. }   

27. void addAll(TreeInfo other){  

28.     files.addAll(other.files);  

29.     dirs.addAll(other.dirs);  

30. }  

31. public String toString(){  

32.     return “dirs: ” + dirs + “\n\nfiles: ” + files;  

33. }  

34. }  

35. //从指定的文件/目录开始遍历符合条件的文件  

36. public static TreeInfo walk(String start, String regex){  

37.     return recurseDirs(new File(start), regex);  

38. }  

39. //重载遍历文件/目录方法  

40. \public static TreeInfo walk(File start, String regex){  

41.     return recurseDirs(start, regex);  

42. }  

43. //默认的指定文件/目录查找任何文件名的文件  

44. public static TreeInfo walk(File start){  

45.     return recurseDirs(start, “.*”);  

46. }  

47. //重载默认的查找任何文件的方法  

48. public static TreeInfo walk(String start){  

49.     return recurseDirs(new File(start), “.*”);  

50. }   

51. //从指定的文件/目录开始遍历,查找符合条件的文件名  

52. static TreeInfo recurseDirs(File startDir, String regex){  

53.     TreeInfo result = new TreeInfo();  

54.     for(File item : startDir.listFiles()){  

55.         //如果遍历的文件是目录      

56. if(item.isDirectory()){  

57.     result.dirs.add(item);  

58.     //迭代子目录  

59.     result.addAll(recurseDirs(item, regex));  

60. }  

61. //如果遍历的的文件是普通文件  

62. else{  

63.     if(item.getName().matches(regex)){  

64.         result.files.add(item);  

65. }  

66. }  

67. }  

68. return result;  

69. }  

70. }  

 

3.文件和目录的其他操作:

文件和目录除了常规的查找和遍历操作意外,还有很多其他的操作,例如:创建、删除、判断文件/目录是否已存在,获取文件.目录的绝对路径,已经文件/目录的权限等等,下面的小例子就展示文件/目录的这些操作:

[java] view plaincopy

1. import java.io.*;  

2. public class MakeDirectories{  

3.     //获取文件/目录的基本信息  

4. private static void fileData(File f){  

5.         System.out.println(  

6.     “Absolute path: ” + f.getAbsolutePath() +  

7.     “\n Can read: ” + f.canRead() +  

8.     “\n Can write: ” + f.canWrite() +  

9.     “\n getName: ” + f.getName() +   

10.     “\n getParent: ” + f.getParent() +  

11.     “\n getPath: ” + f.getPath() +  

12.     “\n length: ” + f.length() +  

13.     “\n lastModified: ” + f.lastModifed());  

14. if(f.isFile()){  

15.     System.out.println(f.getName() + “ is a file”);  

16. }  

17. else if(f.isDirectory()){  

18. System.out.println(f.getName() + “ is a directory”);  

19. }  

20. }  

21. public static void main(String[] args){  

22.     File old = new File(“oldFile”);  

23.     File new = new File(“newFile”);  

24.     old.renameTo(new);  

25.     fileData(old);  

26.     fileData(new);  

27.     File d = new File(“/test”);  

28.     if(d.exists()){  

29.     System.out.println(“Deleting …” + d);  

30.     d.delete();  

31. }  

32. else {  

33.     System.out.prinln(“Creating…” + d);  

34.     d.mkdirs();  

35. }  

36. }  

37. }  

 

11——Java I/O 

Java中使用流来处理程序的输入和输出操作,流是一个抽象的概念,封装了程序数据于输入输出设备交换的底层细节。JavaIO中又将流分为字节流和字符流,字节流主要用于处理诸如图像,音频视频等二进制格式数据,而字符流主要用于处理文本字符等类型的输入输出。

1.字节输入流InputStream

输入流InputStream负责从各种数据/文件源产生输入,输入源包括:数组,字符串,文件,管道,一系列其他类型的流,以及网络连接产生的流等等。

常用字节输入流的主要类型:

(1).ByteArrayInputStream字节数组输入流:

主要功能:允许内存缓存作为输入流。

ByteArrayInputStream包含一个内部缓冲区,该缓冲区包含从流中读取的字节。内部计数器跟踪read()方法要提供的下一个字节。

注意:关闭ByteArrayInputStream无效,该类中的方法在关闭此流之后仍可被调用,而不会产生任何的IOException。

(2).FileInputStream文件输入流:

主要功能:从文件系统中的某个文件获得输入字节,用于读取诸如图像数据子类的原始字节流。若要读取字符流,请使用FileReader。

(3).PipedInputStream管道输入流:

主要功能:和管道输出流一起构成一个输入输出的管道,是管道的数据输入端。

管道输入流应该连接到管道输出流,管道输入流提供要写入管道输出流的所有数据字节。通常,这些数据有某个线程从PipedInputStream对象中读取,并有其他线程将其写入到相应的PipedOutputStream对象中。

注意:不建议PipedInputStream和PipedOutputStream对象使用单线程,因为这样可能思索线程。管道输入流包含一个缓 冲区,可以在缓冲区限定范围内将读操作和写操作分离开,如果先连接管道输出流提供数据字节的线程不再存在,则认为该管道已损坏。

(4).SequenceInputStream顺序输入流:

重要功能:将两个或多个输入流对象转换为一个单个输入流对象。

SequenceInputStream表示其他输入流的逻辑串联关系,它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,以此类推,直到到达包含的最后一个输入流的文件末尾为止。

(5).FilterInputStream过滤输入流:

主要功能:包含其他一些输入流,将这些被包含的输入流用作其基本数据源,它可以直接传输数据或者提供一些额外的功能。

常用的FilterInputStream是DataInputStream数据输入流,主要用于允许程序以与机器无关的方式从底层输入流中读取java基本数据类型。其常用的方法有readInt(),readBoolean(),readChar()等等。

2.字节输出流OutputStream:

和字节输入流相对应,字节输出流负责字节类型数据想目标文件或设备的输出。常见的字节输出流如下:

(1).ByteArrayOutputStream字节数组输出流:

主要功能:在内存中创建一个缓冲区,将接收到的数据放入该内存缓冲区中。

ByteArrayOutputStream实现了一个输出流,其中的数据被写入一个byte数组中。缓冲区会随着数据的不断写入而自动增长,可使用toByteArray()和toString()方法获取数据。

注意:和ByteArrayInputStream类似,关闭ByteArrayOutputStream也是无效的,此类中的方法在关闭此流后仍可被调用,而不会产生任何IOException。

(2).FileOutputStream文件输出流:

主要功能:将数据写入到指定文件中。

文件输出流是用于将数据写入File或FIleDescriptor的输出流,用于写入诸如图像数据之类的原始字节流,若要写入字符流,请使用FileWriter。

(3).PipedOutputStream管道输出流:

主要功能:连接管道输入流用来创建通信管道,管道输出流是管道数据输出端。

(4).FilterOutputStream过滤输出流:

主要功能:用于将已存在的输出流作为其基本数据接收器,可以直接传输数据或提供一些额外的处理。

常用的FIlterOutputStream是DataOutputStream数据输出流,它允许程序以适当的方式将java基本数据类型写入输 出流中。其常用方法有writeInt(intV),writeChar(int v),writeByte(String s)等等。

3.字符流:

Java中得字节流只能针对字节类型数据,即支持处理8位的数据类型,由于java中的是Unicode码,即两个字节代表一个字符,于是在JDK1.1之后提供了字符流Reader和Writer。

字符流相关常用类如下:

(1).Reader:

用于读取字符串流的抽象类,子类必须实现的方法只有reader(char[],int, int)和close()。

(2).InputStreamReader:

是将字节输入流转换为字符输入流的转换器,它使用指定的字符集读取字节并将其解码为字符。即:字节——>字符。

它使用的字符集可以有名称指定或显式给定,也可以使用平台默认的字符集。

(3).Writer:

用于写入字符流的抽象类,子类必须实现的方法只有write(char[],int, int)和close()。

(4).OutputStreamWriter:

是将字符输出流转换为字节输出流的转换器,它使用指定的字符集将要写入流的字符编码成字节。即:字符——>字节。

4.综合使用java IO各种流:

Java IO中的各种流,很少单独使用,经常结合起来综合使用,既可以满足特定需求,又搞效。例子如下:

(1).使用缓冲流读取文件:

[java] view plaincopy

1. import java.io.*;  

2.   

3. public class BufferedInputFile{  

4.     public static String read(String filename) throws IOException{  

5.         //缓冲字符输入流  

6.     BufferedReader in = new BufferedReader(new FileReader(filename));  

7.     String s;  

8.     StringBuilder sb = new StringBuilder();  

9.     //每次读取文件中的一行  

10.     While((s = in.readLine()) != null){  

11.     sb.append(s + “\n”);  

12. }  

13. in.close();  

14. return sb.toString();  

15. }  

16. public static void main(String[] args) throws IOException{  

17.     System.out.println(read(“BufferedInputFile.java”));  

18. }  

19. }  

 

(2).读取内存中的字符串:

[java] view plaincopy

1. import java.io.*;  

2.   

3. public class MemoryInput{  

4.     public static void main(String[] args) throws IOException{  

5.         //将字符串包装为字符输入流  

6.         StringReader in = new StringReader(  

7.   BufferedInputFile.read(“BufferedInputFile.java”));  

8.         int c;  

9.         //读取字符输入流中的字符  

10.         while((c == in.read()) != -1){  

11.     System.out.println((char)c);  

12. }  

13. }  

14. }  

 

(3).数据输入/输出流:

[java] view plaincopy

1. import java.io.*;  

2.   

3. public class DataInputOutput{  

4.     public static void main(String[] args) thows IOException{  

5.         DataOutputStream out = new DataOutputStream(new BufferedOutputStream(  

6. new FileOutputStream(“Data.txt”)));  

7.         out.writeDouble(3.14159);  

8.         out.writeUTF(“That was pi”);  

9.         out.writeDouble(1.41413);  

10.         out.writeUTF(“Square root of 2”);  

11.         out.close();  

12.         DataInputStream in = new DataInputStream(new BufferedInputStream(  

13. new FileOutputStream(“Data.txt”)));  

14.         System.out.println(in.readDouble());  

15.         System.out.println(in.readUTF());  

16. System.out.println(in.readDouble());  

17.         System.out.println(in.readUTF());  

18. }  

19. }  

 

输出结果:

3.14159

That was pi

1.41413

Square root of 2

(4).文本文件输出流:

[java] view plaincopy

1. import java.io.*;  

2.   

3. public class TextFileOutput{  

4.     //输出文件名  

5.     static String file = “BasicFileOutput.out”;  

6.     public static void main(String[] args) throws IOException{  

7.         //将字符串先包装成字符串输入流,然后将字符串输入流再包装成缓冲字符输入流  

8.     BufferedReader in = new BufferedReader(new StringReader  

9. (BufferedInputFile.read(“TextFileOutput.java”)));  

10.         //字符输出流  

11.         PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file)));  

12.         int lineCount = 1;  

13.         String s;  

14.         While((s = in.readLine()) != null){  

15.     out.println(lineCount++ + “: ” + s);  

16. }  

17. out.close();  

18. }  

19. }  

 

(5).二进制文件读写:

[java] view plaincopy

1. import java.io.*;  

2.   

3. public class BinaryFileOutput{  

4.     //输出文件名  

5.     static String file = “BinaryFileOutput.out”;  

6.     public static void main(String[] args) throws IOException{  

7.         //将字符串先包装成字符串输入流,然后将字符串输入流再包装成缓冲字符输入流  

8.     BufferedInputStream in = new BufferedInputStream(  

9. new FileInputStream(“TestFile.png”)));  

10.         //字符输出流  

11.         BufferedOutputStream out = new BufferedOutputStream (  

12. new FileOutputStream(file));  

13.         byte[] buf = new byte[1024];  

14.         int n;  

15.         While((n = in.read(buf)) > 0){  

16.     out.write(buf, 0, n);  

17. }  

18. out.close();  

19. }  

20. }  

 

12——Java new I/O(

为了提高Java I/O的速度和效率,从JDK1.4开始引入了java.nio.*包,即java new I/O(NIO)。
    事实上,为了利用java nio的速度和效率优势,原来的java I/O包中相关的类已经使用java nio重新实现,因此在编程中即使没有显式地使用java nio的代码,使用传统java I/O还是利用了nio的速度和效率优势。Java nio的速度提高主要在:文件I/O和网络I/O两个方面。
    Java nio的速度提升主要因为使用了类似于操作系统本身I/O的数据结构:I/O通道(Channel)和缓冲区。
1.Channel通道:
通道表示实体,如硬件设备、文件、网络套接字或可以执行的一个或多个不同的I/O操作(如读取或写入)的程序组件的开发的链接,用于I/O操作的链接。
通道可处于打开或关闭状态。创建通道时它处于打开状态,一旦将其关闭,则保持关闭状态。一旦关闭了某个通道,试图对其调用I/O操作都会抛出 ClosedChannelException异常,通过调用通道的isOpen()方法可以探测通道是否处于打开状态。一般情况下通道对于多线程的访问 是安全的。
2.ByteBuffer字节缓冲区:
字节缓冲区是nio中唯一直接和通道channel打交道的缓冲区。字节缓冲区可以存放原始字节,也可以存放java的8中基本类型数据,但是不能存放引 用类型数据,String也是不能存放的。正是由于这种底层的存放类型似的字节缓冲区可以更加高效和绝大部分操作系统的I/O进行映射。
字节缓冲区通过allocation()方法创建,此方法为该缓冲区的内容分配空间,或者通过wrapping方法将现有的字节数组包装到缓冲区中来创建。
字节缓冲区的常用操作:
(1).读写单个字节的绝对和相对get和put方法:
a. 绝对方法:
get(int index):读取指定索引处的字节。
put(int index, byte b):将字节写入指定索引处。
b.相对方法:
get():读取此缓冲区当前位置的字节,然后该位置递增。
put(byte b):将字节写入此缓冲区的当前位置,然后该位置递增。
(2).相对批量get方法:
ByteBuffer get(byte[] dst):将此缓冲区的字节传输到给定的目标数组中。
(3).相对批量put方法:
ByteBuffer put(byte[] src):将给定的源byte数组的所有内容传输到此缓冲区中。
(4).读写其他基本类型值:
getChar(), putChar(char value), getChare(int index), putChar(int index, char value).
getInt(), putInt(int value), getInt(int index), putInt(int index, int value)等等读写基本类型值得相对和绝对方法。
注意:基本类型值得相对和绝对读写方法,根据java基本类型数据底层字节数进行缓冲区移动。
(5).创建视图缓冲区:
为了访问同类二进制数据,允许将字节缓冲区视为包含它们基本类型值的缓冲区,视图缓冲区只是其内容受该字节缓冲区支持的另一种缓冲区。字节缓冲区和视图缓冲区内容更改是相互可见的。这两种缓冲区的位置、限制和标记值都是独立的。创建方法如下:
asCharBuffer(), asDoubleBuffer(), asFloatBuffer(), asIntBuffer(), asLongBuffer(), asReadOnlyBuffer(), asShortBuffer()。
与具体类型的get和put方法系列相比,视图缓冲区优势如下:
a视图缓冲区不是根据字节进行索引,而是根据其特定类型的值得大小进行索引。
b.视图缓冲区提供了相对批量get和put方法,这些方法可以在缓冲区和数组或相同类型的其他缓冲区直接传输连续序列的值。
c.视图缓冲区可能更搞效,因为当且仅当其支持的字节缓冲区为直接缓冲区时它才是直接缓冲区。
(6).缓冲区其他操作:
a.ByteBuffer compact():压缩缓冲区,从缓冲区写入数据之后调用,以防写入不完整。
b.ByteBuffer duplicate():创建共享此缓冲区内容的新的字节缓冲区,新缓冲区中的内容为此缓冲区的内容,此缓冲区和新缓冲区内容更改是相互可见的。
c.ByteBuffer slice():创建新的字节缓冲区,其内容是此缓冲区内容的共享子序列。
3.直接与非直接缓冲区:
字节缓冲区要么是直接的,要么是非直接的。
如果字节缓冲区是直接的,则JVM会尽最大努力直接在此缓冲区上执行本机的I/O操作,即在每次调用底层操作系统的一个本机I/O之前(或之后),JVM 都会尽量避免将缓冲区的内容复制到中间缓冲区中(或尽量避免从中间缓冲区复制内容)。直接字节缓冲区可以通过调用allocateDirect()方法来 创建,此方法返回的缓冲区进行分配和取消分配所需的成本通常高于非直接缓冲区。直接缓冲区的内容可以驻留在常规的垃圾回收堆之外,因此它对应用程序的内存 需求量造成的影响可能并不明显,所以建议将直接缓冲区主要分配给那些容易受底层操作系统的本机I/O操作影响的大型的、持久的缓冲区
可以通过调用isDirect()来判断字节缓冲区是直接的还是非直接的。
4.文件通道:

Java I/O的FileInputStream,FileOutputStream和RandomAccessFile可以产生文件通道,例子如下:

[java] view plaincopy

1. import java.nio.*;  

2. import java.nio.channels.*;  

3. import java.io.*;  

4.   

5. public class FileChannel{  

6.     //分配字节缓冲区时指定其大小  

7.     private static final int BSIZE = 1024;  

8.     public static void main(String[] args) throw Exception{  

9.         //获取FileOutputStram的文件通道  

10.         FileChannel fc = new FileOutputStream(“data.txt”).getChannel();  

11.         //向字节缓冲区中写入字节数组  

12.         fc.write(ByteBuffer.wrap(“Some text”.getBytes()));  

13.         fc.close();  

14.         //以读写方式获取随机访问文件的文件通道  

15.         fc = new RandomAccessFile(“data.txt”, “rw”).getChannel();  

16.         //定位到字节缓冲区当前内容之后  

17. fc.position(fc.size());  

18. //向字节缓冲区中追加内容  

19.         fc.write(ByteBuffer.wrap(“Some more”.getBytes()));  

20.         fc.close();  

21.         //获取FileInputStream的文件通道  

22.         fc = new FileInputStream(“data.txt”).getChannel();  

23.         //分配字节缓冲区  

24.         ByteBuffer buff = ByteBuffer.allocate(BSIZE);  

25.         //将字节数组从文件通道读入到字节缓冲区中  

26.         fc.read(buff);  

27.         //放置缓冲区,为缓冲区写出或相对获取做准备  

28.         buff.flip();  

29.         //判断缓冲区是是否还有元素  

30.         while(buff.hasRemaining()){  

31.             //获取字节缓冲区字节的相对方法  

32.             System.out.println((char)buff.get());  

33. }  

34. }  

35. }   

输出结果:
Some text Some more
传统java I/O中FileInputStream, FileOutputStream和RandomAccessFile三个类可以产生文件通道。
注意:Java new I/O的目标是提高I/O速度,快速移动大批量的数据,因此,字节缓冲区的大小非常重要,例子中的1K大小不一定是合适的,应用程序需要在生产环境中测试确定合适的缓冲区大小。
5.文件复制:
文件通道和字节缓冲区不但可以实现数据从文件读入和读出,还可以实现文件的复制,例子如下:

[java] view plaincopy

1. import java.nio.*;  

2. import java.nio.channels/*;  

3. import java.io.*  

4.   

5. public class FileCopy{  

6.     //字节缓冲区大小  

7.     private static final int BSIZE = 1024;  

8.     public static void main(String[] args) throws Exception{  

9.         //获取文件输入通道  

10.     FileChannel in = new FileInputStream(“FileCopy.java”).getChannel();  

11.     //获取文件输出通道  

12.     FileChannel out = new FileOutputStream(“FileOut.txt”).getChannel();  

13.     ByteBuffer buffer = ByteBuffer.allocate(BSIZE);  

14.     While(in.read(buffer) != -1){  

15.     //重置缓冲区,准备写出  

16. buffer.flip();  

17. //将字节缓冲区内容写出到文件输出通道  

18. out.write(buffer);  

19. //清除字节缓冲区  

20. buffer.clear();  

21. }  

22. }  

23. }  

6.文件通道传输:
文件通道的transferFrom(ReadableByteChannel src, long position, long count)方法将字节从给定的可读字节通道传输到此通道的文件中,transferTo(long position, long count, WritableByteChannel target)方法将此通道的文件传输到给定的可写字节通道。例子如下:

[java] view plaincopy

1. import java.nio.channels.*;  

2. import java.io.*  

3.   

4. public class ChannelTransfer{  

5.     public static void main(String[] args) throws Exception{  

6.     FileChannel in = new FileInputStream(“ChannelTransfer.java”).getChannel();  

7.     FileChannel out = new FileOutputStream(“out.txt”).getChannel();  

8.     //将输入文件通道传输到输出文件通道  

9.     in.transferTo(0, in.size(); out);  

10.     //从输入文件通道传输到输出文件通道  

11.     out.transferFrom(in, 0, in.size());  

12. }  

13. }  

 

13——Java new I/O(

1.数据转换:
使用字节缓冲区时,向字节缓冲区写入或者从字节缓冲区读取内容时,如果内容是字符类型,写入时指定字符编码集对内容进行编码,读取时指定字节编码集对内容解码。

每次从字节缓冲区中读取一个字节,程序中还需要对读取的字节进行类型转换才可以使用,如果使用视图缓冲区就可以直接获取所需的数据类型,例子如下:

[java] view plaincopy

1. import java.nio.*;  

2. import java.nio.channels.*;  

3. import java.nio.charset.*;  

4. import java.io.*;  

5.   

6. public class BufferToText{  

7.     private static final int BSIZE = 1024;  

8.     public static void main(String[] args) throws Exception{  

9.         FileChannel fc = new FileOutputStream(“data.txt”).getChannel();  

10.         fc.write(ByteBuffer.wrap(“Some text”.getBytes()));  

11.         fc.close();  

12.         fc = new FileInputStream(“data.txt”).getChannel();  

13.         ByteBuffer buffer = ByteBuffer.allocate(BSIZE);  

14.         fc.read(buffer);  

15.     buffer.flip();  

16.     //创建字节缓冲区视图,作为char缓冲区,由于编码问题输出乱码  

17.     System.out.println(buffer.asCharBuffer());  

18.     //重绕字节缓冲区,即回到字节缓冲区开始处  

19. buffer.rewind();  

20. //获取系统默认字节编码  

21. String encoding = System.getProperties(“file.encoding”);  

22. System.out.println(“Decode using ” + encoding + “: ”   

23. + Charset.forName(encoding).decode(buffer));  

24.         fc = new FileOutputStream(“data2.txt”).getChannel();  

25.         //创建字节缓冲区时指定字符集  

26.         fc.write(ByteBuffer.wrap(“Some text”.getBytes(“UTF-16BE”)));  

27.         fc.close();  

28.         fc= new FileInputStream(“data2.txt”).getChannel();  

29.         buffer.clear();  

30.         fc.read(buffer);  

31.         buffer.flip();  

32.         //创建字节缓冲区的视图,将其作为char缓冲  

33.         System.out.println(buffer.asCharBuffer());  

34. }  

35. }  


输出结果:
????
Decoded using Cp1252: Some text
Some text
从上面例子可以看出,使用java.nio.charset.Charset类对字符进行编码/解码之后,写入/读取字节缓冲区时,字节缓冲区中的字符内容才可以正常显示。
2.创建字节缓冲区视图:
字节缓冲区中只能存放字节内容,java 8种基本类型数据向字节缓冲区中写入,或者从字节缓冲区中读取,就需要使用字节缓冲区视图和读取8中java基本类型数据的方法,例子如下:

[java] view plaincopy

1. import java.nio.*;  

2.   

3. public class ViewBuffer{  

4.     private static final int BSIZE = 1024;  

5.     public static void main(String[] args){  

6.         ByteBuffer bb = ByteBuffer.allocate(BSIZE);  

7.         int i = 0;  

8.         //检查字节缓冲区的内容是否为0  

9.         while(i++ < bb.limit()){  

10.             if(bb.get() != 0){  

11.                 System.out.println(“nonzero”);  

12. }  

13. System.out.println(“i = ” + i);  

14. //回到字节缓冲区开始处  

15. bb.rewind();  

16. }  

17.        //创建字节缓冲区视图,作为char缓冲  

18.       bb.asCharBuffer().put(“Hello!”);  

19.       char c;  

20.       while((c = bb.getChar)) != 0){  

21.           System.out.println(c + “ ”);  

22.   }  

23. bb.rewind();  

24. //创建字节缓冲区视图,作为short缓冲  

25. bb.asShortBuffer().put((Short)12390);  

26. System.out.println(bb.getShort());  

27. bb.rewind();  

28. //创建字节缓冲区视图,作为int缓冲  

29. bb.asIntBuffer().put(99471142);  

30. System.out.println(bb.getInt());  

31. bb.rewind();  

32. //创建字节缓冲区视图,作为long缓冲  

33. bb.asLongBuffer().put(99471142);  

34. System.out.println(bb.getLong());  

35. bb.rewind();  

36. //创建字节缓冲区视图,作为float缓冲  

37. bb.asFloatBuffer().put(99471142);  

38. System.out.println(bb.getFloat());  

39. bb.rewind();  

40. //创建字节缓冲区视图,作为double缓冲  

41. bb.asDoubleBuffer().put(99471142);  

42. System.out.println(bb.getDouble());  

43.    }  

44. }  


输出结果:
i = 1025
H e l l o !
12390
99471142
99471142
9.9471142E7
9.9471142E7
对于刚分配的字节缓冲区来说,其所有内容都是0,所有第一次i的输出值为1025.
向字节缓冲区中写入,或者从字节缓冲区中读取8中基本类型java数据最简便的方法是使用字节缓冲区的视图缓冲区。
3.java nio相关类图:
下图是从《java编程思想第4版》中截的java nio相关类图:

从类图中可以看出,java nio中,只有字节缓冲区ByteBuffer可以向文件通道写数据或者从文件通道中读取数据。

4.缓冲区详解:
缓冲区由:内容数据和4个索引组成。
缓冲区索引:
a. 缓冲区标记(mark):使缓冲区能够记住一个位置并在之后将其返回。并非总需要定义标记,但在定义标记时,不能将其定义为负数,且不能大于其位置。
b. 缓冲区位置(position):是缓冲区下一个要读取或写入元素的索引,位置不能为负,且不能大于其限制。
c. 缓冲区限制(limit):是第一个不应该读取或写入的元素的索引,限制不能为负,且不能大于其容量。
d. 缓冲区容量(capacity):是缓冲区所包含的元素数量,不能为负,且不能更改。
缓冲区索引遵循以下不变公式:
0 <= 标记 <= 位置 <= 限制 <= 容量
通过这些索引,缓冲区可以高效的访问和操作缓冲区中的内容数据,新创建的缓冲区总有一个0位置和一个未定义的标记,初始限制可以为0,也可以为其他值,取决于缓冲区类型和构建方式。
缓冲区的以下方法可以查询,设置和充值缓冲区的索引:
(1).capacity()方法:
返回此缓冲区的容量。
(2).clear()方法:
清除此缓冲区,将缓冲区位置设置为0,将缓冲区限制设置为容量,并丢弃标记。
(3).flip()方法:
反转此缓冲区,首先将限制设置为当前位置,然后将位置设置为0,如果已定义了标记,则丢弃该标记。
(4).limit()方法:
返回此缓冲区的限制。
(5).limit(int lim)方法:
设置此缓冲区的限制,如果位置大于新的限制,则将位置设置为新的限制,如果标记已定义且大于新限制,则丢弃该标记。
(6).mark()方法:
在此缓冲区的位置设置标记。
(7).position()方法:
返回此缓冲区的位置。
(8).position(int pos)方法:
设置此缓冲区的位置,如果标记已定义且大于新的位置,则丢弃该标记。
(9).remaining()方法:
返回当前位置与限制之间的元素个数,即limit-position。
(10).hasRemaining()方法:
判断在当前位置和限制之间是否有元素。

 

14——I/O高级 

1.内存映射文件:

内存映射文件允许把比内存大的文件读入内存中创建和修改,使用内存映射文件,可以像使用内存中数组一样在内存中访问整个文件,例子如下:

[java] view plaincopy

1. import java.nio.*;  

2. import java.nio.channels.*;  

3. import java.io.*;  

4.   

5. pulbic class MemoryMappedFile{  

6.     //十六进制,128MB大小  

7.     static int length = 0x8FFFFFF;  

8.     public static void main(String[] args)throws Exception{  

9.         //通过文件通道将文件映射为内存中的自己缓冲区  

10.     MappedByteBuffer out = new RandomAccessFile(“test.dat”, “rw”).getChannel()  

11. .map(FileChannel.MapMode.READ_WRITE, 0, length);  

12.         for(int i = 0; i < length; i++){  

13.     out.put((byte)’x’);  

14. }   

15. System.out.println(“Finished writing”);  

16. for(int I = length/2; i < length/2 + 6; i++){  

17.     System.out.println((char)out.get(i));  

18. }  

19. }  

20. }  

 

MappedByteBuffer是直接字节缓冲区,其内容是文件的内存映射区域,映射的字节缓冲区和它所表示的文件映射关系在该缓冲区本身成为垃圾回收之前一直保持有效。

FileChannel的MappedByteBuffermap(FileChannel.MapMode mode, long position, long size) thorws IOException方法可以将此通道的文件区域直接映射到内存中。

可以通过以下三种模式将文件区域映射到内存中:

(1).只读:视图修改得到的缓冲区将导致抛出ReadOnlyBufferException。

(2).读/写:对得到的缓冲区的更改将最终传播到文件,该更改对映射到同一文件的其他程序不一定是可见的。

(3).专用:对的到的缓冲区更改将不会被传播到文件,并且该更改对映射到同一文件的其他程序也是不可见的,相反,会创建缓冲区已修改部分的专用副本。

创建映射时指定文件区域映射的起始位置和大小,因此,只有指定的部分才会被影响到内存的字节缓冲区中,而不再指定范围内的文件区域不会被映射到内存中,因此,对于大型文件来说,可以大大提高程序性能。

注意:映射关系一经创建,就不再依赖于创建它时所用的文件通道,特别是关闭该通道对映射关系的有效性没有任何影响。另外,从性能观点来讲,通常相对较大的文件映射到内存中才是值得的。

2.文件锁定:

在同一个JVM中,共享资源的文件可以通过线程同步来确保访问的安全性,但是在不同的JVM或者Java线程和操作系统本地线程共同竞争一个共享的文件资源时,就必须通过对文件的锁定机制来确保,例子如下:

[java] view plaincopy

1. import java.nio.channels.*;  

2. import java.util.concurrent.*;  

3. import java.io.*;  

4.   

5. public class FileLocking{  

6.     public static void main(String[] args)throws Exception{  

7.     FileOutputStream fos = new FileOutputStream(“file.txt”);  

8.     //试图对文件通道的文件锁定  

9.     FileLock fl = fos.getChannel().tryLock();  

10.     //文件锁定成功  

11.     if(fl != null){  

12.     System.out.println(“Locked File”);  

13.     TimeUnit.MILLISECONDS.sleep(100);  

14.     //释放文件锁  

15.     fl.release();  

16.     System.out.println(“Released Lock”);  

17. }  

18. foc.close();  

19. }  

20. }  

 

输出结果:

Locked File

Released Lock

文件通道的tryLock()方法试图获取对此通道的文件给定区域的锁定,是个非阻塞方法,无论是否已成功获得请求区域的锁定,调用总是立即返回。如果由于另一个程序保持这一个重叠锁而无法锁定,则此方法返回null.

文件锁定方法:

(1).FileLock tryLock():

试图获取对此通道文件的独占锁定。

(2).FileLock tryLock(long position, long size, Boolean shared):

视图获取对此通道文件给定区域的锁定。

(3).FileLock lock():

获取此通道的文件的独占锁定。

(4).FileLock lock(long position, long size, Boolean shared):

获取此通道的文件的给定区域锁定。

文件锁定方法是共享锁还是排斥锁取决于底层操作系统,可以使用FileLock.isShared()方法判断使用的是何种类型的文件锁。

3.压缩/解压缩:

Java I/O类库中提供了一些关于压缩和加压的类,由于压缩和解压缩算法是针对字节数据进行操作的,因此javaI/O中关于压缩和加压素的类是继承自InputStream和OutputStream字节流体系。

Java压缩和解压的相关类在java.util.zip包下,具体的类如下:

(1).CheckedInputStream

需要维护所读取数据校验和的输入流,校验和可用于验证输入数据的完整性。

(2).CheckedOutputStream:

需要维护所写入数据校验和的输出流。

(3).Deflater:

使用流行的”ZLIB”压缩程序库为通用压缩提供支持。

(4).Inflater:

使用流行的”ZLIB”压缩程序库为通用解压缩提供支持。

(5).DeflaterInputStream:

为压缩“deflate“格式压缩数据实现输入流过滤器。

(6).DeflaterOutputStream:

为压缩 “deflate“格式压缩数据实现输出流过滤器,它还用作其他类型的压缩过滤器(如GZIPOutputStream)的基础。

(7).InflaterInputStream:

为解压缩”deflate”压缩格式的数据实现输入流过滤器,它还用作其他解压缩过滤器(如GZIPInputStream)的基础。

(8).InfaterOutputStream:

为解压缩“deflate”压缩格式存储数据实现输出流过滤器。

(9).ZipOutputStream:

为以”zip”文件格式写入文件实现输出流过滤器,包括对已压缩和未压缩条目的支持。

(10).ZipInputStream:

为读取以”zip”文件格式的文件实现输入流过滤器,包括对已压缩和未压缩条目的支持。

(11).GZIPOutputStram:

为使用“GZIP“文件格式写入压缩数据实现输出流过滤器。

(12).GZIPInputStram:

为读取“GZIP“文件格式的压缩数据实现输入流过滤器。

Java I/O压缩和解压缩操作小例子如下:

[java] view plaincopy

1. import java.util.zip.*;  

2. import java.io.*;  

3.   

4. public class GZIPCompress{  

5.     public static void main(String[] args)throws IOException{  

6.     BufferedReader in = new BufferedReader(new FileReader(“test.dat”));  

7.     BufferedOutputStream out = new BufferedOutputStream(new GZIPOutputStream(  

8. new FileOutputStream(“test.gz”)));  

9.         int c;  

10.         //写GZIP格式压缩文件  

11.         while((c = in.read()) != -1){  

12.     out.write(c);  

13. }  

14. in.close();  

15. out.close();  

16. BufferedReader in2 = new BufferedReader(new InputStreamReader(  

17. new GZIPInputStream(new FileInputStream(“test.gz”))));  

18.         String s;  

19.         //读取GZIP格式压缩文件  

20.         while((s = in2.readLine()) != null){  

21.     System.out.println(s);  

22. }  

23. in2.close();  

24. }  

25. }  

 

使用java中压缩相关类时,只需对输入流使用GZIPInputStream或ZipInputStream包装,对输出流使用GZIPOutputStream或ZipOutputStream包装即可,其他的操作和普通的java I.O操作相同。

4.使用zip多文件压缩:

zip格式的压缩文件是最常用的压缩方式,使用zip多文件压缩时,可以将多个文件压缩在一个压缩包中,同时还可以从一个包含多个文件的压缩包中读取所有的压缩文件。使用zip进行多文件压缩时,一般要使用CheckSum类计算校验和,校验和的计算有两种算法:

(1).Adler32:速度比较快。

(2).CRC32:速度比较慢,但是更精确。

使用zip多文件压缩的例子如下:

[java] view plaincopy

1. import java.util.zip.*;  

2. import java.io.*;  

3. import java.util.*;  

4.   

5. public class ZipCompress{  

6.     public static void main(String[] args)throws Exception{  

7.         FileOutputStream f = new FileOutputStream(“test.zip”);  

8.         //使用Adler32算法为文件输入流产生输出校验和文件  

9.         CheckedOutputStream csum = new CheckedOutputStream(f, new Adler32());  

10.         ZipOutputStream zos = new ZipOutputStream(csum);  

11.         BufferedOutputStream out = new BufferedOutputStream(zos);  

12.         //设置zip文件注释  

13.         zos.setComment(“A test of java zipping”);  

14.         //向zip压缩文件写入多个文件  

15.         for(String arg : args){  

16.     System.out.println(“Writing file:” + arg);  

17. BufferedReader in = new BufferedReader(new FileReader(arg));  

18.             //写入一个zip文件条目,并将流定位到条目数据的开始处  

19.             zos.putNextEntry(new ZipEntry(arg));  

20.             int c;  

21.             //写入zip文件内容  

22.             while((c = in.read()) != -1){  

23.                 out.write©;  

24. }  

25. in.close();  

26. out.flush();  

27. }  

28.         out.close();  

29. //文件关闭后获取校验和  

30. System.out.println(“Checksum:” + csum.getChecksum().getValue());  

31. FileInputStream fi = new FileInputStream(“test.zip”);  

32. //使用Adler32算法为输入文件流产生输入校验和文件流  

33. CheckedInputStream csumi = new CheckedInputStream(fi, new Adler32());  

34. ZipInputStream in2 = new ZipInputStream(csumi);  

35. BufferedInputStream bis = new BufferedInputStream(in2);  

36. ZipEntry ze;  

37. //读取zip文件条目  

38. While((ze = in2.getNextEntry()) != null){  

39.     System.out.println(“Reading file:” + ze);  

40. int x;  

41.     //读取zip文件条目内容  

42.     while((x = bis.read()) != -1){  

43.     System.out.println(x);  

44. }  

45. }  

46. if(args.length == 1){  

47.     System.out.println(“Checksum:” + csumi.getChecksum().getValue());  

48. }  

49. bis.close();  

50. //另一种读取zip文件的方法  

51. ZipFile zf = new ZipFile(“test.zip”);  

52. //获取zip文件的条目  

53. Enumeration e = zf.entries();  

54. while(e.hasMoreElements()){  

55.     ZipEntry ze2 = (ZipEntry)e.nextElement();  

56.     System.out.println(“Reading File:” + ze2);  

57. }  

58. }  

59. }  

 

15——对象序列化 

Java的对象序列化是一个轻量级的持久化过程,序列化时,可以将java对象以字节序列形式写入硬盘、数据库或者通过网络传输到另一个JVM等等,反序列化是读取序列化的文件,将其在JVM中还原为java对象的过程。

1.简单的序列化和反序列化:

最简单的序列化是对象实现Serializable序列化接口,这个接口是一个标记接口,不需要实现任何方法。

下面的小例子简单展示java对象序列化和反序列化的过程:

[java] view plaincopy

1. import java.io.*;  

2.   

3. //简单的测试对象  

4. public class TestObject implements Serializable{}  

5.   

6. import java.io.*;  

7.   

8. //序列化  

9. public class SerializeObject{  

10.     public static void main(String[] args)throws Exception{  

11.         ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(“x.file”));  

12.         TestObject obj = new TestObject();  

13.         //将TestObject对象序列化到x.file文件中  

14.         out.write(obj);  

15. }  

16. }  

17.   

18. import java.io.*;  

19.   

20. //反序列化  

21. public class DeserializeObject{  

22.     public static void main(String[] args)throw Exception{  

23.         ObjectInputStream in = new ObjectInputStream(new FileInputStream(“x.file”));  

24.         Object myObj = in.readObject();  

25.         System.out.println(myObj.getClass());  

26. }  

27. }  

 

输出结果:

class TestObject

在序列化时,使用ObjectOutputStream将对象序列化写出,反序列化时,使用ObjectInputStream将对象反序列化读入。

注意:反序列化对象使用时,对象的class文件必须在classpath中,否则,JVM找不到对象的class文件会抛出ClassNotFoundException。

2.序列化控制:

默认的Serializable序列化是将对象整体序列化,但是对于一些特殊的需求例如:序列化部分对象或者反序列化部分对象的情况,就必须使用 Externalizable接口来代替Serializable接口,Externalizable的writeExternal()和 readExternal()方法可以实现对序列化的控制,这两个方法在对象序列化和反序列化时自动调用,例子如下:

[java] view plaincopy

1. import java.io.*;  

2.   

3. class Blip1 implements Externalizable{  

4.     public Blip1(){  

5.         System.out.println(“Blip1 Constructor”);  

6. }  

7. public void writeExternal(ObjectOutput out)throws IOException{  

8.     System.out.println(“Blip1.writeExternal”);  

9. }  

10. public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException{  

11.     System.out.println(Blip1.readExternal);  

12. }  

13. }  

14.   

15. class Blip2 implements Externalizable{  

16.     Blip1(){  

17.         System.out.println(“Blip2 Constructor”);  

18. }  

19. public void writeExternal(ObjectOutput out)throws IOException{  

20.     System.out.println(“Blip2.writeExternal”);  

21. }  

22. public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException{  

23.     System.out.println(Blip2.readExternal);  

24. }  

25. }  

26.   

27. public class Blips{  

28.     public static void main(String[] args)throws IOException, ClassNotFoundException{  

29.         System.out.println(“Constructing objects:”);  

30.         Blip1 b1 = new Blip1();  

31.         Blip2 b2 = new Blip2();  

32.         //序列化  

33.         ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream(“Blips.out”));  

34.         System.out.println(“Saving objects:”);  

35.         o.writeObject(b1);  

36.         o.writeObject(b2);  

37.         //反序列化  

38.         ObjectInputStream in = new ObjectInputStream(new FileInputStream(“Blips.out”));  

39.         System.out.println(“Recovering b1:”);  

40.         b1 = (Blip1)in.readObject();  

41.         //由于Blip2的默认无参数构造方法不是public的,所以会抛异常  

42.         //System.out.println(“Recovering b2:”);  

43.         //b2 = (Blip2)in.readObject();  

44. }  

45. }  

输出结果:

Constructing objects:

Blip1 Constructor

Blip2 Constructor

Saving objects:

Blip1.writeExternal

Blip2.writeExternal

Recovering b1:

Blip1 Constructor

Blip1.readExternal

注意:由于对象在反序列化时需要调用默认的public的无参数构造方法创建对象,所以Blip2非public类型无参数构造方法无法反序列化。 所以使用Externalizable序列化和反序列化时,对象必须要有public类型的无参数构造方法,这一点是Externalizable和 Serializable的区别。

Serializable和Externlizable反序列化的区别:

(1).Serializable的反序列化是通过序列化的字节数组序列进行反序列化的。对象的序列化的反序列化都是自动进行的。

(2).Externalizable的反序列化是通过调用默认的构造方法进行的。对象的序列化需要使用writeExternal()显式控制,对象的反序列化需要使用readExternal()显式控制,不是自动进行的。

3. Externlizable序列化高级:

使用Externlizable序列化和反序列化对象时,readExternal()和writeExternal()方法可以对象中指定字段进行初始化,例子如下:

[java] view plaincopy

1. import java.io.*;  

2.   

3. public class Test implements Externalizable{  

4.     private int i;  

5.     private String s;  

6.     public Test(){  

7.         System.out.println(“Test Constructor”);  

8. }  

9. public Test(String x, int a){  

10.     System.out.println(“Test(String x, int a)”);  

11.     i = a;  

12.     s = x;  

13. }  

14. public void toString(){  

15.     return s + i;  

16. }  

17. public void writeExternal(ObjectOutput out)throws IOException{  

18.     System.out.println(“Test.writeExternal”);  

19.     out.writeObject(s);  

20.     out.writeInt(i);  

21. }  

22. public void readExternal(ObjectInput in)throws IOException, ClassNotFoundException{  

23.     System.out.println(“Test.readExternal”);  

24.     s = (String)in.readObject();  

25.     i = in.readInt();  

26. }  

27. public static void main()throws IOException, ClassNotFoundException{  

28.     System.out.println(“Constructing objects:”);  

29.     Test t1 = new Test(“A String”, 47);  

30.     Test t2 = new Test();  

31.     System.out.println(t1);  

32.     System.out.println(t2);  

33.     //序列化  

34.     ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream(“Test.out”));  

35.     System.out.println(“Saving objects:”);  

36.     o.writeObject(t1);\  

37.     o.writeObject(t2);  

38.     o.close();  

39.     //反序列化  

40.     ObjectInputStream in = new ObjectInputStream(new FileInputStream(“Test.out”));  

41.     System.out.println(“Recovering objects:”);  

42.     t1 = (Test)in.readObject();  

43.     System.out.println(t1);  

44.     t2 = (Test)in.readObject();  

45.     System.out.println(t2);  

46. }  

47. }  

 

输出结果:

Constructing objects:

Test(String x, int a)

A String 47

Test Constructor

null0

Saving objects:

Test.writeExternal

Test.writeExternal

Recovering objects:

Test Constructor

Test.readExternal

A String 47

Test Constructor

Test.readExternal

null0

该例子中,当创建对象使用非无参数构造方法时,对象字段被初始化,因此序列化时可以将初始化的值保存。当创建对象使用无参数的构造方法时,对象的字段没有被赋值而使用默认的初始化值。反序列化时只会调用默认的无参数构造方法。

因此,为了使序列化和反序列化更高效使用,在序列化时,调用writeExternal()只写有用的重要数据,而在反序列化时,readObject()只读取有用的重要数据。

4.transient关键字:

当对象实现Serializable接口进行自动序列化时,类中某些字段不想被序列化,需要使用transient关键字,虽然 Externalizable通过writeExternal()方法也可以实现此功能,但是序列化不是自动进行的,使用Serializable和 transient关键字更加方便。

例子如下:

 

[java] view plaincopy

1. import java.io.*;  

2.   

3. public class User implements Serializable{  

4.     private String username;  

5.     private transient String password;  

6.     public User(String name, String pwd){  

7.         username = name;  

8.         password = pwd;  

9. }  

10. public String toString(){  

11.     return “username:” + username + “, password:” + password;  

12. }  

13. public static void main(String[] args)throws Exception{  

14.     User user = new User(“Test”, “passwd”);  

15.     System.out.println(“User=” + user);  

16.     ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream(“User.out”));  

17.     o.writeObject(user);  

18.     o.close();  

19.     ObjectInputStream in = new ObjectInputStream(new FileInputStream(“User.out”));  

20.     System.out.println(“Recovering object:”);  

21.     user = (User)in.readObject();  

22.     System.out.println(“User=” + user);  

23. }  

24. }  

输出结果:

User=username:Test,password:passwd

Recovering object:

User=username:Test,password:null

注意:由于Externalizable默认序列化不存储任何字段,所以transient关键字只在Serializable中使用。

 

 

16——枚举 

1.在枚举中添加方法:

Java中的枚举是一种特殊的类,不但可以添加字段,构造方法,普通方法,甚至可以添加main()方法,例子如下:

[java] view plaincopy

1. public enum Test{  

2.     //调用枚举构造方法创建枚举对象  

3.     WEST(“Go west”), NORTH(“Go north”),  

4.     EAST(“Go east”), SOUTH(“Go south”);  

5. private String description;  

6.     //私有构造方法  

7. private Test(String description){  

8.         this.description = description;  

9. }  

10. public String getDescription(){  

11.     return description;  

12. }  

13. public static void main(String[] args){  

14.     for(Test witch : Test.values()){  

15.     System.out.println(witch + “: ” + witch.getDescription());  

16. }  

17. }  

18. }  

输出结果:

WEST:Go west

NORTH:Go north

EAST:Go east

SOUTH:Go south

注意:若要在枚举中定义字段和方法,必须在枚举对象的分号之后,java强制规定枚举对象必须放在枚举的最开始处。

另外,枚举的构造方法必须是private的,因为只允许在枚举内部定义枚举对象时使用,不允许在枚举类之后使用枚举的构造方法。

2.switch分支语句中使用枚举:

Java的switch分支语句中,switch的条件只能是整数数值类型,在JDK5以后枚举之后,由于枚举对象的顺序也是整数数值,因此switch也支持枚举,例子如下:

[java] view plaincopy

1. enum Signal{GREEN, YELLOW, RED}  

2. public class TrafficLight{  

3.     Signal color = Signal.RED;  

4.     public void change(){  

5.         switch(color){  

6.     case RED:  

7.         color = Signal.GREEN;  

8.         break;  

9.     case GREEN:  

10.         color = Signal.YELLOW;  

11.         break;  

12.     case YELLOW:  

13.         color = Signal.RED;  

14.         break;  

15. }  

16. }  

17. public String toString(){  

18.     return “The traffic light is ” + color;  

19. }  

20. public static void main(String[] args){  

21.     TrafficLight t = new TrafficLight();  

22.     for(int i=0; i<5; i++){  

23.         System.out.println(t);  

24.         t.change();  

25. }  

26. }  

27. }  

 

输出结果:

The traffic light is RED 

The traffic light is GREEN 

The traffic light is YELLOW 

The traffic light is RED 

The traffic light is GREEN

3.枚举Set:

EnumSet枚举Set是JDK5中引入的与枚举类型一起使用的专用Set实现,EnumSet中所有键都必须来自单个枚举类型,枚举Set在内 部表示为位向量。位向量表示形式的EnumSet非常紧凑高效,空间性和时间性非常好,同时由于枚举对象含义清楚,因此EnumSet可以替换传统技艺 int的位标志(在程序开发中,很多时候需要使用一串数字序列的位标志,每位代表不同的含义,例如网络协议等)。例子如下:

[java] view plaincopy

1. //星期枚举  

2. public enum Week{  

3.     MON, TUE, WEN, THU, FRI, SAT, SUN  

4. }  

5.   

6. import java.util.*;  

7. //EnumSet例子  

8. public class EnumSets{  

9.     public static void main(String[] args){  

10.         //EnumSet的noneOf方法创建指定枚举类型的空EnumSet  

11.         EnumSet<Week> weeks = EnumSet.noneOf(Week.class);  

12.         weeks.add(MON);  

13.         System.out.println(weeks);  

14.         //EnumSet的of方法创建包含指定枚举类型元素的EnumSet  

15.         weeks.addAll(EnumSet.of(TUE, WEN, THU));  

16.         System.out.println(weeks);  

17.         // EnumSet的allOf方法创建包含指定枚举类型所有元素的EnumSet  

18.         weeks = EnumSet.allOf(Week.class);  

19.         System.out.println(weeks);  

20.         weeks.removeAll(EnumSet.of(FRI, SAT, SUN));  

21. System.out.println(weeks);  

22. // EnumSet的rang方法创建包含指定枚举类型两个元素之间的EnumSet  

23. weeks.removeAll(EnumSet.rang(MON, WEN));  

24. System.out.println(weeks);  

25. // EnumSet的complementOf方法创建指定EnumSet所不包含元素的EnumSet  

26. weeks.removeAll(EnumSet.complementOf(weeks));  

27. System.out.println(weeks);  

28. }  

29. }  

输出结果:

[MON]

[MON, TUE, WEN, THU]

[MON, TUE, WEN, THU, FRI, SAT, SUN]

[MON, TUE, WEN, THU]

[THU]

[MON, TUE, WEN, FRI, SAT, SUN]

注意:EnumSet的元素排列是依据Enum的定义顺序排列的,所有的基本操作都在固定时间内执行,速度比HashSet可能更快。

4.枚举Map:

EnumMap是与枚举类型一起使用的专用Map实现,枚举Map中所有的键都必须来自单个枚举类型,枚举Map在内部表示为数组。和 EnumSet类似,EnumMap键的顺序也是和Enum的定义顺序一致,EnumMap的所有操作也都在固定时间内执行,它的速度可能比 HashMap更快。

下面的例子使用EnumMap演示command interface的设计模式:

[java] view plaincopy

1. public enum Directions{  

2.     EAST, NORTH, WEST, SOUTH  

3. }   

4. interface Command{  

5.     public void action();  

6. }  

7. public class EnumMaps{  

8.     Public static void main(String[] args){  

9.         EnumMap<Directions, Command> em =   

10. new EnumMap<Directions, Command>(Directions.class);  

11.         em.put(EAST, new Command(){  

12.     public void action(){  

13.     System.out.println(“Go east”);  

14. }     

15. });  

16.         em.put(NORTH, new Command(){  

17.     public void action(){  

18.     System.out.println(“Go north”);  

19. }     

20. });  

21.         em.put(WEST, new Command(){  

22.     public void action(){  

23.     System.out.println(“Go west”);  

24. }     

25. });  

26.         for(Map.Entry<Directions, Command> e : em.entrySet()){  

27.             System.out.println(e.getKey() + “: ” + e.getValue().action());  

28. }  

29. try{  

30.     em.get(SOUTH).action();  

31. }catch(Exception e){  

32.     System.out.println(e);  

33. }  

34. }  

35. }  

 

输出结果:

EAST: Go east

NORTH: Go north

WEST: Go west

java.lang.NullPointerException

从最后的空指针我们可以看出,EnumMap在创建时将Enum所有元素全部放进了key中,但是如果没有使用put显式将key和value关联的话,对应的value是null。

5.枚举特定方法:

在枚举中可以定义一个抽象的公共方法,然后各个枚举实例提供具体实现,实现一种类似匿名内部类的多态方式,例子如下:

[java] view plaincopy

1. import java.util.*;  

2. import java.text.*;  

3.   

4. public enum ConstantSpecificMethod{  

5.     DATE_TIME{  

6.     String getInfo(){  

7.         return DateFormat.getDateInstance().format(new Date());  

8. }  

9. },  

10. CLASS_PATH{  

11.     String getInfo(){  

12.         return System.getenv(“CLASSPATH”);  

13. }  

14. },  

15. VERSION{  

16.     String getInfo(){  

17.         return System.getProperty(“java.version”);  

18. }  

19. };  

20. //抽象方法  

21. abstract String getInfo();  

22. public static void main(String[] args){  

23.     for(ConstantSpecificMethod csm : values()){  

24.         System.out.println(csm.getInfo());  

25. }  

26. }  

27. }  

通过这种多态方式,同样方便高效实现Commandinterface设计模式。

 

 

17——注解Annotation 

注解Annotation又叫元数据,是JDK5中引入的一种以通用格式为程序提供配置信息的方式。使用注解Annotation可以使元数据写在 程序源码中,使得代码看起来简洁,同时编译器也提供了对注解Annotation的类型检查,使得在编译期间就可以排除语法错误。

1JDK内置的3中Annotation:

在JDK5中,内置了3个通用目的的注解Annotation,这三个内置的注解在java.lang包下:

(1)@Override

这个注解常用在继承类或实现接口的子类方法上,表面该方法是子类覆盖父类的方法,该方法的方法签名要遵循覆盖方法的原则:即访问控制权限必能比父类更严格,不能比父类抛出更多的异常。

(2)@Deprecated

这个注解告诉编译器该元素是过时的,即在目前的JDK版本中已经有新的元素代替该元素。

(3)@SuppressWarnings

该注解关闭编译器中不合适的警告,即强行压制编译器的警告提示。

2.注解Annotation的定义:

注解Annotation的定义和接口类似,实际上,编译器也把注解Annotationn像接口一样编译成class字节码文件。例如:

[java] view plaincopy

1. import java.lang.annotation.*;  

2.   

3. @Target(ElementType.METHOD)  

4. @Retention(RetentionPolicy.RUNTIME)  

5. public @interface Test{}  

 

像这种没有任何元素的空注解Annotation叫做标记Annotation.

在声明注解的时候往往需要使用@Target,@Retention等注解,这种注解被称为注解的注解(元数据注解),即是专门用于处理注解Annotation本身的。

(1).@Target注解:

用于指示注解所应用的目标程序元素种类,该注解常和ElementType枚举类型一起联合使用,ElementType枚举提供了java程序中声明的元素类型如下:

a. ANNOTATION_TYPE:注释类型声明。

b. CONSTRUCTOR:构造方法声明。

c. FIELD:字段声明(包括枚举常量)。

d. LOCAL_VARIABLE:局部变量声明。

e. METHOD:方法声明。

f. PACKAGE:包声明。

g. PARAMETER:参数声明。

h. TYPE::类,接口或枚举声明。

(2)@Retention注解:

该注解用于指示所定义的注解类型的注释在程序声明周期中得保留范围,该注解常和RetentionPolicy枚举联合使用。RetentionPolicy枚举常量定义了注解在代码中的保留策略:

a. CLASS:编译器把注解记录在类文件中,但在运行时JVM不需要保留注解。

b. RUNTIME:编译器把注解记录在类文件中,在运行时JVM将保留注解,因此可以通过反射机制读取注解。

c. SOURCE:仅保留在源码中,编译器在编译时就要丢弃掉该注解。

3.创建和处理自定义注解Annotation:

正常使用注解时,需要在注解中定义元素,用于接收程序设置的值,正常定义注解的例子如下:

[java] view plaincopy

1. import java.lang.annotation.*;  

2.   

3. @Target(ElementType.METHOD)  

4. @Retention(RetentionPolicy.RUNTIME)  

5. public @interface UseCase{  

6.     public int id();  

7.     public String description() default “no description”;  

8. }  

 

正常定义注解Annotation的方式类似定义接口,id和description是注解UseCase的属性,而不是方法,注解中不能定义方法 只能定义属性。其中description属性有默认的值“no description“,即在使用时如果没有指定description的值,则程序使用其默认值。

上面UseCasee注解用于跟踪方法的测试用例说明,使用上面注解的例子如下:

[java] view plaincopy

1. import java.util.*;  

2.   

3. public class PasswordUtils{  

4.     @UseCase(id = 47, description = “Passwords must contain at least one numeric”)  

5.     public Boolean validatePassword(String password){  

6.         return (password.mathes(“\\w*\\d\\w*”));  

7. }  

8. @UseCase(id = 48)  

9. public String encryptPassword(Srring password){  

10.     return new StringBuilder(password).reverse()/toString();  

11. }  

12. @UseCase(id = 49, description = “New passwords can’t equal previously used ones”)  

13. public Boolean checkForNewPassword(List<String> prevPasswords, String password){  

14.     return !prevPasswords.contains(password);  

15. }  

16. }  

 

JDK5中提供了Annotation相关的API,结合使用java的反射机制可以实现自定义的Annotation注解处理器(JDK中也提供 了使用APT,Annotationprocess tool方式处理注解,在后面会讲解),处理上述Annotation的例子如下:

[java] view plaincopy

1. import java.lang.reflect.*;  

2. import java.util.*;  

3.   

4. public class UseCaseTracker{  

5.     public static void traceUseCases(List<Integer> useCases, Class<?> clazz){  

6.         //获取指定类中所有声明的方法  

7. for(Method m : clazz.getDeclaredMethods()){  

8.     //获取方法上指定类型的注解  

9.             UseCase uc = m.getAnnotation(UseCase.class);  

10.             if(uc != null){  

11.     System.out.println(“Found Use Case:” + uc.id() + “ ” + uc.description());  

12.     useCases.remove(new Integer(uc.id()));  

13. }  

14. }  

15. for(int i : useCases){  

16.     System.out.println(“Warning: Missing use case-” + i);  

17. }  

18. }  

19. public static void main(String[] args){  

20.     List<Integer> useCases = new ArrayLis<Integer>();  

21.     Collections.addAll(useCases, 47, 48, 49, 50);  

22.     trackUseCases(useCases, PasswordUtils.class);  

23. }  

24. }  

输出结果:

Found Use Case:47 Passwords must contain at least onenumeric

Found Use Case:48 no description

Found Use Case:49 New Passwords can’t equal previously usedones

Warning: Missing use case-50

注意:使用反射获取到注解对象之后,类似使用调用方法的方式获取注解的值,如uc.id()等。另外,注解不支持继承,因此声明注解时不能使用extends和implements关键字。

4.Annotation注解元素:

Annotation注解中的元素只能是下面的数据类型:

(1).java的8中基本类型,如int, boolean等等,如果可以自动装箱和拆箱,则可以使用对应的对象包装类型。

(2).String类型。

(3).Class类型。

(4).Enums类型。

(5).Annotation类型。

(6).上面类型的数组。

除了上面这些类型以外,如果在注解中定义其他类型的数据,编译器将会报错。

注意:注解中的元素要么指定默认值,要么由使用的类赋值,如果即没有默认值,使用类也没有赋值的话,注解元素是不会像普通类成员变量一样给定默认值,即必须赋值或者显示指定默认值。默认值例子如下:

[java] view plaincopy

1. import java.lang.annotation.*;  

2.   

3. @Target(ElementType.METHOD)  

4. @Retention(RetentionPolicy.RUNTIME)  

5. public @interface DefaultValue{  

6.     public int id() default -1;  

7.     public String description() default “”;  

8. }  

5.一个使用Annotation实现ORM的例子:

从EJB3之后,EJB的实体Bean持久化技术被单独抽取出来形成了JPA技术,JPA和Hibernate3之后都支持使用 Annotation注解方式进行对象和关系型数据库映射(ObjectRelationship Mapping, ORM),下面使用Annotation注解方式实现一个简单的ORM功能:

(1).相关注解:

[java] view plaincopy

1. import java.lang.annotation.*;  

2.   

3. @Target(ElementType.TYPE)//该注解只能应用在类上  

4. @Retention(RetentionPolicy.RUNTIME)  

5. public @interface DBTable{//指定数据库名称  

6.     public String name() default “”;  

7. }  

8.   

9. @Target(ElementType.FIELD)  

10. @Retention(RetentionPolicy.RUNTIME)  

11. public @interface Constraints{//数据库约束  

12.     boolean primaryKey() default false;  

13.     boolean allowNull() default true;  

14.     boolean unique() default false;  

15. }  

16.   

17. @Target(ElementType.FIELD)  

18. @Retention(RetentionPolicy.RUNTIME)  

19. public @interface SQLString{//String类型数据  

20.     int value() default 0;  

21.     String name() default “”;  

22.     Constraints constraints() default @Constraints;//注解的属性元素也是注解  

23. }  

24.   

25. @Target(ElementType.FIELD)  

26. @Retention(RetentionPolicy.RUNTIME)  

27. public @interface SQLInteger{//int类型数据  

28.     String name() default “”;  

29.     Constraints constraints() default @Constraints;   

30. }  

 

(2).使用Annotation注解的实体类:

[java] view plaincopy

1. @DBTable(name=”MEMBER”)  

2. public class Member{  

3.     @SQLString(30)//当只指定一个属性的值,并且该属性名为value时,不用写属性名称  

4.     String firstName;  

5.     @SQLString(50)  

6.     String lastName;  

7.     @SQLInteger  

8.     Integer age;  

9.     @String(value=30, constraints=@Constraints(primaryKey = true))  

10.     String handle;  

11.     static int memberCount;  

12. }  

 

注意:当为注解多于1个以上的属性指定值时,即使有value属性也要写value的属性名称。

(3).实现自定义注解处理器:

[java] view plaincopy

1. import java.lang.annotation.*;  

2. import java.lang.reflect.*;  

3. import java.util.*;  

4.   

5. public class TableCreator{  

6.     public static void mian(String[] args)throws Exception{  

7.         Member m = new Member();  

8.         Class clazz = m.getClass();  

9.         String tableName = null;  

10.         DBTable dbTable =  class.getAnnotation(DBTable.class);  

11.         if(db != null){  

12.             tableName = db.name();  

13. }  

14. If(tableName == null){//如果没有指定Table名称,使用类名  

15.     tableName = clazz.getName().toUpperCase;  

16. }  

17. List<String> columnDefs = new ArrayList<String>();  

18. //构造SQL创建列语句  

19. for(Field f : clazz.getDeclaredFields()){//遍历类中声明的所有字段  

20.     String columnName = null;  

21.     //获取当前字段上声明的所有注解  

22.     Annotationn[] anns = f.getDeclaredAnnotationns();  

23.     if(anns.length() < 1){//当前字段上没有声明注解  

24.         continue;  

25. }  

26. if(anns[0] instanceof SQLInteger){//注解数组第一个定义的是数据类型  

27.     SQLInteget sInt = (SQLInteger) anns[0];  

28.     if(sInt.name().length() < 1){//如果没有指定列名称,则使用字段名称  

29.         columnName = f.getName().toUpperCase();  

30. }else{  

31.     columnName = sInt.name();  

32. }  

33. columnDefs.add(columnName + “ INT” + getConstraints(sInt.constraints()));  

34. }  

35. if(anns[0] instanceof SQLString){  

36.     SQLString sString = (SQLString) anns[0];  

37.     if(sString.name().length() < 1){//如果没有指定列名称,则使用字段名称  

38.         columnName = f.getName().toUpperCase();  

39. }else{  

40.     columnName = sInt.name();  

41. }  

42. columnDefs.add(columnName + “ VARCHAR(” + sString.value() +”)”  

43.  + getConstraints(sString.constraints()));  

44. }  

45. }  

46. StringBuilder createCommand = new StringBuilder(“CREATE TABLE”   

47. + tableName + “(”);  

48.             for(String columnDef : columnDefs){  

49.                 createCommand.append(“\n ” + columnDef + “,”);  

50. }  

51. //删除最后的”,”  

52. String tableCreate = createCommand.subString(0, createCommand.length() - 1)   

53. + “);”;  

54.             System.out.println(“Table creation SQL for ” + className + “ is :\n”   

55. + tableCreate);  

56. }  

57. //获取约束  

58. Private static String getConstraints(Constraints con){  

59.     String constraints = “”;  

60.     if(!con.allowNnull()){  

61.         constraints += “ NOT NULL”;  

62. }  

63. if(con.primaryKey()){  

64.         constraints += “ PRIMARY KEY”;  

65. }  

66. if(con.unique()){  

67.         constraints += “ UNIQUE”;  

68. }  

69. return constraints;  

70. }  

71. }  

 

输出结果:

Table creation SQL for Member is:

CREATE TABLE MEMBER(

            FIRSTNAME  VARCHAR(30),

            LASTNAME   VARCHAR(50),

            AGE  INT,

            HANDLE    VARCHAR(30)  PRIMARY KEY);

6.使用apt处理Annotation注解:

Annotation processing tool, apt是sun提供的第一个版本的Annotation注解处理器,apt的使用和javac类似,是真的未编译的源码,而非已经编译的class字节码 文件,使用apt的时候不能使用java的反射机制,因为源码尚未编译,需要使用mirrorAPI,mirror API可以使得apt看到未编译源代码中的方法,字段和类信息。使用apt的例子如下:

(1).注解:

[java] view plaincopy

1. package annotations;  

2.   

3. import java.lang.annotation.*;  

4.   

5. @Target(ElementType.TYPE)  

6. @Retention(RetentionPolicy.SOURCE)  

7. public @interface ExtractInterface{  

8.     public String value();  

9. }  

这个注解用于从使用该注解的类中抽象公共的方法,并为该类生成一个接口。

(2).使用注解的类:

[java] view plaincopy

1. package annotations;  

2.   

3. @ExtractInterface(“IMultiplier”)  

4. public class Multiplier{  

5.     public int multiply(int x, int y){  

6.         int total = 0;  

7.         for(int i = 0; I < x; i++){  

8.     total = add(total, y);  

9. }  

10. return total;  

11. }  

12. private int add(int x, int y){  

13.     return x + y;  

14. }  

15. }  

(3).注解处理器:

[java] view plaincopy

1. package annotations;  

2.   

3. import com.sun.mirror.apt.*;  

4. import com.sun.mirror.declaration.*;  

5. import java.io.*;  

6. import java.util.*;  

7.   

8. public class InterfaceExtractorProcessor implements AnnotationProcessor{  

9.     //注解处理器的工作环境  

10. private final AnnotationProcessorEnvironment env;  

11.     private List<MethodDeclaration> interfaceMethods =   

12. new ArrayList< MethodDeclaration>();  

13.     public InterfaceExtractorProcessor(AnnotationProcessEnvironment env){  

14.     this.env = env;  

15. }  

16. public void process(){  

17.     //查询注解处理器环境中的类型声明  

18.     for(TypeDeclaration typeDecl : env.getSpecifiedTypeDeclarations()){  

19.         //获取注解  

20. ExtractInterface annot = typeDecl.getAnnotation(ExtractInterface.class);  

21.         if(annot == null){  

22.             break;  

23. }  

24. //遍历所有添加注解的方法  

25. for(MethodDeclaration m : typeDecl.getMethods()){  

26.     //方法签名中的访问控制符是public的,且不是静态方法  

27. if(m.getModifiers().contains(Modifier.PUBLIC) &&   

28. !(m.getModifiers().contains(Modifier.STATIC))){  

29.     interfaceMethods.add(m);  

30. }  

31. }  

32. if(interfaceMethods.size() > 0){  

33.     try{  

34.         PrintWriter writer = env.getFiler().createSourceFile(annot.value());  

35.         writer.println(“package ” + typeDecl.getPackage().getQualifiedName()   

36. +  “;”);  

37.                     writer.println(“public interface ” + annot.value() + “{“);  

38.                     //写方法声明  

39.                     for(MethodDeclaration m : interfaceMethods){  

40.     writer.print(“ public’);  

41.     writer.print(m.getReturnType() + “ ”);  

42.     writer.print(m.getSimpleName() + “ ”);  

43.     int i = 0;  

44.     //写方法参数列表  

45.     for(ParametherDeclaration parm : m.getParameters()){  

46.     writer.print(parm.getType() + “ ” + parm.getSimpleName());  

47.     if( ++i < m.getParameters().size()){  

48.     writer.print(“, ”);  

49. }  

50. }  

51. writer.println(“);”)  

52. }  

53. writer.println(“}”);  

54. writer.close();  

55. }catch(Exception e){  

56.     Throw new RuntimeException(e);  

57. }  

58. }  

59. }  

60. }  

61. }  

使用sun的mirror API可以获取源码中的Field, type, method等信息。

(4).为apt工厂提供注解处理器:

[java] view plaincopy

1. package annotations;  

2.   

3. import com.sun.mirror.apt.*;  

4. import com.sun.mirror.declaration.*;  

5. import java.util.*;  

6.   

7. public class InterfaceExtractorProcessorFactory implements AnnotationProcessorFactory{  

8.     //获取注解处理器  

9. public AnnotationProcessor getProcessorFor(Set<AnnotationTypeDeclaration> atds,  

10. AnnotationProcessorEnvironment env){  

11.     return new InterfaceExtractorProcess(env);  

12. }  

13. //定义注解处理器所支持的注解类型  

14. public Collection<String> supportedAnnotationTypes(){  

15.     return Collections.singleton(“ExtractInterface”);  

16. }  

17. //定义注解处理器支持的选项  

18. public Collection<String> supportedOptions(){  

19.     return Collections.emptySet();  

20. }  

21. }     

(5).运行apt:

使用下面的命令允许apt:

apt-factory annotations.InterfaceExtractorProcessorFactory Multiplier.java –s ../annotations

输出结果:

package annotations;

public interface IMultiplier{

            public int multiply(intx, int y);

}

 

 

18——并发编程(一) 

线程是进程中一个任务控制流序列,由于进程的创建和销毁需要销毁大量的资源,而多个线程之间可以共享进程数据,因此多线程是并发编程的基础。

多核心CPU可以真正实现多个任务并行执行,单核心CPU4程序其实不是真正的并行运行,而是通过时间片切换来执行,由于时间片切换频繁,使用者感 觉程序是在并行运行。单核心CPU中通过时间片切换执行多线程任务时,虽然需要保存线程上下文,但是由于不会被阻塞的线程所阻塞,因此相比单任务还是大大 提高了程序运行效率。

1.线程的状态和切换:

线程的7种状态及其切换图如下:

2.多线程简单线程例子:

Java中实现多线程常用两种方法是:实现Runnable接口和继承Thread类。

(1).继承Thread类的多线程例子如下:

[java] view plaincopy

1. class PrimeThread extends Thread {  

2.          long minPrime;  

3.          PrimeThread(long minPrime) {  

4.              this.minPrime = minPrime;  

5.          }  

6.         //重写Thread类的run方法  

7.          public void run() {  

8.               . . .  

9.          }  

10.      }  

启动继承Thread类线程的方法:

[java] view plaincopy

1. PrimeThread p = new PrimeThread(143);  

2. p.start();  

(2).实现Runnable接口的多线程例子如下:

[java] view plaincopy

1. class PrimeRun implements Runnable {  

2. long minPrime;  

3. PrimeRun(long minPrime) {  

4. this.minPrime = minPrime;  

5. }  

6. public void run() {  

7. . . .  

8. }  

9. }  

启动实现Runnable接口线程的方法:

[java] view plaincopy

1. PrimeThread p = new Thread(new PrimeThread(143));  

2. p.start();  

由于java的单继承特性和面向接口编程的原则,建议使用实现Runnable接口的方式实现java的多线程。 

3.使用Executors线程池:

在JDK5中,在java.util.concurrent包中引入了Executors线程池,使得创建多线程更加方便高效,例子如下:

[java] view plaincopy

1. import java.util.concurrent.*;  

2.   

3. public class CachedThreadPool{  

4.     public static void main(String[] args){  

5.         //创建一个缓冲线程池服务  

6.         ExecutorService exec = Executors.newCachedThreadPool();  

7.         for(int i=0; i<5; i++){  

8.             //线程池服务启动线程  

9.     exec.execute(  

10.     new Runnable(){  

11.         //使用匿名内部类实现的java线程  

12.     public void run(){  

13.         System.out.println(“Thread ” + i + “ is running”);  

14. }  

15. }  

16. );  

17.         //线程池服务停止  

18.         exec.shoutdown();  

19. }  

20. }  

21. }  

Executors.newCachedThreadPool()方法创建缓冲线程池,即在程序运行时创建尽可能多需要的线程,之后停止创建新的线 程,转而通过循环利用已经创建的线程。Executors.newFixedThreadPool(intsize)方法创建固定数目的线程池,即程序会 创建指定数量的线程。缓冲线程池效率和性能高,推荐优先考虑使用。

Executors.newSingleThreadPool()创建单线程池,即固定数目为1的线程池,一般用于长时间存活的单任务,例如网络socket连接等,如果有多一个任务需要执行,则会放进队列中顺序执行。

4.获取线程的返回值:

实现Runnable接口的线程没有返回值,如果想获取线程的返回值,需要实现Callable接口,Callable是JDK5中引入的现实线程的接口,其call()方法代替Runnable接口的run方法,可以获取线程的返回值,例子如下:

[java] view plaincopy

1. import java.util.concurrent.*;  

2. import java.util.*;  

3.   

4. class TaskWithResult implements Callable<String>{  

5.     private int id;  

6.     public TaskWithResult(int id){  

7.         this.id = id;  

8. }  

9. public String call(){  

10.     return “result of TaskWithResult ” + id;  

11. }  

12. public static void main(String[] args){  

13.     ExecutorService exec = Executors.newCachedThreadPool();  

14.     List<Future<String>> results = new ArrayList<Future<String>>();  

15.     for(int i=0; i<5; i++){  

16.         //将线程返回值添加到List中  

17.         results.add(exec.submit(new TaskWithResult(i)));  

18. }  

19. //遍历获取线程返回值  

20. for(Future<String> fs : results){  

21.         try{  

22.     System.out.println(fs.get());  

23. }catch(Exception e){  

24.     System.out.println(e);  

25. }finally{  

26.     exec.shutdown();  

27. }  

28. }  

29. }  

30. }  

输出结果(可能的结果,由于多线程执行顺序不确定,结果不固定):

result of TaskWithResult 0

result of TaskWithResult 1

result of TaskWithResult 3

result of TaskWithResult 4

result of TaskWithResult 5

注解:使用线程池服务的submit()方法执行线程池时,会产生Future<T>对象,其参数类型是线程Callable的call()方法返回值的类型,使用Future对象的get()方法可以获取线程返回值。

5.线程休眠:

在jdk5之前,使用Thread.sleep(1000)方法可以使线程休眠1秒钟,在jdk之后,使用下面的方法使线程休眠:

TimeUnit.SECONDS.sleep(1);

线程休眠的方法是TimeUnit枚举类型中的方法。

注意:不论是Thread.sleep还是TimeUnit的sleep线程休眠方法,都要捕获InterruptedExecutors。

6.线程优先级:

线程的优先级是指线程被线程调度器调度执行的优先级顺序,优先级越高表示获取CPU允许时间的概率越大,但是并不是绝对的,因为线程调度器调度线程 是不可控制的,只是一个可能性的问题。可以通过Thread线程对象的getPriority()方法获取线程的优先级,可以通过线程对象的 setPriority()方法设置线程的优先级。

Java的线程优先级总共有10级,最低优先级为1,最高为10,Windows的线程优先级总共有7级并且不固定,而Sun的Soloaris操 作系统有23级,因此java的线程优先级无法很好地和操作系统线程优先级映射,所有一般只使用 MAX_PRIORITY(10),NORM_PRIORITY(5)和MIN_PRIORITY(1)这三个线程优先级。

7.守护线程:

守护线程(DaemonThread)是某些提供通用服务的在后台运行的程序,是优先级最低的线程。当所有的非守护线程执行结束后,程序会结束所有的守护线程而终止运行。如果当前还有非守护线程的线程在运行,则程序不会终止,而是等待其执行完成。守护进程的例子如下:

[java] view plaincopy

1. import java.util.concurrent.*;  

2.   

3. public class SimpleDaemons implements Runnable{  

4.     public void run{  

5.         try{  

6. System.out.println(“Start daemons”);  

7. TimeUtil.SECONDS.sleep(1);  

8. }catch(Exception e){  

9. System.out.println(“sleep() interrupted”);  

10. }finally{  

11.     System.out.println(“Finally is running”);  

12.   

13. }  

14. }  

15. public static void main(String[] args) throws Exception{  

16. Thread daemon = new Thread(new SimpleDaemons());  

17.     daemon.setDaemon(true);  

18.     daemon.start();  

19. }  

20. }  

输出结果:

Start daemons

Finally没有执行,如果注释掉daemon.setDaemon(true)设置守护进程这一句代码。

输出结果:

Start daemons

Finally is running

之所以产生这样的结果原因是,main()是这个程序中唯一的非守护线程,当没有非守护线程在运行时,JVM强制推出终止守护线程的运行。

通过Thread对象的setDaemon方法可以设置线程是否为守护线程,通过isDaemon方法可以判断线程对象是否为守护线程。

由守护线程创建的线程对象不论有没有通过setDaemon方法显式设置,都是守护线程。

8.synchronized线程同步:

编程中的共享资源问题会引起多线程的竞争,为了确保同一时刻只有一个线程独占共享资源,需要使用线程同步机制,即使用前对共享资源加锁,使用完毕之后释放锁。

Java中通过synchronized关键字实现多线程的同步,线程同步可以分为以下几种:

(1).对象方法同步:

[java] view plaincopy

1. public synchronized void methodA(){       

2.         System.out.println(this);       

3.     }    

每个对象有一个线程同步锁与之关联,同一个对象的不同线程在同一时刻只能有一个线程调用methodA方法。

(2).类所有对象方法同步:

[java] view plaincopy

1. public synchronized static void methodB(){       

2.         System.out.println(this);       

3.     }    

静态方法的线程同步锁对类的所有对象都起作用,即所有对象的线程在同一时刻只能有一个类的一个线程调用该方法。

(3).对象同步代码块:

[java] view plaincopy

1. public void methodC(){    

2.         synchronized(this){    

3.             System.out.println(this);    

4.         }    

5.     }    

使用当前对象作为线程同步锁,同一个对象的不同线程在同一时刻只能有一个线程调用methodC方法中的代码块。

(4).类同步代码块:

[java] view plaincopy

1. public void methodD(){    

2.         synchronized(className.class){    

3.             System.out.println(this);    

4.         }    

5.     }    

使用类字节码对象作为线程同步锁,类所有对象的所有线程在同一时刻只能有一个类的一个线程调用methodD的同步代码块。

注意:线程的同步是针对对象的,不论是同步方法还是同步代码块,都锁定的是对象,而非方法或代码块本身。每个对象只能有一个线程同步锁与之关联。

如果一个对象有多个线程同步方法,只要一个线程访问了其中的一个线程同步方法,其它线程就不能同时访问这个对象中任何一个线程同步方法。

9.线程锁:

JDK5之后,在java.util.concurrent.locks包中引入了线程锁机制,编程中可以显式锁定确保线程间同步,例子如下:

[java] view plaincopy

1. import java.util.concurrent.*;  

2. import java.util.concurrent.locks.*;  

3.   

4. public class Locking{  

5.     //创建锁  

6.     private ReentrantLock lock = new ReentrantLock();  

7.     public void untimed(){  

8.         boolean captured = lock.tryLock();  

9.         try{  

10.     System.out.println(“tryLock(): ” + captured);  

11. }finally{  

12.     if(captured){  

13.     lock.unlock();  

14. }  

15. }  

16. }  

17. public void timed(){  

18.     boolean captured = false;  

19.     try{  

20.         //对象锁定两秒钟  

21.     captured = lock.tryLock(2, TimeUnit.SECONDS);  

22. }catch(InterruptedException e){  

23.     Throw new RuntimeException(e);  

24. }try{  

25.     System.out.println(“tryLock(2, TimeUnit.SECONDS): ” + captured);  

26. }finally{  

27.     if(captured){  

28.     lock.unlock();  

29. }  

30. }     

31. }  

32. public static void main(String[] args){  

33.     //主线程  

34.     final Locking al = new Locking();  

35.     al.untimed();  

36.     al.timed();  

37.     //创建新线程  

38.     new Thread(){  

39.     {//动态代码块,对象创建时执行  

40. setDaemon(true);//当前线程设置为守护线程  

41.             }  

42.             public void run(){  

43.                 //获取al对象的线程锁并锁定al对象  

44.                 al.lock.lock();  

45.         System.out.println(“acquired”);  

46. }  

47. }.start();  

48. //主线程让出CPU  

49. Thread.yield();  

50. al.untimed();  

51.     al.timed();  

52. }  

53. }  

输出结果:

tryLock(): true 

tryLock(2, TimeUnit.SECONDS): true 

acquired 

tryLock(): false 

tryLock(2, TimeUnit.SECONDS): false

由于创建的守护线程锁定对象之后没有释放锁,所有主线程再也无法获取对象锁。

ReentrantLock可以通过lock()锁定对象,也可通过tryLock()方法来锁定对象,对于显式使用线程锁的方法体或代码块必须放在try-catch-finally块中,必须在finally中释放对象锁。

ReentrantLock和synchronized的功能是类似的,区别在于:

(1). Synchronized代码简单,只需要一行代码即可,不用try-catch-finally捕获异常,同时不用显式释放对象锁。

(2).ReentrantLock可以控制锁的锁定和释放状态,也可以指定锁定时间等。

10.volatile传播性:

volatile关键字确保变量的垮程序可见性,使用volatile声明的字段,只要发生了写改动,所有读取该字段的值都会跟着改变,即使使用了本地缓存仍然会被改变。

volatile字段会立即写入主内存中,所有的读取值都是从主内存中读取。

volatile关键字告诉编译器移除线程中的读写缓存,直接从内存中读写。volatile适用的情况:

字段被多个任务同时访问,至少有一个任务是写操作。

volatile字段的读写操作实现了线程同步。

 

 

19——并发编程(二) 

1.java中的原子操作类:

原子操作是指程序编译后对应于一条CPU操作指令,即原子操作是最小的不可再分指令集,编程中的原子操作是线程安全的,不需要使用进行线程同步和加锁机制来确保原子操作的线程同步。

JDK5中引入了java.util.concurrent.atomic原子操作类包,提供了常用的原子类型操作数据类型,如:AtomicInteger,AtomicLong, AtomicReference等等,原子操作类例子如下:

[java] view plaincopy

1. import java.util.concurrent.*;  

2. import java.util.concurrent.atomic.*;  

3. import java.util.*;  

4.   

5. public class AtomicIntegerTest implement Runnable{  

6.     //创建一个值为0的Integer类型原子类  

7.     private AtomicInteger i = new AtomicInteger(0);  

8.     public int getValue(){  

9.         //获取Integer类型原子类的值  

10.         return i.get();  

11. }  

12. private void evenIncrement(){  

13.     //值增加2  

14.     i.addAndGet(2);  

15. }  

16. public void run(){  

17.     while(true){  

18.     evenIncrement();  

19. }  

20. }  

21. public static void main(String[] args){  

22.     //创建一个定时任务,5秒钟终止运行  

23. new Timer().schedule(new TimerTask(){  

24.     public void run(){  

25.     System.err.println(“Aborting”);  

26.     System.exit(0);  

27. }  

28. }, 5000);  

29. ExecutorService exec = Executors.newCachedThreadPool();  

30. AtomicIntegerTest at = new AtomicIntegerTest();  

31. Exec.execute(at);  

32. while(true){  

33.     int val = at.getValue();  

34.     //奇数  

35.     if(val % 2 != 0){  

36.     System.out.println(val);  

37.     System.exit(0);  

38. }  

39. }  

40. }  

41. }  

Java中加减运算等不是原子操作,为了确保线程安全必须确保线程同步安全,使用整数类型的原子类之后,整数原子类的加减运算是原子操作,因此evenIncrement()方法不再需要synchronized关键字或者线程锁机制来确保线程安全。

注意:原子类不是Integer等通用类的通用替换方法,原子类不顶用诸如hashCode和compareTo之类的方法,因为源自变量是可变的,所以不是hash表键的好的选择。

2.线程局部变量:

防止多个线程对同一个共享的资源操作碰撞的另一种方法是使用线程局部变量,线程局部变量的机制是为共享的变量在每个线程中创建一个存储区存储变量副 本。线程局部变量ThreadLocal实例通常是类中的private static字段,它们希望将状态与某一个线程相关联。例子如下:

[java] view plaincopy

1. import java.util.concurrent.atomic.AtomicInteger;  

2.   

3. public class UniqueThreadGenerator{  

4.     private static final AtomicInteger uniqueId = new AtomicInteger(0);  

5.     //创建一个线程局部变量  

6.     private static final ThreadLocal<Integer> uniqueNum = new ThreadLocal<Integer>(){  

7.     //覆盖线程局部变量的initialValue方法,为线程局部变量初始化赋值  

8.     protected Integer initialValue(){  

9. return uniqueId.getAndIncrement();  

10. }     

11. }  

12. public static int getCurrentThreadId(){  

13.     //获取线程局部变量的当前线程副本中的值  

14.     return uniqueId.get();  

15. }  

16. }  

线程局部变量java.lang.ThreadLocal类有下面四个方法:

(1).T get():返回此线程局部变量的当前线程副本中的值。

(2).protected T initialValue():返回此线程局部变量的当前线程的初始值。

(3).void remove():移除此线程局部变量当前线程的值。

(4).void set(T value):将此线程局部变量的当前线程副本中的值设置为指定值。

只要线程是活动的并且线程局部变量实例是可访问的,每个线程都保持对其线程局部变量副本的隐式引用。在线程消失之后,其线程局部变量实例的所有副本都会被垃圾回收。

3.线程的yield,sleep和wait的区别:

(1).yield:

正在运行的线程让出CPU时间片,让线程调度器运行其他优先级相同的线程。使用yield()处于可运行状态的线程有可能立刻又进入执行状态。

yield不释放对象锁,即yield线程对象中其他synchronized的数据不能被别的线程使用。

(2).sleep:

当前正在运行的线程进入阻塞状态,sleep是线程Thread类的方法,在sleep的时间内线程肯定不会再运行,sleep可使优先级低得线程得到执行的机会,也可以让同优先级和高优先级的线程有执行机会。

sleep的线程如果持有对象的线程锁,则sleep期间也不会释放线程锁,即sleep线程对象中其他synchronized的数据不能被别的线程使用。

sleep方法可以在非synchronized线程同步的方法中调用,因为sleep()不释放锁。

(3).wait:

正在运行的线程进入等待队列中,wait是Object的方法,线程调用wait方法后会释放掉所持有的对象锁,即wait线程对象中其他synchronized的数据可以被别的线程使用。

wait(), notify()和notifyAll()方法必须只能在synchronized线程同步方法中调用,因为在调用这些方法之前必须首先获得线程锁,如果 在非线程同步的方法中调用时,编译时没有问题,但在运行时会抛IllegalMonitorStateException异常,异常信息是当前线程不是方 法对象的监视器对象所有者。

4.线程间的通信:

Java中通过wait(),notify()和notifyAll()方法进行线程间的通信,在JDK5之后,又引入了signal()和signalAll()进行线程通信。

(1).wait()和notify(),notifyAll():

wait使得一个正在执行的线程进入阻塞状态,wait也可以象sleep一样指定时间,但是常用的是无参数的wait()方法。notify和 notifyAll唤醒处于阻塞态的线程进入可运行状态,通知他们当前的CPU可用。这三个方法都是Object中的方法,不是线程Thread的特有方 法。例子如下:

[java] view plaincopy

1. import java.util.concurent.*;  

2.   

3. class Car{  

4.     private Boolean waxOn = false;  

5.     public synchronized void waxed(){  

6.     waxOn = true;  

7.     notifyAll();  

8. }  

9. public synchronized void buffed(){  

10.     waxOn = false;  

11.     notifyAll();  

12. }   

13. public synchronized void waitForWaxing()throws InterruptedException{  

14.     while(waxOn == false){  

15.         wait();  

16. }  

17. }  

18. public synchronized void waitForBuffing()throws InterruptedException{  

19.     while(waxOn == true){  

20.     wait();  

21. }  

22. }  

23. }  

24. class WaxOn implements Runnable{  

25.     private Car car;  

26.     public WaxOn(Car c){  

27.     car = c;  

28. }  

29. public void run(){  

30.     try{  

31.     while(!Thread.interrupted()){  

32.     System.out.println(“Wax On!”);  

33.     TimeUnit.SECONDS.sleep(1);  

34.     car.waxed();  

35.     car.waitForBuffing();  

36. }  

37. }catch(InterruptedException e){  

38.     System.out.println(“Exiting via interrupt”);  

39. }  

40. System.out.println(“Ending Wax On task”);  

41. }  

42. }  

43. class WaxOff implements Runnable{  

44.     private Car car;  

45.     public WaxOff(Car c){  

46.     car = c;  

47. }  

48. public void run(){  

49.     try{  

50.     while(!Thread.interrupted()){  

51.         car.waitForWaxing();  

52.     System.out.println(“Wax Off!”);  

53.     TimeUnit.SECONDS.sleep(1);  

54.     car.buffed();  

55. }  

56. }catch(InterruptedException e){  

57.     System.out.println(“Exiting via interrupt”);  

58. }  

59. System.out.println(“Ending Wax Off task”);  

60. }  

61. }  

62. public class WaxOMatic{  

63.     public static void main(String[] args)throws Exception{  

64.     Car car = new Car();  

65.     ExecutorService exec = Executors.newCachedThreadPool();  

66.     exec.execute(new WaxOff(car));  

67.     exec.execute(new WaxOn(car));  

68.     TimeUnit.SECONDS.sleep(5);  

69.     exec.shutdownNow();  

70. }  

71. }  

输出结果:

Wax On! Wax Off! Wax On! Wax Off! Wax On!

Exiting via interrupt 

Ending Wax On task 

Exiting via interrupt 

Ending Wax Off task

注意:waite (),notify()和notifyAll()因为会对对象的“锁标志”进行操作,所以它们必须在synchronized函数或 synchronized block中进行调用。如果在non-synchronized函数或non-synchronized block中进行调 用,虽然能编译通过,但在运行时会发生IllegalMonitorStateException的异常。

(2).await(), signal()和signalAll()进行线程通信:

JDK5中引入了线程锁Lock来实现线程同步,使用Condition将对象的监视器分解成截然不同的对象,以便通过这些对象与任意线程锁Lock组合使用,使用signal和signalAll唤醒处于阻塞状态的线程,例子如下:

[java] view plaincopy

1. import java.util.concurrent.*;  

2. import java.util.concurrent.lock.*;  

3.   

4. class Car{  

5.     private Lock lock = new ReentrantLock();  

6.     //获取线程锁的条件实例  

7.     private Condition condition = lock.newCondition();  

8.     private Boolean waxOn = false;  

9.     public void waxed(){  

10.     lock.lock();  

11.     try{  

12.     waxOn = true;  

13.     //唤醒等待的线程  

14.     condition.signalAll();  

15. }finally{  

16.     lock.unlock();  

17. }  

18. }  

19. public void buffed(){  

20.     lock.lock();  

21.     try{  

22.     waxOn = false;  

23.     //唤醒等待的线程  

24.     condition.signalAll();  

25. }finally{  

26.     lock.unlock();  

27. }  

28. }   

29. public void waitForWaxing()throws InterruptedException{  

30.     lock.lock();  

31.     try{  

32.     while(waxOn == false){  

33.         //使当前线程处于等待状态  

34.     condition.await();  

35. }finally{  

36.     lock.unlock();  

37. }  

38. }  

39. }  

40. public void waitForBuffing()throws InterruptedException{  

41.     lock.lock();  

42.     try{  

43.     while(waxOn == true){  

44.         //使当前线程处于等待状态  

45.     condition.await();  

46. }finally{  

47.     lock.unlock();  

48. }  

49. }  

50. }  

51. }  

52. class WaxOn implements Runnable{  

53.     private Car car;  

54.     public WaxOn(Car c){  

55.     car = c;  

56. }  

57. public void run(){  

58.     try{  

59.     while(!Thread.interrupted()){  

60.     System.out.println(“Wax On!”);  

61.     TimeUnit.SECONDS.sleep(1);  

62.     car.waxed();  

63.     car.waitForBuffing();  

64. }  

65. }catch(InterruptedException e){  

66.     System.out.println(“Exiting via interrupt”);  

67. }  

68. System.out.println(“Ending Wax On task”);  

69. }  

70. }  

71. class WaxOff implements Runnable{  

72.     private Car car;  

73.     public WaxOff(Car c){  

74.     car = c;  

75. }  

76. public void run(){  

77.     try{  

78.     while(!Thread.interrupted()){  

79.         car.waitForWaxing();  

80.     System.out.println(“Wax Off!”);  

81.     TimeUnit.SECONDS.sleep(1);  

82.     car.buffed();  

83. }  

84. }catch(InterruptedException e){  

85.     System.out.println(“Exiting via interrupt”);  

86. }  

87. System.out.println(“Ending Wax Off task”);  

88. }  

89. }  

90. public class WaxOMatic{  

91.     public static void main(String[] args)throws Exception{  

92.     Car car = new Car();  

93.     ExecutorService exec = Executors.newCachedThreadPool();  

94.     exec.execute(new WaxOff(car));  

95.     exec.execute(new WaxOn(car));  

96.     TimeUnit.SECONDS.sleep(5);  

97.     exec.shutdownNow();  

98. }  

99. }  

输出结果:

Wax On! Wax Off! Wax On! Wax Off! Wax On! 

Exiting via interrupt 

Ending Wax On task 

Exiting via interrupt 

Ending Wax Off task

5.使用ScheduledThreadPoolExecutor实现定时任务:

JDK1.5之前,使用Timer实现定时任务,JDK1.5之后引入了ScheduleThreadPoolExecutor实现多线程的定时任务。例子如下:

[java] view plaincopy

1. import java.util.concurrent.*;  

2. import java.util.*;  

3.   

4. public class DemoSchedule{  

5.     private final ScheduledExecutorService scheduler =   

6.  Executors.newScheduledThreadPool(1);  

7.     //创建并在给定延迟后时间启动的一次性操作  

8. public void schedule(Runnable event, long delay){  

9.     scheduler.schedule(event, delay, TimeUnit.SECOND);  

10. }  

11. //创建并在给定延迟时间之后,每个给定时间周期性执行的操作  

12.     public void repeat(Runnable event, long initialDelay, long period){  

13.     scheduler.scheduleAtFixedRate(event, initialDelay, period, TimeUnit.SECOND);  

14. }  

15. }  

16. class OnetimeSchedule implement Runnable{  

17.     public void run(){  

18.     System.out.println(“One time schedule task”);  

19. }  

20. }   

21. class RepeatSchedule implement Runnable{  

22.     public void run(){  

23.     System.out.println(“Repeat schedule task”);  

24. }  

25. }  

26. class TestSchedule {  

27.     public static void main(String[] args){  

28.     DemoSchedule scheduler = new DemoSchedule();  

29.     scheduler.schedule(new OnetimeSchedule(), 5);  

30.     scheduler.schedule(new RepeatSchedule (), 10, 10);  

31. }  

32. }  

输出结果:

5秒钟之后打印输出:One timeschedule task

10秒钟之后打印输出:Repeatschedule task

之后每隔10秒钟打印输出:Repeat schedule task

6.信号量Semaphore:

一个正常的线程同步锁concurrent.locks或者内置的synchronized只允许同一时间一个任务访问一个资源,而计数信号量 Semaphore运行多个任务在同一时间访问一个资源。Semaphore是一个计数信号量,维护了一个许可集,通常用于限制可以访问某些资源的线程数 目。例子如下:

[java] view plaincopy

1. import java.util.concurrent.*;  

2. import java.util.*;  

3.   

4. public class Pool<T> {  

5.     //限制的线程数目  

6.     private int size;  

7.     //存放资源集合  

8.     private List<T> items = new ArrayList<T>();  

9.     //标记资源是否被使用  

10.     private volatile boolean[] checkedOut;  

11.     private Semaphore available;  

12.     public Pool(Class<T> classObject, int size){  

13.     this.size = size;  

14.     checkedOut = new boolean[size];  

15.     //创建信号量对象  

16.     available = new Semaphore(size, true);  

17.     for(int i = 0; i < size; ++i){  

18.     try{  

19.         //加载资源对象并存放到集合中  

20.     items.add(classObject.newInstance());  

21. }catch(Exception e){  

22. throw new RuntimeException(e);  

23. }  

24. }  

25. }  

26. //访问资源  

27. public T checkout() throws InterruptedException{  

28.     //从信号量中获取一个资源许可  

29.     available.acquired();  

30.     return getItem();  

31. }  

32. //释放资源  

33. public void checkIn(T x){  

34.     if(releaseItem(x))  

35.         //释放一个资源许可,将其返回给信号量  

36.         available.release();  

37. }  

38. //获取资源  

39. private synchronized T getItem(){  

40.     for(int i = 0; i < size; ++i){  

41.         //资源没有被使用  

42.     if(!checkedOut[i]){  

43.         //标记资源被使用  

44.     checkedOut[i] = true;  

45.     return items.get(i);  

46. }  

47. }  

48. return null;  

49. }  

50. private synchronized boolean releaseItem(T item){  

51.     int index = items.indexOf(item);  

52.     //资源不在资源集合中  

53.     if(index == -1) return false;  

54.     //资源正在被使用  

55.     if(checkedOut[index]){  

56.         //将资源标记为不再使用  

57.     checkedOut[index] = false;  

58.     return true;  

59. }  

60. return false;  

61. }  

62. }  

在访问资源前,每个线程必须首先从信号量获取许可,从而保证可以使用该资源。该线程结束后,将资源释放回资源池中并将许可返回给信号量,从而允许其他线程获取和使用该资源。

如果当前信号量中没有资源访问许可,则信号量会阻塞调用的线程直到获取一个资源许可,否则,线程将被中断。

注意:调用信号量的acquire时无法保持同步锁,因为这会阻止将该资源返回到资源池中,信号量封装所需要的同步,以限制对资源池的访问。

7.Exchanger线程同步交换器:

Exchanger类,可以用来完成线程间的数据交换。 类java.util.concurrent.Exchanger提供了一个同步点,在这 个同步点,一对线程可以交换数据。每个线程通过exchange()方法的入口提供数据给他的伙伴线程,并接收他的伙伴线程提供的数据,并返回。当两个线 程通过Exchanger交换了对象,这个交换对于两个线程来说都是安全的。例子如下:

[java] view plaincopy

1. //杯子类  

2. class Cup{  

3. private int capacity = 0;  

4. public Cup(int capacity){  

5. this.capacity = capacity;  

6. }  

7. public int getCapacity(){  

8.     return capacity;  

9. }  

10. public void addWaterToCup(int i){  

11. capacity += I;  

12.         capacity = capacity > 100 ? 100 : capacity;  

13. }  

14. public void drinkWaterFromCup(int i){  

15. capacity += I;  

16. capacity = capacity < 0 ? 0 : capacity;  

17. }  

18. public void isFull(){  

19. return capacity == 100 ? true : false;  

20. }  

21. public void isEmpty(){  

22. return capacity == 0 ? true : false;  

23. }  

24. }  

25. import  java.util.concurrent.Exchanger;  

26. import java.util.*;  

27.   

28. public class TestExchanger{  

29.     Cup emptyCup = new Cup(0);  

30.     Cup fullCup = new Cup(100);  

31.     Exchanger<Cup> exchanger = new Exchanger<Cup>();  

32.       

33. //服务员类  

34. class Waiter implements Runnable{  

35.         private int addSpeed = 1;  

36.         public Waiter(int addSpeed){  

37.         this.addSpeed = addSpeed;  

38. }  

39. public void run(){  

40.         while(emptyCup != null){  

41.         try{  

42.             //如果杯子已满,则与顾客交换,服务员有获得空杯子  

43.         if(emptyCup.isFull()){  

44.         emptyCup = exchanger.exchange(emptyCup);  

45.         System.out.println(“Waiter: ” + emptyCup. getCapacity());  

46. }else{  

47.     emptyCup.addWaterToCup(addSpeed);  

48. System.out.println(“Waiter add ” + addSpeed +   

49. “ and current capacity is:” + emptyCup. getCapacity());  

50.                         TimeUnit.SECONDS.sleep(new Random().nextInt(10));  

51. }  

52. }catch(InterruptedException e){  

53.     e.printStackTrack();  

54. }  

55. }  

56. }  

57. }  

58. //顾客类  

59. class Customer implements Runnable{  

60.     int drinkSpeed = 1;  

61.     public Customer(int drinkSpeed){  

62.     this.drinkSpeed = drinkSpeed;  

63. }  

64. public void run(){  

65.     while(fullCup != null){  

66.     try{  

67.         //如果杯子已空,则与服务员进行交换,顾客获得装满水的杯子  

68.         if(fullCup.isEmpty()){  

69.     fullCup = exchanger.exchanger(fullCup);  

70. System.out.println(“Customer: ” + fullCup. getCapacity());  

71. }else{  

72.     fullCup.drinkWaterFromCup(drinkSpeed);  

73. System.out.println(“Customer drink ” + drinkSpeed +   

74. “ and current capacity is:” + fullCup.getCapacity());  

75.                         TimeUnit.SECONDS.sleep(new Random().nextInt(10));  

76. }  

77. }catch(InterruptedException e){  

78.     e.printStackTrack();  

79. }  

80. }  

81. }  

82. }  

83. public static void main(String[] args){  

84.     new Thread(new Waiter(3)).start();  

85.     new Thread(new Customer(6)).start();  

86. }  

87. }  

 

 

Effective java》读书笔记1——避免创建不必要的对象 

Java中Sting很特别,有如下两种初始化方式:

(1).String s1 = “This isstring1”;

(2).String s2 = new String(“Thisis string2”);

第一种字符串初始化方式,当有多于一个字符串的内容相同情况,字符串内容会放在字符串缓冲池中,即字符串内容在内存中只有一份。

第二种字符串初始化方式,不论有没有字符串值相同,每次都会在内存堆中存储字符串的值。

如果一个方法中字符串的值都相同,调用100万次情况下第一种字符串初始化方式的内存占用率很低,性能非常高,而第二种方式的字符串初始化则会占用大量的内存.

看下面一个例子,直观感受创建不必要对象的性能危害:

[java] view plaincopy

1. public class Person{  

2.     private Date birthDate;  

3.     //判断是否是婴儿潮出生的人  

4.     public boolean isBabyBoomer(){  

5.     Calendar cal = Calendar.getInstance(TimeZone.getTimeZone(“GMT”));  

6.     //婴儿潮开始时间  

7.     cal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);  

8. Date boomStart = cal.getTime();  

9. //婴儿潮结束时间  

10.     cal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);  

11. Date boomEnd = cal.getTime();  

12. return birthDate.compareTo(boomStart) >= 0 && birthDate.compareTo(boomEnd) < 0;  

13. }  

14. }  

每次调用isBabyBoomer()方式时,都需要创建Calendar,TimeZone,boomStart和boomEnd四个对象,测试调用该方法1000万次,大约耗时32秒。

Calendar,TimeZone,boomStart和boomEnd四个对象是所有调用者共用的对象,只需创建一份即可,改进之后代码如下:

[java] view plaincopy

1. public class Person{  

2.     private Date birthDate;  

3.     private static final Date BOOM_START;  

4.     private static final Date BOOM_END;  

5.     static{  

6.     Calendar cal = Calendar.getInstance(TimeZone.getTimeZone(“GMT”));  

7.     //婴儿潮开始时间  

8.     cal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);  

9. BOOM_START = cal.getTime();  

10. //婴儿潮结束时间  

11.     cal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);  

12. BOOM_END = cal.getTime();  

13. }  

14.     //判断是否是婴儿潮出生的人  

15.     public boolean isBabyBoomer(){  

16. return birthDate.compareTo(BOOM_START) >= 0 && birthDate.compareTo(BOOM_END) < 0;  

17. }  

18. }  

经测试,调用1000万次,耗时大约130毫秒,性能提高250倍。

再看第二个例子,计算所有的int之和,代码如下:

[java] view plaincopy

1. public CalculateInt{  

2.     public static void main(String[] args){  

3.     Long sum = 0L;  

4.     for(long i = 0; i < Integer.MAX_VALUE; i++){  

5.     sum += i;  

6. }  

7. System.out.println(sum);  

8. }  

9. }  

由于sum是Long类型,而不是原始类型的long,因此总共进行了2的31次方不必要的类型自动封装(将原始类型的long封装为Long),总共耗时大约43秒。

将sum声明的由Long类型改为原始类型的long,由于省去了2的31次方不必要的类型自动封装,程序大概耗时6秒,性能提高7倍。

Effective java》读书笔记2——内存泄漏 

JVM提供了垃圾回收器,java程序员再也不用想C/C++程序员一样整天为对象的清理工作而焦头烂额,但是并非说Java中不存在内存泄漏的情况,下面展示一个典型的内存溢出例子。

使用数组作为底层数据结构实现一个Stack栈数据结构的容器,代码如下:

[java] view plaincopy

1. public class Stack{  

2.     private Object[] elements;  

3.     private int size = 0;  

4.     private static final int DEFAULT_SIZE = 15;  

5.     public Stack(){  

6.     elements = new Object[DEFAULT_SIZE];  

7. }  

8. public void push(Object o){  

9.     ensureCapatity();  

10.     elements[size++] = o;  

11. }  

12. public Object pop(){  

13.     if(size == 0){  

14.     throw new EmptyStackException();  

15. }  

16. return elements[--size];  

17. }  

18. //如果栈已满,则将容量扩大一倍  

19. public ensureCapatity(){  

20.     if(elements.length == size){  

21.     elements = Arrays.copyOf(elements, 2 * size + 1);  

22. }  

23. }  

24. }  

咋一看可能看不出什么问题,相信很多人也写过这样的代码,如果使用大量数据测试就会发现内存持续上升,如果把测试数据增大一倍,发现内存也跟着增长 一倍,这就产生了典型的内存泄漏情况。仔细研究出栈pop()操作你会发现,栈中存在大量的无法被引用到的对象,因为出栈之后并没有将元素标记为不再使 用,这些在size之后的元素就成了无法被访问的无效引用对象,但是由于stack栈对象本身还是有效引用,因此这些无效的引用无法被垃圾回收器回收,造 成内存泄漏。

解决上述例子内存泄漏的方法也很简单,重新pop()方法如下:

[java] view plaincopy

1. public Object pop(){  

2.     if(size == 0){  

3.     throw new EmptyStackException();  

4. }  

5. Object result = elements[--size];  

6. elements[size] = null;//将已经出栈的元素置为null,垃圾回收器就会清理这些对象  

7. return result;  

8. }  

Java中经常引起内存泄漏的情况及解决方法:

(1).长生命周期的集合容器对象中存在大量无效引用的短生命元素对象:将容器中不再使用的对象标记为null,以防止产生无法访问的无效引用。

(2).通用的资源服务,如namingservice,cache等等:定时清理;使用WeakReference弱引用;使用WeakHashMap容器等。

(3).监听器和callback回调函数。使用WeakReference弱引用;使用WeakHashMap容器等。

 

 

 

 

Java命令参数说明大全 

Java 在运行已编译完成的类时,是通过 java 虚拟机来装载和执行的,java 虚拟机通过操作
系统命令 JAVA_HOME\bin\java –option 来启动,-option 为虚拟机参数,JAVA_HOME 为JDK
安装路径,通过这些参数可对虚拟机的运行状态进行调整,掌握参数的含义可对虚拟机的运
行模式有更深入理解。 
 
一、  查看参数列表: 

虚拟机参数分为基本和扩展两类,在命令行中输入 JAVA_HOME\bin\java 就可得到基本参数
列表, 
 
在命令行输入 JAVA_HOME\bin\java –X 就可得到扩展参数列表。 
 
二、  基本参数说明: 
 
1.  -client,-server 
 
这两个参数用于设置虚拟机使用何种运行模式,client 模式启动比较快,但运行时性能和
内存管理效率不如 server 模式,通常用于客户端应用程序。相反,server 模式启动比 client
慢,但可获得更高的运行性能。 
 
在 windows上,缺省的虚拟机类型为 client 模式,如果要使用 server模式,就需要在启动
虚拟机时加-server 参数,以获得更高性能,对服务器端应用,推荐采用 server 模式,尤
其是多个 CPU 的系统。在 Linux,Solaris 上缺省采用 server模式。 
 
 
2.  -hotspot 
 
含义与 client 相同,jdk1.4 以前使用的参数,jdk1.4 开始不再使用,代之以 client。 
 
 
3.  -classpath,-cp 
虚拟机在运行一个类时,需要将其装入内存,虚拟机搜索类的方式和顺序如下: 
 
Bootstrap classes,Extension classes,User classes。 
 
Bootstrap 中的路径是虚拟机自带的 jar 或 zip 文件,虚拟机首先搜索这些包文件,用
System.getProperty("sun.boot.class.path")可得到虚拟机搜索的包名。 
 
Extension 是位于 jre\lib\ext 目录下的 jar 文件,虚拟机在搜索完 Bootstrap 后就搜索该
目录下的 jar 文件。用 System. getProperty("java.ext.dirs”)可得到虚拟机使用
Extension 搜索路径。 
 
User classes 搜索顺序为当前目录、环境变量 CLASSPATH、-classpath。 
 
 
4.  -classpath 
告知虚拟机搜索目录名、jar 文档名、zip 文档名,之间用分号;分隔。 
 
例如当你自己开发了公共类并包装成一个 common.jar 包,在使用 common.jar 中的类时,就
需要用-classpath common.jar 告诉虚拟机从 common.jar 中查找该类,否则虚拟机就会抛
出 java.lang.NoClassDefFoundError异常,表明未找到类定义。 
 
在运行时可用 System.getProperty(“java.class.path”)得到虚拟机查找类的路径。 
 
 
使用-classpath 后虚拟机将不再使用 CLASSPATH 中的类搜索路径,如果-classpath 和
CLASSPATH 都没有设置,则虚拟机使用当前路径(.)作为类搜索路径。 
 
推荐使用-classpath 来定义虚拟机要搜索的类路径,而不要使用环境变量 CLASSPATH 的搜
索路径,以减少多个项目同时使用 CLASSPATH 时存在的潜在冲突。例如应用 1 要使用
a1.0.jar 中的类 G,应用 2 要使用 a2.0.jar 中的类 G,a2.0.jar 是 a1.0.jar 的升级包,当
a1.0.jar,a2.0.jar 都在 CLASSPATH 中,虚拟机搜索到第一个包中的类 G 时就停止搜索,
如果应用1应用2的虚拟机都从CLASSPATH中搜索,就会有一个应用得不到正确版本的类G。 
 
 
5.  -D<propertyName>=value 
在虚拟机的系统属性中设置属性名/值对,运行在此虚拟机之上的应用程序可用

 
当虚拟机报告类找不到或类冲突时可用此参数来诊断来查看虚拟机从装入类的情况。
 
 
7.  -verbose:gc 
在虚拟机发生内存回收时在输出设备显示信息,格式如下: 
 
[Full GC 268K->168K(1984K), 0.0187390 secs] 
 
该参数用来监视虚拟机内存回收的情况。 
 
 
8.  -verbose:jni 
在虚拟机调用 native 方法时输出设备显示信息,格式如下: 
 
[Dynamic-linking native method HelloNative.sum ... JNI] 
 
该参数用来监视虚拟机调用本地方法的情况,在发生 jni错误时可为诊断提供便利。 
 
 
9.  -version 
显示可运行的虚拟机版本信息然后退出。一台机器上装有不同版本的 JDK 时 
 
 
10. -showversion 
显示版本信息以及帮助信息。 
 
 
11. -ea[:<packagename>...|:<classname>] 
12. -enableassertions[:<packagename>...|:<classname>] 
 
从 JDK1.4 开始,java 可支持断言机制,用于诊断运行时问题。通常在测试阶段使断言有效,
在正式运行时不需要运行断言。断言后的表达式的值是一个逻辑值,为 true 时断言不运行,
为 false 时断言运行,抛出 java.lang.AssertionError 错误。 
 
上述参数就用来设置虚拟机是否启动断言机制,缺省时虚拟机关闭断言机制,用-ea 可打开
断言机制,不加<packagename>和 classname 时运行所有包和类中的断言,如果希望只运行
某些包或类中的断言,可将包名或类名加到-ea 之后。例如要启动包 com.foo.util 中的断
言,可用命令 –ea:com.foo.util 。 
 
 
13. -da[:<packagename>...|:<classname>] 
14. -disableassertions[:<packagename>...|:<classname>] 
 

用来设置虚拟机关闭断言处理,packagename和classname 的使用方法和-ea 相同。 
 
 
15. -esa | -enablesystemassertions 
设置虚拟机显示系统类的断言。 
 
 
16. -dsa | -disablesystemassertions 
设置虚拟机关闭系统类的断言。 
 
 
17. -agentlib:<libname>[=<options>] 
该参数是 JDK5 新引入的,用于虚拟机装载本地代理库。 
 
Libname 为本地代理库文件名,虚拟机的搜索路径为环境变量 PATH 中的路径,options 为传
给本地库启动时的参数,多个参数之间用逗号分隔。在 Windows 平台上虚拟机搜索本地库名
为 libname.dll 的文件,在 Unix 上虚拟机搜索本地库名为 libname.so 的文件,搜索路径环
境变量在不同系统上有所不同,Linux、SunOS、IRIX上为LD_LIBRARY_PATH,AIX上为LIBPATH,
HP-UX 上为SHLIB_PATH。 
 
 
例如可使用-agentlib:hprof 来获取虚拟机的运行情况,包括 CPU、内存、线程等的运行数
据,并可输出到指定文件中,可用-agentlib:hprof=help 来得到使用帮助列表。在 jre\bin
目录下可发现 hprof.dll 文件。 
 
 
18. -agentpath:<pathname>[=<options>] 
设置虚拟机按全路径装载本地库,不再搜索 PATH 中的路径。其他功能和 agentlib 相同。 
 
 
19. -javaagent:<jarpath>[=<options>] 
虚拟机启动时装入java语言设备代理。Jarpath文件中的mainfest文件必须有Agent-Class
属性。代理类要实现 public static void premain(String agentArgs, Instrumentation 
inst)方法。当虚拟机初始化时,将按代理类的说明顺序调用 premain方法。 
 
参见:java.lang.instrument 
 
 
 
三、  扩展参数说明 
 
1.  -Xmixed

设置-client 模式虚拟机对使用频率高的方式进行 Just-In-Time 编译和执行,对其他方法
使用解释方式执行。该方式是虚拟机缺省模式。 
 
2.  -Xint 
设置-client模式下运行的虚拟机以解释方式执行类的字节码,不将字节码编译为本机码。 
 
3.  -Xbootclasspath:path 
4.  -Xbootclasspath/a:path 
5.  -Xbootclasspath/p:path 
 
改变虚拟机装载缺省系统运行包 rt.jar 而从-Xbootclasspath 中设定的搜索路径中装载系
统运行类。除非你自己能写一个运行时,否则不会用到该参数。 
 
/a:将在缺省搜索路径后加上 path 中的搜索路径。 
 
/p:在缺省搜索路径前先搜索 path 中的搜索路径。 
 
6.  -Xnoclassgc 
关闭虚拟机对 class 的垃圾回收功能。 
 
 
7.  -Xincgc 
启动增量垃圾收集器,缺省是关闭的。增量垃圾收集器能减少偶然发生的长时间的垃圾回收
造成的暂停时间。但增量垃圾收集器和应用程序并发执行,因此会占用部分 CPU 在应用程序
上的功能。 
 
8.  -Xloggc:<file> 
将虚拟机每次垃圾回收的信息写到日志文件中,文件名由 file 指定,文件格式是平文件,
内容和-verbose:gc 输出内容相同。 
 
 
9.  -Xbatch 
虚拟机的缺省运行方式是在后台编译类代码,然后在前台执行代码,使用-Xbatch参数将关
闭虚拟机后台编译,在前台编译完成后再执行。 
 
 
10. -Xms<size> 
设置虚拟机可用内存堆的初始大小,缺省单位为字节,该大小为 1024 的整数倍并且要大于
1MB,可用 k(K)或m(M)为单位来设置较大的内存数。初始堆大小为 2MB。 
 
例如:-Xms6400K,-Xms256M 
 
 
11. -Xmx<size>

设置虚拟机内存堆的最大可用大小,缺省单位为字节。该值必须为 1024 整数倍,并且要大
于 2MB。可用 k(K)或 m(M)为单位来设置较大的内存数。缺省堆最大值为 64MB。 
 
例如:-Xmx81920K,-Xmx80M 
 
当应用程序申请了大内存运行时虚拟机抛出 java.lang.OutOfMemoryError: Java heap
space 错误,就需要使用-Xmx 设媒洗蟮目捎媚诖娑选?BR> 
 
12. -Xss<size> 
设置线程栈的大小,缺省单位为字节。与-Xmx 类似,也可用 K 或 M 来设置较大的值。通常
操作系统分配给线程栈的缺省大小为 1MB。 
 
另外也可在 java 中创建线程对象时设置栈的大小,构造函数原型为 Thread(ThreadGroup 
group, Runnable target, String name, long stackSize)。 
 
13. -Xprof 
输出 CPU 运行时的诊断信息。 
 
 
14. -Xfuture 
对类文件进行严格格式检查,以保证类代码符合类代码规范。为保持向后兼容,虚拟机缺省
不进行严格的格式检查。 
 
 
15. -Xrs 
减少虚拟机中操作系统的信号(singals)的使用。该参数通常用在虚拟机以后台服务方式
运行时使用(如 Servlet)。 
 
 
16. -Xcheck:jni 
调用 JNI 函数时进行附加的检查,特别地虚拟机将校验传递给 JNI 函数参数的合法性,在本
地代码中遇到非法数据时,虚拟机将报一个致命错误而终止。使用该参数后将造成性能下降

 

 

 

Java中的深拷贝和浅拷贝 

1.浅拷贝与深拷贝概念

(1)浅拷贝(浅克隆)

 浅拷贝又叫浅复制,将对象中的所有字段复制到新的对象(副本)中。其中,值类型字段(java中8中原始类型)的值被复制到副本中后,在副本中的 修改不会影响到源对象对应的值。而引用类型的字段被复制到副本中的还是引用类型的引用,而不是引用的对象,在副本中对引用类型的字段值做修改会影响到源对 象本身。

浅拷贝简单归纳就是只复制一个对象,对象内部存在指向其他对象,数组或引用则不复制。

(2)深拷贝(深克隆)

 将对象中的所有字段复制到新的对象中。不过,无论是对象的值类型字段,还是引用类型字段,都会被重新创建并赋值,对于副本的修改,不会影响到源对象本身。

深拷贝简单归纳就是对象内部引用的对象均复制。

2.Java中的clone()方法

(1)clone()方法将对象复制了一份并返回给调用者。一般而言,clone()方法满足下面规范:

①对任何的对象x,都有x.clone() != x;//克隆对象与原对象不是同一个对象

②对任何的对象x,都有x.clone().getClass()== x.getClass();//克隆对象与原对象的类型一样

③如果对象x的equals()方法定义恰当,那么x.clone().equals(x);应该成立。

(2)Java中对象的克隆

clone()方法是在Object中定义的,而且是protected的,只有实现了Cloneable接口的类才可以在其实例上调用clone()方法,否则会抛出CloneNotSupportException。

为了获取对象的一份拷贝,我们可以利用Object类的clone()方法,也可以实现Cloneable接口,覆盖基类的clone()方法,在clone()方法中,调用super.clone()。

Cloneable接口是一个标记接口,也就是没有任何内容,定义如下:
package java.lang;
pubilc interface Cloneable{
}

 例子代码如下:

[java] view plaincopy

1. class Student implements Cloneable  

2. {  

3.     String name;  

4.     int age;  

5.     Student(String name,int age){  

6.         this.name=name;  

7.         this.age=age;  

8.     }  

9.     public Object clone(){  

10.         Object o=null;  

11.         try{  

12.         //Object中的clone()识别出你要复制的是哪一个对象  

13.                 o=(Student)super.clone();  

14.         }catch(CloneNotSupportedException e){  

15.             System.out.println(e.toString());  

16.         }  

17.         return o;  

18.     }  

19. }  

20.  public static void main(String[] args){  

21.         Student s1=new Student("zhangsan",18);  

22.         Student s2=(Student)s1.clone();  

23.         s2.name="lisi";  

24.         s2.age=20;  

25. System.out.println("name="+s1.name+","+"age="+s1.age);//修改学生2后,不影响学生1的值。  

26.    }  

说明:

①为什么我们在派生类中覆盖Object的clone()方法时,一定要调用super.clone()呢?

在运行时刻,Object中的clone()识别出你要复制的是哪一个对象,然后为此对象分配空间,并进行对象的复制,将原始对象的内容一一复制到新对象的存储空间中。

②继承自java.lang.Object类的clone()方法是浅复制。以下代码可以证明:

[java] view plaincopy

1. //Professor没有实现Cloneable接口,默认使用java.lang.Object类的clone()方法  

2. class Professor{  

3.     String name;  

4.     int age;  

5.     Professor(String name,int age){  

6.         this.name=name;  

7.         this.age=age;  

8.     }  

9. }  

10. //Student实现了Cloneable接口  

11. class Student implements Cloneable{  

12.     String name;//常量对象。  

13.     int age;  

14.     Professor p;  

15.     Student(String name,int age,Professor p){  

16.         this.name=name;  

17.         this.age=age;  

18.         this.p=p;  

19.     }  

20.     public Object clone(){  

21.         Student o=null;  

22.         try{  

23.             o=(Student)super.clone();  

24.         }catch(CloneNotSupportedException e){  

25.             System.out.println(e.toString());  

26.         }  

27.     //使用Object类的clone()方法  

28.         o.p=(Professor)this.p.clone();  

29.         return o;  

30.     }  

31. }  

32. public static void main(String[] args){  

33.       Professor p=new Professor("wangwu",50);  

34.       Student s1=new Student("zhangsan",18,p);  

35.       Student s2=(Student)s1.clone();  

36.       s2.p.name="lisi";  

37.       s2.p.age=30;  

38.    //学生1的教授也变成了lisi,age为30  

39.    System.out.println("name="+s1.p.name+","+"age="+s1.p.age);   

40. }  

那应该如何实现深层次的克隆,即修改s2的教授不会影响s1的教授?代码改进如下:

[java] view plaincopy

1. //Professor类实现了Cloneable接口,不再使用Object默认的clone()方法  

2. class Professor implements Cloneable{  

3.     String name;  

4.     int age;  

5.     Professor(String name,int age){  

6.         this.name=name;  

7.         this.age=age;  

8.     }  

9.     public Object clone(){  

10.         Object o=null;  

11.         try{  

12.             o=super.clone();  

13.         }catch(CloneNotSupportedException e){  

14.             System.out.println(e.toString());  

15.         }  

16.         return o;  

17.     }  

18. }  

19. class Student implements Cloneable{  

20.     String name;  

21.     int age;  

22.     Professor p;  

23.     Student(String name,int age,Professor p){  

24.         this.name=name;  

25.         this.age=age;  

26.         this.p=p;  

27.     }  

28.     public Object clone(){  

29.         Student o=null;  

30.         try{  

31.             o=(Student)super.clone();  

32.         }catch(CloneNotSupportedException e){  

33.             System.out.println(e.toString());  

34.         }  

35.     //调用Professor类的clone()方法实现深拷贝  

36.         o.p=(Professor)this.p.clone();  

37.         return o;  

38.     }  

39. }  

40. public static void main(String[] args){  

41.       Professor p=new Professor("wangwu",50);  

42.       Student s1=new Student("zhangsan",18,p);  

43.       Student s2=(Student)s1.clone();  

44.       s2.p.name="lisi";  

45.      s2.p.age=30;  

46.    //学生1的教授不改变  

47.    System.out.println("name="+s1.p.name+","+"age="+s1.p.age);  

48. }  

 

3.利用串行化来做深复制

把对象写到流里的过程是串行化(Serilization)过程,又叫对象序列化,而把对象从流中读出来的(Deserialization)过程 叫反序列化。应当指出的是,写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面,因此在Java语言里深复制一个对象,常常可以先使对象实现 Serializable接口,然后把对象(实际上只是对象的一个拷贝)写到一个流里,再从流里读出来便可以重建对象。

如下为深复制源代码:

[java] view plaincopy

1. public Object deepClone(){  

2.     //将对象写到流里  

3.     ByteArrayOutoutStream bo=new ByteArrayOutputStream();  

4.     ObjectOutputStream oo=new ObjectOutputStream(bo);  

5.     oo.writeObject(this);  

6.     //从流里读出来  

7. ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray());  

8.     ObjectInputStream oi=new ObjectInputStream(bi);  

9.     return(oi.readObject());  

10. }  

11. 这样做的前提是对象以及对象内部所有引用到的对象都是可串行化的,否则,就需要仔细考察那些不可串行化的对象可否设成transient,从而将之排除在复制过程之外。上例代码改进如下。  

12. class Professor implements Serializable{  

13.     String name;  

14.     int age;  

15.     Professor(String name,int age){  

16.         this.name=name;  

17.         this.age=age;  

18.     }  

19. }  

20. class Student implements Serializable{  

21.     String name;  

22.     int age;  

23.     Professor p;  

24.     Student(String name,int age,Professor p){  

25.         this.name=name;  

26.         this.age=age;  

27.         this.p=p;  

28.     }  

29.     public Object deepClone() throws IOException,OptionalDataException,ClassNotFoundException{  

30.     //将对象写到流里  

31.     ByteArrayOutoutStream bo=new ByteArrayOutputStream();  

32.     ObjectOutputStream oo=new ObjectOutputStream(bo);  

33.     oo.writeObject(this);// object of studnet  

34.  /  /从流里读出来  

35.     ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray());  

36.     ObjectInputStream oi=new ObjectInputStream(bi);  

37.     return(oi.readObject());  

38. }   

39. public static void main(String[] args){  

40.       Professor p=new Professor("wangwu",50);  

41.       Student s1=new Student("zhangsan",18,p);  

42.       Student s2=(Student)s1.deepClone();  

43.       s2.p.name="lisi";  

44.       s2.p.age=30;  

45.    //学生1的教授不改变  

46.    System.out.println("name="+s1.p.name+","+"age="+s1.p.age);   

47. }  

48. }  

 

 

 

java内存结构 

 

一、Java内存分配

1、 Java有几种存储区域?

* 寄存器

     -- 在CPU内部,开发人员不能通过代码来控制寄存器的分配,由编译器来管理

* 栈

     -- 在Windows下, 栈是向低地址扩展的数据结构,是一块连续的内存的区域,即栈顶的地址和栈的最大容量是系统预先规定好的。

     -- 优点:由系统自动分配,速度较快。

     -- 缺点:不够灵活,但程序员是无法控制的。

     -- 存放基本数据类型、开发过程中就创建的对象(而不是运行过程中)

* 堆

     -- 是向高地址扩展的数据结构,是不连续的内存区域

     -- 在堆中,没有堆栈指针,为此也就无法直接从处理器那边获得支持

     -- 堆的好处是有很大的灵活性。如Java编译器不需要知道从堆里需要分配多少存储区域,也不必知道存储的数据在堆里会存活多长时间。

* 静态存储区域与常量存储区域

     -- 静态存储区用来存放static类型的变量

     -- 常量存储区用来存放常量类型(final)类型的值,一般在只读存储器中

* 非RAM存储

     -- 如流对象,是要发送到另外一台机器上的

     -- 持久化的对象,存放在磁盘上

2、 java内存分配

     -- 基础数据类型直接在栈空间分配;

     -- 方法的形式参数,直接在栈空间分配,当方法调用完成后从栈空间回收;

     -- 引用数据类型,需要用new来创建,既在栈空间分配一个地址空间,又在堆空间分配对象的类变量;

     -- 方法的引用参数,在栈空间分配一个地址空间,并指向堆空间的对象区,当方法调用完后从栈空间回收;

     -- 局部变量 new 出来时,在栈空间和堆空间中分配空间,当局部变量生命周期结束后,栈空间立刻被回收,堆空间区域等待GC回收;

     -- 方法调用时传入的 literal 参数,先在栈空间分配,在方法调用完成后从栈空间释放;

     -- 字符串常量在 DATA 区域分配 ,this 在堆空间分配;

     -- 数组既在栈空间分配数组名称, 又在堆空间分配数组实际的大小!

3、Java内存模型

* Java虚拟机将其管辖的内存大致分三个逻辑部分:方法区(Method Area)、Java栈和Java堆。

    -- 方法区是静态分配的,编译器将变量在绑定在某个存储位置上,而且这些绑定不会在运行时改变。

        常数池,源代码中的命名常量、String常量和static 变量保存在方法区。

    -- Java Stack是一个逻辑概念,特点是后进先出。一个栈的空间可能是连续的,也可能是不连续的。

        最典型的Stack应用是方法的调用,Java虚拟机每调用一次方法就创建一个方法帧(frame),退出该方法则对应的  方法帧被弹出(pop)。栈中存储的数据也是运行时确定的?

    -- Java堆分配(heap allocation)意味着以随意的顺序,在运行时进行存储空间分配和收回的内存管理模型。

        堆中存储的数据常常是大小、数量和生命期在编译时无法确定的。Java对象的内存总是在heap中分配。

4、Java内存分配实例解析

     常量池(constant pool)指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。它包括了关于类、方法、接口等中的常量,也包括字符串常量。

     常量池在运行期被JVM装载,并且可以扩充。String的intern()方法就是扩充常量池的一个方法;当一个String实例str调用 intern()方法时,Java查找常量池中是否有相同Unicode的字符串常量,如果有,则返回其引用,如果没有,则在常量池中增加一个 Unicode等于str的字符串并返回它的引用。

     例:

     String s1=new String("kvill");

     String s2=s1.intern();

     System.out.println( s1==s1.intern() );//false

     System.out.println( s1+" "+s2 );// kvill kvill

     System.out.println( s2==s1.intern() );//true

     这个类中事先没有声名”kvill”常量,所以常量池中一开始是没有”kvill”的,当调用s1.intern()后就在常量池中新添加了一 个”kvill”常量,原来的不在常量池中的”kvill”仍然存在。s1==s1.intern()为false说明原来的“kvill”仍然存 在;s2现在为常量池中“kvill”的地址,所以有s2==s1.intern()为true。

 

 

 

String 常量池问题

(1) 字符串常量的"+"号连接,在编译期字符串常量的值就确定下来, 拿"a" + 1来说,编译器优化后在class中就已经是a1。

     String a = "a1";  

     String b = "a" + 1;  

     System.out.println((a == b)); //result = true 

     String a = "atrue";  

     String b = "a" + "true";  

     System.out.println((a == b)); //result = true 

     String a = "a3.4";  

     String b = "a" + 3.4;  

     System.out.println((a == b)); //result = true

(2) 对于含有字符串引用的"+"连接,无法被编译器优化。

     String a = "ab";  

     String bb = "b";  

     String b = "a" + bb;  

     System.out.println((a == b)); //result = false

     由于引用的值在程序编译期是无法确定的,即"a" + bb,只有在运行期来动态分配并将连接后的新地址赋给b。

(3) 对于final修饰的变量,它在编译时被解析为常量值的一个本地拷贝并存储到自己的常量池中或嵌入到它的字节码流中。所以此时的"a" + bb和"a" + "b"效果是一样的。

     String a = "ab";  

     final String bb = "b";  

     String b = "a" + bb;  

     System.out.println((a == b)); //result = true

(4) jvm对于字符串引用bb,它的值在编译期无法确定,只有在程序运行期调用方法后,将方法的返回值和"a"来动态连接并分配地址为b。

     String a = "ab";  

     final String bb = getbb();  

     String b = "a" + bb;  

     System.out.println((a == b)); //result = false  

     private static string getbb() { 

       return "b";  

     }

(5) String 变量采用连接运算符(+)效率低下。

     String s = "a" + "b" + "c"; 就等价于String s = "abc"; 

     String a = "a"; 

     String b = "b"; 

     String c = "c"; 

     String s = a + b + c; 

     这个就不一样了,最终结果等于: 

       Stringbuffer temp = new Stringbuffer(); 

       temp.append(a).append(b).append(c); 

       String s = temp.toString(); 

(6) Integer、Double等包装类和String有着同样的特性:不变类。 

     String str = "abc"的内部工作机制很有代表性,以Boolean为例,说明同样的问题。 

     不变类的属性一般定义为final,一旦构造完毕就不能再改变了。 

     Boolean对象只有有限的两种状态:true和false,将这两个Boolean对象定义为命名常量: 

     public static final Boolean TRUE = new Boolean(true); 

     public static final Boolean FALSE = new Boolean(false); 

     这两个命名常量和字符串常量一样,在常数池中分配空间。 Boolean.TRUE是一个引用,Boolean.FALSE是一个引用,而"abc"也是一个引用!由于Boolean.TRUE是类变量 (static)将静态地分配内存,所以需要很多Boolean对象时,并不需要用new表达式创建各个实例,完全可以共享这两个静态变量。其JDK中源 代码是: 

     public static Boolean valueOf(boolean b) { 

       return (b ? TRUE : FALSE); 

     } 

     基本数据(Primitive)类型的自动装箱(autoboxing)、拆箱(unboxing)是JSE 5.0提供的新功能。 Boolean b1 = 5>3; 等价于Boolean b1 = Boolean.valueOf(5>3); //优于Boolean b1 = new Boolean (5>3); 

    static void foo(){ 

        boolean isTrue = 5>3;  //基本类型 

        Boolean b1 = Boolean.TRUE; //静态变量创建的对象 

        Boolean b2 = Boolean.valueOf(isTrue);//静态工厂 

        Boolean b3 = 5>3;//自动装箱(autoboxing) 

        System.out.println("b1 == b2 ?" +(b1 == b2)); 

        System.out.println("b1 == b3 ?" +(b1 == b3)); 

        Boolean b4 = new Boolean(isTrue);不宜使用 

        System.out.println("b1 == b4 ?" +(b1 == b4));//浪费内存、有创建实例的时间开销 

    } //这里b1、b2、b3指向同一个Boolean对象。

(7) 如果问你:String x ="abc";创建了几个对象? 

     准确的答案是:0或者1个。如果存在"abc",则变量x持有"abc"这个引用,而不创建任何对象。 

     如果问你:String str1 = new String("abc"); 创建了几个对象? 

     准确的答案是:1或者2个。(至少1个在heap中)

(8) 对于int a = 3; int b = 3;

     编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找有没有字面值为3的地址,没找到,就开辟一个存放3这个字面值的地址,然后将a指向3的地址。接着处 理int b = 3;在创建完b的引用变量后,由于在栈中已经有3这个字面值,便将b直接指向3的地址。这样,就出现了a与b同时均指向3的情况。

5、堆(Heap)和非堆(Non-heap)内存

     按照官方的说法:“Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。堆是在 Java 虚拟机启动时创建的。”

     可以看出JVM主要管理两种类型的内存:堆和非堆。

     简单来说堆就是Java代码可及的内存,是留给开发人员使用的;

     非堆就是JVM留给自己用的,所以方法区、JVM内部处理或优化所需的内存(如JIT编译后的代码缓存)、每个类结构(如运行时常数池、字段和方法数据)以及方法和构造方法的代码都在非堆内存中。 

堆内存分配

     JVM初始分配的内存由-Xms指定,默认是物理内存的1/64;

     JVM最大分配的内存由-Xmx指定,默认是物理内存的1/4。

     默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制。

     因此服务器一般设置-Xms、-Xmx相等以避免在每次GC 后调整堆的大小。 

非堆内存分配

     JVM使用-XX:PermSize设置非堆内存初始值,默认是物理内存的1/64;

     由XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的1/4。 

例子

     -Xms256m

     -Xmx1024m

     -XX:PermSize=128M

     -XX:MaxPermSize=256M

 

二、Java垃圾回收

1. JVM运行环境中垃圾对象的定义

     一个对象创建后被放置在JVM的堆内存中,当永远不再引用这个对象时,它将被JVM在堆内存中回收。或  当对象在JVM运行空间中无法通过根集合(root set)到达时,这个对象就被称为垃圾对象。

2. 堆内存

* 在JVM启动时被创建;堆内存中所存储的对象可以被JVM自动回收,不能通过其他外部手段回收

* 堆内存可分为两个区域:新对象区和老对象区

    -- 新对象区可分为三个小区:Eden区、From区、To区

    Eden区用来保存新创建的对象,当Eden区中的对象满了之后,JVM将会做可达性测试,检测有哪些对象由根集合出发是不可达的,不可达的对象就会被 JVM回收,并将所有的活动对象从Eden区拷到To区,此时一些对象将发生状态交换,有的对象就从To区被转移到From区。

3. JVM中对象的生命周期

 * 创建阶段(步骤)

    -- 为对象分配存储空间

    -- 开始构造对象

    -- 递归调用其超类的构造方法

    -- 进行对象实例初始化与变量初始化

    -- 执行构造方法体

 * 应用阶段

    -- 特征:系统至少维护着对象的一个强引用;所有对该对象引用强引用(除非显示声明为其它引用)

    -- 强引用

      指JVM内存管理器从根引用集合出发,遍寻堆中所有到达对象的路径。当到达某对象的任意路径都不含有引用对象时,对这个对象的引用就被称为强引用。

当内存不足时,JVM宁愿抛出OutOfMemeryError错误使程序停止,也不会靠收回具有强引用的对象来释放内存空间

    -- 软引用

      它能实现cache功能,防止最大限度的使用内存时引起的OutOfMemory异常,在内存不够用的时候jvm会自动回收Soft Reference。

软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,java虚拟机就会把这个软引用加入到与之关联的引用队列中。

      Java中提供软引用的包:java.lang.ref.SoftReference(后续详解)

     软引用

      实现cache功能,防止最大限度的使用内存时引起的OutOfMemory异常,在内存不够用的时候jvm会自动回收Soft Reference.软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,Java虚拟机就会把这个 软引用加入到与之关联的引用队列中。

Java代码 

import java.lang.ref.SoftReference  

//实现cache功能,最大限度利用内存  

Test test = new Test();  

SoftReference sr = new SoftRefence(test);  

test = null;  

if(sr.get() != null){  

     test = sr.get();  

}else{  

     test = new Test();  

     sr = new SoftReference(test);  

     test = null;  

}  

Java代码 

//创建一个强引用  

String str = new String("hello");   

//创建引用队列, <String>为范型标记,表明队列中存放String对象的引用  

ReferenceQueue<String> rq = new ReferenceQueue<String>();   

//创建一个弱引用,它引用"hello"对象,并且与rq引用队列关联  

//<String>为范型标记,表明WeakReference会弱引用String对象  

SoftReference<String> wf = new SoftReference<String>(str, rq);  

str=null; //取消"hello"对象的强引用  

String str1=wf.get(); //假如"hello"对象没有被回收,str1引用"hello"对象  

//假如"hello"对象没有被回收,rq.poll()返回null  

Reference<? extends String> ref=rq.poll();   

    -- 弱引用

      只具有弱引用的对象有更短的生命周期,无论内存是否紧张,被垃圾回收器发现立即回收。弱引用可以和一个引用队列(ReferenceQueue)联合使用。

      可分为长弱引用和短弱引用,长弱引用在对象的Finalize方法被GC调用后依然追踪对象

      Java中提供弱引用的包:java.lang.ref.WeakReference

    -- 虚引用

      虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。虚引用主要用来跟踪对象被垃圾回收的活动。

      Phantom对象指一些执行完了finalize函数,并且为不可达对象,但是还没被GC回收的对象。这种对象可以辅助finalize进行一些后期的回收工作。

 * 不可视阶段

    -- 如果一个对象已使用完,并且在其可视区域不再使用,应该主动将其设置为null,即obj=null;这样可以帮助JVM及时地发现这个垃圾对象,并且可以及时地挥手该对象所占用的系统资源。

Java代码 

package reference;  

/*   

WeakHashMap, 在这种Map中存放了键对象的弱引用,当一个键对象被垃圾回收,那么相应的值对象的引用会从Map中删除。WeakHashMap能够节约存储空间,可用 来缓存那些非必须存在的数据。   

*/  

import java.util.*;  

import java.lang.ref.*;  

 

class Key {  

 String id;  

 public Key(String id) {  

  this.id = id;  

 }  

 public String toString() {  

  return id;  

 }  

 

 public int hashCode() {  

  return id.hashCode();  

 }  

 

 public boolean equals(Object r) {  

  return (r instanceof Key) && id.equals(((Key) r).id);  

 }  

 

 public void finalize() {  

  System.out.println("Finalizing Key " + id);  

 }  

}  

 

class Value {  

 String id;  

 

 public Value(String id) {  

  this.id = id;  

 }  

 

 public String toString() {  

  return id;  

 }  

 

 public void finalize() {  

  System.out.println("Finalizing Value " + id);  

 }  

}  

 

public class MapCache {  

 public static void main(String[] args) throws Exception {  

  int size = 1000;  

  // 或者从命令行获得size的大小  

  if (args.length > 0)  

   size = Integer.parseInt(args[0]);  

 

  Key[] keys = new Key[size]; // 存放键对象的强引用  

  WeakHashMap<Key, Value> whm = new WeakHashMap<Key, Value>();  

  for (int i = 0; i < size; i++) {  

   Key k = new Key(Integer.toString(i));  

   Value v = new Value(Integer.toString(i));  

   if (i % 3 == 0)  

    keys[i] = k; // 使Key对象持有强引用  

   whm.put(k, v); // 使Key对象持有弱引用  

  }  

  // 催促垃圾回收器工作  

  System.gc();  

 

  // 把CPU让给垃圾回收器线程  

  Thread.sleep(8000);  

 }  

}  

 

4. Java中的析构方法finalize

    finalize()方法常称之为终止器

          protected void finalize(){

              // finalization code here

         }

    对象即将被销毁时,有时需要做一些善后工作。可以把这些操作写在finalize()方法里。

    Java终止器却是在对象被销毁时调用。一旦垃圾收集器准备好释放无用对象占用的存储空间,它首先调用那些对象的finalize()方法,然后才真正 回收对象的内存。而被丢弃的对象何时被销毁,应用是无法获知的。大多数场合,被丢弃对象在应用终止后仍未销毁。到程序结束的时候,并非所有收尾模块都会得 到调用。

5. 应用能干预垃圾回收吗?

    在应用代码里控制JVM的垃圾回收运作是不可能的事。

    对垃圾回收有两个途径。第一个就是将指向某对象的所有引用变量全部移走。这就相当于向JVM发了一个消息:这个对象不要了。第二个是调用库方法 System.gc()。第一个是一个告知,而调用System.gc()也仅仅是一个请求。JVM接受这个消息后,并不是立即做垃圾回收,而只是对几个 垃圾回收算法做了加权,使垃圾回收操作容易发生,或提早发生,或回收较多而已。

    希望JVM及时回收垃圾,是一种需求。其实,还有相反的一种需要:在某段时间内最好不要回收垃圾。要求运行速度最快的实时系统,特别是嵌入式系统,往往希望如此。

    Java的垃圾回收机制是为所有Java应用进程服务的,而不是为某个特定的进程服务的。因此,任何一个进程都不能命令垃圾回收机制做什么、怎么做或做多少。

6. 垃圾回收算法

* 引用计数

    该算法在java虚拟机没被使用过,主要是循环引用问题,因为计数并不记录谁指向他,无法发现这些交互自引用对象。

    -- 怎么计数?

        当引用连接到对象时,对象计数加1

        当引用离开作用域或被置为null时减1

    -- 怎么回收?

        遍历对象列表,计数为0就释放

    -- 有什么问题?

        循环引用问题。

* 标记算法

    标记算法的思想是从堆栈和静态存储区的对象开始,遍历所有引用,标记活得对象。

    对于标记后有两种处理方式:

  (1) 停止-复制

    -- 所谓停止,就是停止在运行的程序,进行垃圾回收

    -- 所谓复制,就是将活得对象复制到另外一个堆上,以使内存更紧凑

    -- 优点在于,当大块内存释放时,有利于整个内存的重分配

    -- 有什么问题?

        一、停止,干扰程序的正常运行,二,复制,明显耗费大量时间,三,如果程序比较稳定,垃圾比较少,那么每次重新复制量是非常大的,非常不合算

    -- 什么时候启动停止-复制?

        内存数量较低时,具体多低我也不知道

  (2) 清除 也称标记-清除算法

    -- 也就是将标记为非活得对象释放,也必须暂停程序运行

    -- 优点就是在程序比较稳定,垃圾比较少的时候,速度比较快

    -- 有什么问题?

       很显然停止程序运行是一个问题,只清除也会造成很对内存碎片。

    -- 为什么这2个算法都要暂停程序运行?

       这是因为,如果不暂停,刚才的标记会被运行的程序弄乱

 

 

java正则表达式学习总结,以及和javascript正则表达式的区别 

用正则表达式处理字符串功能非常强大,下面总结一下java正则表达式的一些知识:

 

基本元字符

. 任何字符(与行结束符可能匹配也可能不匹配) 

 

// 反斜杠
/t 间隔 ('/u0009')
/n 换行 ('/u000A')
/r 回车 ('/u000D')
/d 数字 等价于[0-9]
/D 非数字 等价于[^0-9]
/s 空白符号 [/t/n/x0B/f/r]
/S 非空白符号 [^/t/n/x0B/f/r]
/w 单独字符 [a-zA-Z_0-9]
/W 非单独字符 [^a-zA-Z_0-9]
/f 换页符
/e Escape
/b 一个单词的边界
/B 一个非单词的边界
/G 前一个匹配的结束

^为限制开头
$为限制结尾

 

加入特定限制条件[]
[a-z]     条件限制在小写a to z范围中一个字符
[A-Z]     条件限制在大写A to Z范围中一个字符
[a-zA-Z] 条件限制在小写a to z或大写A to Z范围中一个字符
[0-9]     条件限制在小写0 to 9范围中一个字符
[0-9a-z] 条件限制在小写0 to 9或a to z范围中一个字符
[0-9[a-z]] 条件限制在小写0 to 9或a to z范围中一个字符(交集)

[]中加入^后加再次限制条件[^]
[^a-z]     条件限制在非小写a to z范围中一个字符
[^A-Z]     条件限制在非大写A to Z范围中一个字符
[^a-zA-Z] 条件限制在非小写a to z或大写A to Z范围中一个字符
[^0-9]     条件限制在非小写0 to 9范围中一个字符
[^0-9a-z] 条件限制在非小写0 to 9或a to z范围中一个字符
[^0-9[a-z]] 条件限制在非小写0 to 9或a to z范围中一个字符(交集)

 

 

表示匹配次数的符号

 

{n,}:至少n次

 

“或”符号

可以使用“|”操作符。“|”操作符的基本意义就是“或”运算。要匹配“toon”,使用“t(a|e|i|o|oo)n”正则表达式。这里不能使用方扩号,因为方括号只允许匹配单个字符;这里必须使用圆括号“()”。圆括号还可以用来分组。

 

捕获组

 

>捕获组可以通过从左到右计算其开括号来编号。例如,在表达式 ((A)(B(C))) 中,存在四个这样的组: 

1    

((A)(B(C)))

2    

/A

3    

(B(C))

4    

(C)

 

以 (?) 开头的组是纯的非捕获 组,它不捕获文本,也不针对组合计进行计数。

 

匹配模式

java正则表达式默认使用的是最长匹配模式,即:如果用ro*去匹配room,并将其替换为r,则结果为rm。

如果某些情况下想使用最短匹配,即:如果用ro?去匹配room,并将其替换为b,则结果为boom。

总结:默认使用最长匹配,但是结尾为"?"的模式使用的是最短匹配。

 

基本用法

Pattern pattern = Pattern.compile(正则表达式模式串);
Matcher matcher = pattern.matcher(要验证或处理的源字符串);

matcher.matches();如果匹配返回true,否则,false.

 

 

JavaScript正则表达式和java正则表达式的区别

javascript中的正则表达式和java的正则表达式基本上是相同的,区别在于分组引用和对象,方法

具体区别:

1).javascript正则表达式创建有两种方法:

a.显式创建:

var re = new RegExp("正则表达式模式串");

re.test(要校验或处理的源字符串);

 

b.隐式创建:

var re = /正则表达式模式串/;

要校验或处理的源字符串.match(re);

 

2).分组捕获对象引用方式不同

javascript也是使用"()"进行分组,但是捕获对象用RegExp对象的$1到$99来引用捕获对象。

 

 

附录:常用的javascript正则表达式,java的也类似

ip地址校验正则表达式(IPv4):

/^(/d{1,2}|1/d/d|2[0-4]/d|25[0-5])(/.(/d{1,2}|1/d/d|2[0-4]/d|25[0-5])){3}$/

 

Email校验正则表达式:

/^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(/.[a-zA-Z0-9_-]+)+$/

 

格式为:2010-10-08类型的日期格式校验正则表达式:

/^/d{4}-(0?[1-9]|1[0-2])-(0?[1-9]|[1-2]/d|3[0-1])$/

 

格式为:23:11:34类型的时间格式校验正则表达式:

/^([0-1]/d|2[0-3]):[0-5]/d:[0-5]/d$/

 

 

防范JAVA内存泄漏解决方案 

Java是如何管理内存

为了判断Java中是否有内存泄露,我们首先必须了解Java是如何管理内存的。Java的内存管理就是对象的分配和释放问题。在Java中,程序 员需要通过关键字new为每个对象申请内存空间 (基本类型除外),所有的对象都在堆 (Heap)中分配空间。另外,对象的释放是由GC决定和执行的。在Java中,内存的分配是由程序完成的,而内存的释放是有GC完成的,这种收支两条线 的方法确实简化了程序员的工作。但同时,它也加重了JVM的工作。这也是Java程序运行速度较慢的原因之一。因为,GC为了能够正确释放对象,GC必须 监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等,GC都需要进行监控。

监视对象状态是为了更加准确地、及时地释放对象,而释放对象的根本原则就是该对象不再被引用。

为了更好理解GC的工作原理,我们可以将对象考虑为有向图的顶点,将引用关系考虑为图的有向边,有向边从引用者指向被引对象。另外,每个线程对象可 以作为一个图的起始顶点,例如大多程序从main进程开始执行,那么该图就是以main进程顶点开始的一棵根树。在这个有向图中,根顶点可达的对象都是有 效对象,GC将不回收这些对象。如果某个对象 (连通子图)与这个根顶点不可达(注意,该图为有向图),那么我们认为这个(这些)对象不再被引用,可以被GC回收。

以下,我们举一个例子说明如何用有向图表示内存管理。对于程序的每一个时刻,我们都有一个有向图表示JVM的内存分配情况。以下右图,就是左边程序运行到第6行的示意图。


 

Java使用有向图的方式进行内存管理,可以消除引用循环的问题,例如有三个对象,相互引用,只要它们和根进程不可达的,那么GC也是可以回收它们 的。这种方式的优点是管理内存的精度很高,但是效率较低。另外一种常用的内存管理技术是使用计数器,例如COM模型采用计数器方式管理构件,它与有向图相 比,精度行低(很难处理循环引用的问题),但执行效率很高。

 

什么是Java中的内存泄露

下面,我们就可以描述什么是内存泄漏。在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点,首先,这些对象是可达的,即在有 向图中,存在通路可以与其相连;其次,这些对象是无用的,即程序以后不会再使用这些对象。如果对象满足这两个条件,这些对象就可以判定为Java中的内存 泄漏,这些对象不会被GC所回收,然而它却占用内存。

在C++中,内存泄漏的范围更大一些。有些对象被分配了内存空间,然后却不可达,由于C++中没有GC,这些内存将永远收不回来。在Java中,这些不可达的对象都由GC负责回收,因此程序员不需要考虑这部分的内存泄露。

通过分析,我们得知,对于C++,程序员需要自己管理边和顶点,而对于Java程序员只需要管理边就可以了(不需要管理顶点的释放)。通过这种方式,Java提高了编程的效率。


 

因此,通过以上分析,我们知道在Java中也有内存泄漏,但范围比C++要小一些。因为Java从语言上保证,任何对象都是可达的,所有的不可达对象都由GC管理。

对于程序员来说,GC基本是透明的,不可见的。虽然,我们只有几个函数可以访问GC,例如运行GC的函数System.gc(),但是根据Java 语言规范定义, 该函数不保证JVM的垃圾收集器一定会执行。因为,不同的JVM实现者可能使用不同的算法管理GC。通常,GC的线程的优先级别较低。JVM调用GC的策 略也有很多种,有的是内存使用到达一定程度时,GC才开始工作,也有定时执行的,有的是平缓执行GC,有的是中断式执行GC。但通常来说,我们不需要关心 这些。除非在一些特定的场合,GC的执行影响应用程序的性能,例如对于基于Web的实时系统,如网络游戏等,用户不希望GC突然中断应用程序执行而进行垃 圾回收,那么我们需要调整GC的参数,让GC能够通过平缓的方式释放内存,例如将垃圾回收分解为一系列的小步骤执行,Sun提供的HotSpot JVM就支持这一特性。

下面给出了一个简单的内存泄露的例子。在这个例子中,我们循环申请Object对象,并将所申请的对象放入一个Vector中,如果我们仅仅释放引 用本身,那么Vector仍然引用该对象,所以这个对象对GC来说是不可回收的。因此,如果对象加入到Vector后,还必须从Vector中删除,最简 单的方法就是将Vector对象设置为null。

Vector v=new Vector(10);

for (int i=1;i<100; i++)

{

Object o=new Object();

v.add(o);

o=null;

}

 

//此时,所有的Object对象都没有被释放,因为变量v引用这些对象。

 

几种典型的内存泄漏 
    我们知道了在Java中确实会存在内存泄漏,那么就让我们看一看几种典型的泄漏,并找出他们发生的原因和解决方法。 

    (1) 全局集合 
    在大型应用程序中存在各种各样的全局数据仓库是很普遍的,比如一个JNDI-tree或者一个session table。在这些情况下,必须注意管理储存库的大小。必须有某种机制从储存库中移除不再需要的数据。 

    通常有很多不同的解决形式,其中最常用的是一种周期运行的清除作业。这个作业会验证仓库中的数据然后清除一切不需要的数据。 

    另一种管理储存库的方法是使用反向链接(referrer)计数。然后集合负责统计集合中每个入口的反向链接的数目。这要求反向链接告诉集合何时会退出入口。当反向链接数目为零时,该元素就可以从集合中移除了。 
   
    (2) 缓存 
    缓存一种用来快速查找已经执行过的操作结果的数据结构。因此,如果一个操作执行需要比较多的资源并会多次被使用,通常做法是把常用的输入数据的操作结果进 行缓存,以便在下次调用该操作时使用缓存的数据。缓存通常都是以动态方式实现的,如果缓存设置不正确而大量使用缓存的话则会出现内存溢出的后果,因此需要 将所使用的内存容量与检索数据的速度加以平衡。 

    常用的解决途径是使用java.lang.ref.SoftReference类坚持将对象放入缓存。这个方法可以保证当虚拟机用完内存或者需要更多堆的时候,可以释放这些对象的引用。 

    (3) 类装载器 
    Java类装载器的使用为内存泄漏提供了许多可乘之机。一般来说类装载器都具有复杂结构,因为类装载器不仅仅是只与"常规"对象引用有关,同时也和对象内 部的引用有关。比如数据变量,方法和各种类。这意味着只要存在对数据变量,方法,各种类和对象的类装载器,那么类装载器将驻留在JVM中。既然类装载器可 以同很多的类关联,同时也可以和静态数据变量关联,那么相当多的内存就可能发生泄漏。 

 

如何检测内存泄漏

最后一个重要的问题,就是如何检测Java的内存泄漏。目前,我们通常使用一些工具来检查Java程序的内存泄漏问题。市场上已有几种专业检查 Java内存泄漏的工具,它们的基本工作原理大同小异,都是通过监测Java程序运行时,所有对象的申请、释放等动作,将内存管理的所有信息进行统计、分 析、可视化。开发人员将根据这些信息判断程序是否有内存泄漏问题。这些工具包括Optimizeit Profiler,JProbe Profiler,JinSight , Rational 公司的Purify等。

下面,我们将简单介绍Optimizeit的基本功能和工作原理。

Optimizeit Profiler版本4.11支持Application,Applet,Servlet和Romote Application四类应用,并且可以支持大多数类型的JVM,包括SUN JDK系列,IBM的JDK系列,和Jbuilder的JVM等。并且,该软件是由Java编写,因此它支持多种操作系统。Optimizeit系列还包 括Thread Debugger和Code Coverage两个工具,分别用于监测运行时的线程状态和代码覆盖面。

当设置好所有的参数了,我们就可以在OptimizeIt环境下运行被测程序,在程序运行过程中,Optimizeit可以监视内存的使用曲线(如 下图),包括JVM申请的堆(heap)的大小,和实际使用的内存大小。另外,在运行过程中,我们可以随时暂停程序的运行,甚至强行调用GC,让GC进行 内存回收。通过内存使用曲线,我们可以整体了解程序使用内存的情况。这种监测对于长期运行的应用程序非常有必要,也很容易发现内存泄露。


 

在运行过程中,我们还可以从不同视角观查内存的使用情况,Optimizeit提供了四种方式:

· 堆视角。 这是一个全面的视角,我们可以了解堆中的所有的对象信息(数量和种类),并进行统计、排序,过滤。了解相关对象的变化情况。

· 方法视角。通过方法视角,我们可以得知每一种类的对象,都分配在哪些方法中,以及它们的数量。

· 对象视角。给定一个对象,通过对象视角,我们可以显示它的所有出引用和入引用对象,我们可以了解这个对象的所有引用关系。

· 引用图。 给定一个根,通过引用图,我们可以显示从该顶点出发的所有出引用。

在运行过程中,我们可以随时观察内存的使用情况,通过这种方式,我们可以很快找到那些长期不被释放,并且不再使用的对象。我们通过检查这些对象的生 存周期,确认其是否为内存泄露。在实践当中,寻找内存泄露是一件非常麻烦的事情,它需要程序员对整个程序的代码比较清楚,并且需要丰富的调试经验,但是这 个过程对于很多关键的Java程序都是十分重要的。

综上所述,Java也存在内存泄露问题,其原因主要是一些对象虽然不再被使用,但它们仍然被引用。为了解决这些问题,我们可以通过软件工具来检查内存泄露,检查的主要原理就是暴露出所有堆中的对象,让程序员寻找那些无用但仍被引用的对象。


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值