thinking in java

来源:互联网 发布:淘宝网 阿里巴巴 编辑:程序博客网 时间:2024/06/18 04:14

第一章

1.Java一切都是对象,但操纵的标识符是对象的一个“引用”。简单的说,引用代表这个对象内存地址。
如:String s;实际上,我们并没有创建一个对象,我们只是创建了一个引用,它不指向任何对象。
2.Java对象是存储到什么地方的呢?
a)寄存器:位于计算机CPU内部,我们不能直接通过JAVA语言来分配,由虚拟机处理。
b)堆栈:位于RAM,通过堆栈指针从处理器哪里获得支持,堆栈指针向上移动分配内存,向上移动,释放内存,他的速速仅仅次于寄存器。存储时候,JAVA必须知道存储数据的确切生命周期,它一般保存基本数据类型和对象的引用。
c)堆:也在RAM中,用来存放JAVA的对象。我们不知道它确定的生命周期,内存释放由JAVA虚拟机的GC来确定。
d)常量存储:通常直接存放在程序代码内部,有时候会分开,所以我们可以放在ROM中。
e)非RAM存储:完全存活在程序之外,我们可以将一个对象保存在硬盘中,或者通过数据流传送。
3.基本类型不用new来创建,它创建的并不是引用,他的值直接存储在堆栈中。
int a,int b;
a = b;
a = 1;//b的值并不会改变,这是因为a直接指向堆栈中的实际值
Test a,b;
a = b;
a.value = 1;//b对象中的value是会变成1的,因为a,b指向同一个引用,a引用修改内存地址所在的值,就是b引用对应内存地址所在的对象。所以b的值会改变。
4.当创建一个对象数组时,实际上就是创建了一个引用数组,并且每个数组都会初始化成null,如果数组是基本类型,那么这个数组所占用d内存会全部置为0。
5.在Java当中,一个作用域内(花括号)不能定义同一个对象引用名。
如:{
int a;
{
int a;//这样是不允许的,但是我们可以在一个类中定义一个变量a,在方法中定义一个变量a。
}
}
6.引用存活在作用域中,而对象就不一定了。
如:{
String s = new String(“abc”);
}//这个作用域结束后,引用就消失了,但是其关联的对象还是存在的。
7.在一个类中,如果一个基本类型数据是全局变量,那么回自动初始化值,如果是局部变量(这里的局部变量并不是单单指方法中定义的变量,在类中只要他的引用范围不是全局的,都会强制,这种情况比如在类中写一个花括号,里面定义值),就会要求你强制初始化。
8.Java方法传递的对象实际上是引用,当然,如果是基本类型,那么我们会复制传值。
9.当一个事物申明他是一个static的时候,这就意味着这个域或者这个方法不会与包含它的那个类的任何对象关联在一起,所以即使你没有创建一个类的对象,也能够引用static域和方法。即使你创建了两个对象,static变量依旧只有一块内存区域。我们也可以通过一个对象去引用static 变量。
10.java会在每个类中自动引入java.lang包。
11.在控制台中,javac class.java编译java文件,java className执行这个编译后的.class文件。
12.我们通过javadoc来编写嵌入式文档它以/**注释头开始通过@引用某些命令,具体请参考83页语法规则。与此同时,我们可以在eclipse中通过export导出html文档:右键项目 -> Export -> Java -> JavaDoc ->

第二章 操作符

1.equal默认是比较引用,所以如果你没有重写的话,他是不会跟string那样,自动帮你比较具体值的。但java基本类型的包装对象,基本上都重写了这个方法。
2.&&和||逻辑运算符会短路,也就是说,只要前面的条件满足了,就不会执行后面的语句。
而&和|是不短路的,他会执行完所有的判断逻辑。
4.如果一个float类型没有加上f后缀,那么编译器就会错误,让我们强制转化为double.
如:float a = 10.00;//这是会报错的,他会叫我们转化成double,
所以我们要这样写:float a = 10.00f;
5.对于扩展转换(如把int转化成float),他是不会要求强制转换的,但是有种情况(专业术语叫提升)转换后会要求强制转换:
char a,b,c;
a = (char)(b+c);//要注意,我们在做低于int类型的+ -运算时,会自动扩展为int类型,然后再运算。所以在这里,我们是先把a,b转换成int类型做加法运算,然后强转成char类型。
6.在for循环中,我们可以定义多个同样类型的变量。
for(int a,b;a<10;a++)//记住我们只能写int a,b不能写int a,int b
7.我们可以通过break和continue在循环中跳转。Break是跳转到指定标签后,不执行这个循环的。我们仅仅会在多重循环跳转中用到。
A://执行跳转的标签A(类似于goto标签),我们不能再循环前写其他的语句
While(true){
B:
While(true){
Break A://跳转到A点,我们会终止这两个循环
break B;//跳转到B点,我们终止内部循环
continue A;//跳转到A点,我们继续从外部开始循环
continue B;//跳转到B点,我们继续从内部开始循环。
}
}
8.switch语句中的条件只针对int类型和char类型。

第四章

1.重载的方法可以是参数类型,个数以及顺序不同,但不能是返回值不同,参数完全一样。因为这样的话,编译器就不能确定是哪个方法了。
2.如果一个方法传入的参数与实际的参数不同,会自动发生类型转换,例如int方法参数传入char,会自动转化成char。但如果转向精度更低的,则需要自动转型。
3.如果你创建了一个构造器,那么系统就不会帮你创建默认的构造器。
4.如果一个方法不是静态的(static方法跟对象不关联),那么在调用这个方法的时候,系统会隐式的把这个对象的指针传进去。这样该方法就能调用本类的内容了。
5.我们只能在构造方法中通过this调用其它的构造方法,而且必须要放在构造方法前面,并且只能调用一个,除了构造方法之外,我们不能在一个类中其他地方调用构造方法。
6.finalize在执行gc操作的之前会被调用,而且只会调用一次,所以我们不能完全依赖finalize能够被执行。Finalize一般用来清理内存,但new的内存是被GC清理,它通常用来native方法清理。
7.堆分配内存的方式其实使能够提升对象的创建的,因为我们会把一个堆的空间整理好,通过传送带的方式创建对象,C++则不一样,他的内存是散乱的。所以堆分配内存实际上要快一些。(如果不明白这句话,可以翻阅90页最开始的一段)。
8.我们需要理解以下几个JVM内存清理的算法:引用计数,停止—复制算法,标记—清扫。
9.实际上,我们的数组在传递参数的时候,也是可以当成可变参数(也就是不知道具体传了多少个同样类型的值,如string …)传递的(系统会自动对这个数组发生转型)。
10.当你创建一个enum的时候,系统会帮你自动添加一些有用的特性,如ordinal方法(用来获取顺序),toString方法,static value方法(这个方法用来获取值),事实上,你可以把enum当成一个类,他也确实是一个类,我们在使用switch的时候,要多用switch。

第六章

1.每个java文件是一个编译单元,每个Java文件最多只能有一个pulic类(注意是最多,也就是说可以没有),而且public类名字必须根文件名一样。其他的非Public方法是包访问权限(这就意味着在同一个包里,只能有一个这样的类,纵使这个类是在其他的java文件中),访问的时候我们要加上对应文件名的前缀(如A文件中有一个A类,非public的 B类,那么访问的时候要加上A.B),编译过后,每个类都会有一个独立的.class文件。
2.protected修饰符是包访问权限,这就意味着一个类中的方法和变量以及继承父类过来的方法和变量在同一个包中是可以访问的。
3.在一个类中,变量和方法申明应该采用这样的顺序:public->protected->包访问权限->private。这是为了方便别的程序员能够更加快速的看到自己想要的东西(人们往往只关注自己能够调用访问的变量或者方法)。
3.如果不是内部类,那么我们是不能用Protected和private修饰一个类的。

第七章

1.一个类就算不是Public,通过java命令也是可以调用它的main方法的。
2.当创建一个导出类的对象时,该对象包含了一个基类的对象,这个对象跟你使用基类直接创建的对象。如果一个类的基类只有默认方法或者无参的方法,那么在初始化的时候,会自动调用。否则我们就要通过super显示的调用基类的方法。
3.代理就是一个类通过另一个类中的某些方法,调用类中的方法。
Class A{ class B{
Void a(){} A a = new A();
Void b(){} a(){ a.a();}
Void c(){}; b(){a.b();}
} …..}
4.在清理一个类到时候,我们应该跟初始化相反,也就是先清理子类,再清理父类,因为子类可能会依赖父类某些数据,如果先清理了,就可能会崩溃。
5.我们可以写一个和基类返回类型(基类的导出类跟基类型也算是相同返回类型,我们称之为协变类型)以及参数相同的方法来覆盖这个方法,并通过override注解来验证,如果没有覆盖是重载或者申明新的方法那么就会报错。在覆盖一个方法的时候,你只能扩大这个方法的访问范围,不能缩小一个方法的范围(如一个基类的方法是public的,那么你不能用private方法去修饰,只要参数和返回值一样,系统是不会认为你这是再重载的,所以一旦返回类型和参数一致,就必要要遵循访问修饰符只能扩大的原则)
6.如果方法调用的时候参数是一个基类,在传入该子类的对象,这个方法调用的是这个子类的方法和数据。
7.请记住这条设计规则,如果我们需要向上转型,那么我们可以使用继承,否则的话,我们应该使用组合。
8.static final修饰的基本类型实在编译的时候就确定的,而修饰的对象就不是了。修饰的基本类型值是永远不会改变的,修饰的对象他的引用地址永远不会变,但这个对象的内容是可以改变的。如果我们使用static final修饰一个对象的引用,我们不实在编译的时候运行的,我们还是在第一次使用这个类的时候运行的(如果不理解这段话,可以看141页)。
9.final修饰的变量是可以延迟到类构造函数初始化的,但static final就必须要在申明的时候初始化了。(想想为什么,其实是static方法是跟对象相关的,如果不在申明的时候就初始化,那就不能初始化了)
10.final方法修饰一个类中的对象,子类是不能覆盖这个类的,类中的private方法其实是隐式的包含了fianl的(想想为什么,因为private方法是不能改变的)。如果父类申明了一个Private方法,子类是不能重写覆盖这个方法的,因为他的作用域只是这个类中。我们可以把这个理论延伸开来。
11.如果一个类申明为final方法,那么我们不能继承这个类。这样的设计意味着我们不想要别人修改这个类。

第八章

1.前期绑定和动态绑定的理解(如果不记得了,可以看书的151页)。
2.在父类的构造函数中调用一个方法A,然而这个方法又被其子类覆盖重写了,当我们在初始化子类的时候,我们在调用父类的构造函数的时候,其实我们还是会调用子类覆盖的这个方法。因为在调用父类构造函数的时候,子类还没被初始化,然而现在又调用了子类的方法,这就意味着会发生很多意想不到的问题(子类没初始化,变量也就没初始化)。所以要想绝对安全的初始化,在父类中,我们仅仅调用final修饰的方法(fianl方法不能被重写,我们就不会调用子类的这个方法了,要记得private修饰的方法隐含一个final,所以也是也适用private方法)。这一条的证明可以看第六条:对象的创建过程。
3.我们在向上转型,通过动态绑定(也就是方法参数是父类,但传入的是子类的对象),在调用这个对象的域(说白了,域就是类定义的变量),如果子类也有同一个这样的域,那么访问的还是父类的,而不是子类的。
Class A{ class B extends A{
Int a; int a;
} }
Void displayA(A a){//这里传入B对象
System.out.println(a.a);//实际上我们这里输出的是父类A对象的a变量
}
4.静态方法是不会被覆盖重写的(因为他跟一个类存在关联,而不是对象)。
5.类初始化步骤总结:
a)如果一个类包含了final static标志的基本类型变量,那么会在编译的时候就初始化了。如果final static修饰的是对象,会在创建这个类的对象或者使用这个变量的时候,初始化。
b)一个类中包含的static数据只会存在一份内存,并且只会初始化一次,在new这个类的对象或者在使用任意static数据的时候,会初始化所有的static数据或者static花括号块.
c)如果一个类不包含父类,那么初始化的顺序是:先按申明顺序初始化所有的变量,然后再执行构造器的代码。
d)如果一个类包含父类,那么在初始化的时候,会先初始化父类(父类初始化流程跟c一样),然后再初始化该类的所有变量,然后再执行构造器初始化。
6.对象创建的过程:
a)即使没有显式的使用static关键字,构造器实际上也是静态方法。当首次创建类或者其静态方法跟变量被访问时,java解释器必须查找类路径,然后定位这个类的class文件
b)载入该类的class文件,静态初始化的动作会被执行,因此静态初始化只会被执行一次。(这里很好的解释了静态数据初始化为什么只会被执行一次,因为.class文件只会被加载一次,在加载的时候就会执行静态数据初始化)
c)在堆上分配该对象的内存
d)将该内存清零,并初始化基本类型为默认值,对象的引用为null。
e)执行定义变量的初始化代码(d步骤是虚拟机默认执行的步骤,它是必须的,也就是我们在使用代码初始化这个变量之前,它先会被默认初始化一次)
f)执行构造器代码)
e)如果这个对象有父类,那么我们会先加载子类和父类的.class文件,然后再分配内存,并且将这个内存清零,然后再先初始化父类,接着初始化子类。这就意味着,如果在子类中覆盖了父类的方法,父类初始化的时侯又调用了这个方法,他会执行子类的方法调用。然而在这个时候,子类是没有初始化的,这个方法如果调用了子类的某些变量,其实他会拿到这些变量的默认初始化值(这个时候我们已经完成了d步骤)。这就解释了第二条的产生结构。(具体请看163页)

第九章

1.接口的申明只能是Public或者默认的包访问权限(一个文件只能有一个Public接口,而且是跟这个文件名对应的接口),不能使用Private和protected(在类中是可以定义的)。接口中的方法强制是public的,如果不指明,也会是Public,我们不能用private和protected来修饰接口。接口中也可以包含域,但这些域会隐式的包含一个static和final修饰符。
2.创建一个能够根据所传递参数对象的不同而具有不同行为的方法,称为策略模式。
3.如果继承了一个类和一个接口,他们有同样方法,那么不需要实现这个接口,父类中的方法会替代接口中的方法。
4.接口是可以继承接口的,而且可以多重继承extends A,B,C都没问题的。接口继承会涉及到方法重载问题:如果方法不一致,会发生重载。如果参数一致,返回类型不同,那么会编译不通过,如果方法完全一样,那么只用写一个方法就可以了。
5.在某些时候,我们可以用Interface充当enum使用。
6.接口中的域不能是”空final“,也就是说在定义的时候就要初始化。
7.我们可以在类中定义一个接口(在类中可以直接访问,类外需要加上类名和点号),因为这个接口是属于一个类的,所以我们可以用Private修饰符修饰这个接口,使其只能被这个类使用,同样,在接口中我们也可以定义接口,但其访问修饰符必须是public的,就算不写public修饰符,也默认是public。如果在一个类中定义了一个private接口,那么我们是不能在通过类的对象访问到这个接口的,我们不能在方法中传参之外获取这个接口的引用(如果有问题,可以看185页的内容)。

第十章:内部类

1.在创建一个非静态内部类的时候,必须先创建外部类对象,这是因为,内部类对象必须要捕获一个指向那个外围类对象的引用。内部类对象是可以访问外围类所有的数据成员和方法的,它是通过外围类的这个对象的引用获取的。内部类创建的方式:DotNew dn = new DotNew();//创建外部类 DotNew.Inner dni = dn.new Inner();//通过外部类创建内部类。
2.我们是可以覆盖父类的内部类的,也就是说,我们可以重写一个名字和父类内部类一模一样的内部类。
3.我们可以在一个方法作用域中定义一个类,这样的类叫做局部内部类。因为类在一开始就会编译,所以就算我们在条件语句块里定义一个类,他也会编译的。
4.匿名内部类就是直接new一个接口或者抽象类,然后申明这个类。匿名内部类是没有构造器的,如果父类没有默认构造器(或者无参构造器),就要在定义的时候把参数传进去。具体:new A(x){….}.A是一个接口或者抽象类,()中的x就是传入的参数,当然,你也可以传入多个。
5.匿名内部类没有构造器,所以我们可以在定义字段的时候执行初始化,这跟构造器的初始化效果是一样的。匿名内部类或局部内部类中直接使用外部局部变量,必须是final类型的(初始化父类构造器传入的参数是不需要的,因为他不是在匿名内部类中直接使用)。原因是外部的局部变量可能会随着方法的调用结束后释放这个局部变量,然后我们的匿名内部类或者局部内部类还持有了这个变量。所以在我们使用外部局部变量的时候,编译器在匿名内部类或者局部类会初始化的时候自动复制一个这样的值到这个内部类中。然而我们为什么需要这个值是final的呢?是因为设计者考虑到这两个值已经是不同的两个值了,在内部类修改这个值是不会引起外不变量的值修改的,为了避免让人产生困惑,就强制使用fianl修饰,让你的代码在编译阶段无法修改这个值。
6.嵌套类是指static修饰的内部类,它跟普通内部类最大的不同是它没有包含一个指向外部类的引用,他不属于外部类。嵌套类创建的时候,不需要其外围类的对象,我们不能从嵌套类访问外围类非static变量和方法。普通类和嵌套类还有的区别是,普通类不能有static字段和方法,不能包含嵌套类,而嵌套类是可以包含static字段和方法,以及普通内部类和嵌套类的。
7.因为在接口中是默认public static的,所以如果我们在接口中定义一个类,会自动变成嵌套类。所有继承该接口的类都会默认包含这个嵌套类。这样做的好处是,我们能够为实现这个接口的类提供某些公用的功能(这个非常重要)。
8.内部类可以在一个类的任何地方定义(类中(类中是指属于这个类的,不属于某个方法,他是类全局性的),方法中,某一块作用域中(也就是类中的一个花括号中)),而匿名内部类只能在方法体中或者某一块作用域中定义。嵌套类只能在一个类中定义。(可以仔细想想为什么会是这样的,其实这跟使用范围有关,普通内部类可以根据类名在限定的作用域中初始化,嵌套类加了static,所以不能再方法和作用域中定义,而匿名内部类因为他是没有类名的,所以定义了就要立马使用,如果在类中定义,那么就用不到这个类,所以JAVA做了限制)
9.要知道,内部类设计的最大作用是解决多重继承问题。
10.闭包:是一个可调用的对象,它记录了一些信息,这些信息来源于创建他的作用域,通过这个定义可以看出,内部类是面向对象的闭包,因为它持有外部对象的一个引用,在此作用域内,内部类有权操作所有的成员,包括private。内部类闭包回掉的应用请看206页,这个非常重要。
11.请看209页这个非常经典的内部类既能向上转型在编译的时候确定具体对象(策略模式),又能通过内部类,操控外部类的数据的这个例子,这简直就是鱼与熊掌兼得。
12.我们定义一个类,然后继承一个内部类。在初始化的时候,由于这个导出类不包含这个内部类的外部类对象,所以我们在初始化的时候需要传入一个外部类对象。具体例子:
Class WithInner{
Class Inner{}
}
Public class InheritInner extends WithInner.Inner{
InheritInner(WithInner wi){//我们在这个导出类初始化的时候,传入withinner类,然后调用他的初始化方法
Wi.super();
}
}
13.一个类继承另一个类,并重写其内部类,他并不会覆盖这个内部类,这是因为这两个类,都有各自的命名空间,要记得,我们如果继承后覆盖某个方法,向上转型通过父类引用持有子类对象调用父类方法时,其实调用的是子类的方法。所以这里要注意在子类调用的还是子类的内部类。
14.局部内部类(也就是在代码块里创建一个类),他能够拥有这个代码块里所有的变量访问权,也有整个类变量的访问权。使用局部类不使用匿名类最大的依据理由是需不需要多个对象。
15.一个类如果包含内部类,编译后生成.class文件规则是:A1BCD,号,如果实在方法体中,那么就要加上方法体的标号(1,2,3等,系统会根据方法的顺序自动生成编号),如果是内部类中的,那么就要加上这个内部类的名字,然后再加上$符号(p215)。

第十一章:持有对象

1.Collection(容器):一个独立元素的序列,这些元素服从一条或者多条规则。List必须按照插入的顺序保存元素,Set不能有重复的元素,Queue是按照排队规则来确定对象产生的顺序。
2.Map是一组成对的键值对对象,允许你使用键来查找。
3.容器添加元素的方法:
a)Collection connection = new ArrayList(Arrays.asList(1,2,3,4,5));
b)Collections.addAll(collection,11,12,13,14,15);
c)Collections.addAll(collection,moreInts);
d)List list = Arrays.asList(16,17,18,19);
e)List list = new ArrayList(Arrays.asList(16,17,18,19));
值得注意的是,Arrays.asList向List传入的是一个数组,它具有固定长度,所以我们不能再使用list的add方法添加元素。D和e的区别是,d中List和数组保存的是同一引用,所以修改list中元素的值和顺序,都会让数组发生同样的变化,而e是复制传值,所以二者修改不会让对方产生影响。
4.ArrayList,LinkedList都是有序的,HashSet和TreeSet是无序的,LinkHashSet是有序的,HashMap跟TreeMap是无序的,LinkedHashMap是有序的。这里的有序无序,是指插入的顺序。
5.ArrayList访问元素快,LinkedList随机访问慢,但插入删除元素快。这是因为ArrayList用了数组,LinkedList用了Node。
6.在一个容易中,判断一个对象是不是相等,是根据equals方法决定的。基本类型的包装类默认通过基本类型的大小比较,如果自定义类不自己写equals方法,那么就是比较对象的内存地址。
7.Collection容器类为了更好的统一get和add方法,实现了迭代器接口。
a)使用方法iterator()要求容器返回一个Iterator,Iterator将准备好返回序列的第一个元素。
b)使用next()获取序列中的下一个元素,获取后游标会自动加一
c)使用HasNext检查是否还有元素
d)使用remove将迭代器新近返回的元素删除。
8.ListIterator是一个更加强大的Iterator子类型。他只能用于List类的访问,Iterator只能向前移动,ListIterator能够双向移动,而且可以使用set()替换他访问过的最后一个元素,我们通过listIterator方法产生一个指向List开始处的ListIterator,并且可以调用listIterator(n)方法来获取指向列表索引n元素的ListIterator。
9.LinkedList添加了栈,队列或双端队列的方法。下面是他的一些基本方法介绍:
a)getFirst()和element()方法都是返回列表的头元素,并不移除他,如果为空则抛出NoSuchElementException异常。Peek()跟他们一样,都是返回不删除第一个元素,但区别是,如果为空,会返回一个Null。
b)removeFirst()和remove()删除第一个元素,为空抛出NoSuchElementException异常,poll()则抛出null。
c)addFirst(),add()和addLast()相同,都是将元素插入到队尾。
d)removeLast()移除并返回列表的最后一个元素。
10.一般情况下,我们通过LinkedList实现Stack和普通的Queue。
11.容器类前面带有Hash的,一般都是在查找做了优化,带有Tree的,一般都是会自动排序,LinkedHash对访问和查找都做了优化。
12.Map的key不能为基本类型,map.keySet()返回Map键值对的set,map.values()返回所有的值的Collection。
13.PriorityQueue是具有有限弹出元素的Queue,如果PriorityQueue保存的对象是基本类型的包装对象,那么就是按照基本类型的排序规则进行。如果是其他的对象,那么我们就要实现Comparator接口。
14.Foreach语句实际上是依赖Iterable接口或者数组来产生迭代条件的。

第十二章:通过异常处理错误。

1.所有的标准异常类都有两个构造器:一个是默认构造器,另一个是接收字符串作为参数,以便能够把相关信息放入异常对象的构造器中(250p上面部分)。
2.在使用new创建了异常对象后,对象的引用将会传递给throw。尽管返回的异常对象其类型通常与返回的方法类型不同,但从效果上看,它就是从方法上返回的。可以简单的把异常处理堪称一种不同的返回机制,当然若过分强调的话,就又麻烦了。另外还可以用抛出异常的方式从当前作用域退出。这两种情况下将会返回一个异常对象,然后推出方法作用域。异常与方法返回不同的是,他们的返回的地点完全不同,异常将会在一个恰当的异常处理中得到解决,它的位置可能离异常被抛出的地方很远,也可能会跨越方法调用栈许多层次(250p第二段)。
3.一个try后面能跟多个catch块,每个Catch块只能有一个异常类型。值得注意的是,如果前面的catch捕获到了异常,后面的catch就不会执行,所以前面定义了一种类型,后面去catch前面的类以及其子类,那么编译器会报错(251第一段)。
4.在方法定义的时候申明throws exception叫做异常申明,它表明使用这个方法的时候将会抛出异常。在调用这个方法的时候,编译器会强制要求你捕获这个异常(256p)。
5.PrintstackTrace()方法所提供的信息可以通过getStackTrace()方法来直接访问,之歌方法将返回一个由栈轨迹中的元素所构成的数组,其中每个元素都表示当前栈的一帧。数组中最后一个元素和栈底是调用序列中的第一个方法调用。下面是使用方法演示:for(StackTraceElement ste:e.getStackTrace()){
System.out.println(ste.getMethodName());//这里将会输出异常抛出到捕获经过的所有方法。
}(257p下面部分)
6.在catch中抛出的异常,后面的catch块不会接收,他会抛给上面的环境去处理。例:
Catch(Exception e){
throw e;
}值得注意的是,这里throw e抛出的异常,他显示的是当前抛出位置到处理这个异常的轨迹,如果要想加上catch之前的轨迹,就要使用:throw e.fillInStackTrace();
(258p重新处理异常)。
7.我们常常想要在捕获一个异常后,抛出另一个异常(上面的是抛出原来的异常),并且希望把原来的异常信息保存下来,这被称为异常链。在所有的Throwalbe的子类在构造器中都可以接受一个cause对象作为参数,这个cause用来表示原始异常信息。Throwable的子类只有三种基本的异常提供了带cause参数的构造器,他们是Error(虚用于虚拟机报告错误系统),Exception,RuntimeException。如果其他类型想要把异常链接起来,应该使用InitCause()方法,我们通过getCause()拿出之前我们包装的异常(260p异常链)
。例子:catch(Exception e){
Throw new Exception(e);
}
调用e.getCause()方法,我们将拿到之前传入进的e异常。
8.一个子类覆盖父类中带throw exception方法,并不写throw exception在调用这个方法的时候如果是通过向上转型,用父类引用调用这个方法,则需要强制try catch,用子类是不需要的。值得说明的是,异常说明不能作为方法签名(263)。
9.RuntimeException(及其子类)是不需要我们捕获的,也就是说,就算异常申明要抛出一个RuntimeException,我们也不用try catch。被抛出的RuntimeException如果没有被我们捕获,会穿越所有的执行路径达到main方法,而不被捕获(263p)。
10.Finally是在try后面使用的,他是一定能被执行的,一个try块中纵使使用了return语句他也是会被执行的。
11.如果在一个try块中嵌套了另一个try,在里面的try中使用了finally执行清理,里面的try发生了异常,但它会执行finally里面的语句,如果在执行finlly语句的时候又发生了异常,那么他会覆盖原先try块中产生的异常(269p)。例:
try{
try{
throw new Exception();//在执行try块的时候,我们抛出了异常,但我们的这个异常没有来的及处理,我们要先执行finally块
}finally{
doSomething();//这里我们执行清理的时候抛出了异常,我们将会覆盖try块中产生的异常。
}
}
12.如果我们在finally中使用了return,那么我们将会让异常丢失。
try{
throw new Exception();//这里我们抛出了异常,我们会执行finally中的语句
}finally{
return;//这里如果我们使用return。那么异常将会消失
}(269p)
13.派生类覆盖基类方法时,只能抛出比基类有更少的异常说明(也就是说,只能包含父类方法申明锅的异常或者该异常的抽象类),这是为了向下转型的时候,能准确捕获(向下转型,用父类引用去调用派生类方法的时候,编译器只会要求你catch父类的异常说明)。如果一个派生类的基类和实现的接口有共同的方法名,那么在派生类中,异常申明只能是基类和接口中方法申明中共同申明过的接口。派生类申明构造方法的时候,如果必须要申明父类中申明过的异常说明(270p)。
14.在new一个派生类的时候,如果基类构造器会产生异常,那么就算在派生类构造器中用调用基类构造器的时候用try catch捕获这个异常,父类也会抛出异常(275p)。
15. 抛出异常的时候,异常处理系统会按照代码的顺序查找最近的处理程序,找到匹配的处理程序后,他会认为异常将会得到处理,然后不在继续查找。如果catch语句中,父类放在了派生类前面,这意味着派生类永远不会执行,所以编译器会报错。
16.一个类在创建的时候会发生异常,这就意味着我们需要双重try方法。因为如果一个类在创建的时候会发生异常,这意味着创建失败的时候,我们是不需要做清理工作的,所以我们不需要finally块。而在调用的过程中,会发生异常,这个时候我们需要清理,需要finally块(p274)。例:
Class Test{
Test()throws Exception{
throw new CreateException();//我们在创建这个类的时候,就会发生异常。如果发生了异常,表明这个对象没有创建成功。
}
Void run throws Exception(){}
Void dispose(){
//我们在这个方法中执行清理工作。
}
}
try{
Test test = new Test();
try{
test.run();//创建对象成功后,我们执行某些方法产生了异常,这时候我们需要清理。
}catch(Exception e){

}finally{
Test.dispose();//这里我们才能执行清理工作
}
}catch(CreateException e){
//在这里我们捕获创建时候的异常,因为还没创建这个对象,所以我们是不用清理的
}
17.弱类型语言是指在编译的时候是否做检查。
18.在处理一个异常的时候,如果我们不知道怎么处理,可以在这个方法上面加上throws exception。Exception表示要抛出的异常。我们也可以通过throw new RuntTimeException(exception)把他包进运行时的异常,这样我们既忽略了错误,又没有让这个异常不被别人知道(279把异常传递给控制台)。

第十三章:字符串

1.string大部分的方法都不会修改其自身的值,他会重新复制string值,然后修改再返回。这告诉我们string对象是具有只读性的(283p)。
2.string的优化:a.单行字符串相加(string1+string2+string3),jvm会自动转换成stringBuilder.append(string1).append(string2).append(string3),就算两个相加,也是如此;b.如果在for循环中直接相加string,它会先初始化一个StringBuilder,然后append这两个或者多个字符串,再复制给原来的string,所以我们最好在for循环外创建一个stringBuilder。C.stringBuilder如果指定长度,会有效避免缓冲重复分配(285p)。
3.stringBuffer是安全线程,stringBuilder是非安全线程(287p)。
4.我们不能再一个类的toString()方法中试图通过字符串和this相加,因为编译器会默认的把this转化成this.toString(),这样的话就会产生一个无限递归(287)。
5.我们可以使用System.out.printf()或者system.out.format输出类似于C语言printf的那种格式化输出(289)。

第14章:类型信息

1.JAVA有两种方式让我们在运行时识别对象和类的信息,一种是传统的RTTI,它假定我们在编译的时候已经知道了所有多类型。另一种是反射,它允许我们在运行的时候发现和使用类的信息(313)。
2.每个类都有一个Class对象,当编写并编译了这个类的时候,就会产生。更恰当的说,是被保存在一个同名的.class文件中(314)。
3.Class有以下常用的方法:Class.forName(“com.jun.xxx”);这个方法用来根据包括包名的类产生这个类的Class对象。C.getInterfaces()获取接口c.getSuperClass()获取父类,c.newInstance()用来初始化并返回这个对象。值得注意的是,这个类必须要有默认构造器,否则会抛出异常(317)。
4.Class.forName();如果传入基本类型,如int会报错,但确切实实可以获取基本类型的class对象。具体方法是int.class,他通过newInstance能产生他的包装类型。(318)
5.当使用“.class”来创建对Class对象的引用时,不会自动初始化该Class对象。这说明Object.class会获得Class对象的引用,但并不会初始化这个类(也就是不会初始化静态域),但Class.forName()则会(319)。
6.为了使用类而做的准备工作实际包含三个步骤:a.加载:这是由类加载其执行的,该步骤将查找字节码,并从这些字节码中创建Class对象。B.链接:在链接阶段将验证类中的字节码,为静态域分配存储空间,并且如果需要的话,将解析这个类创建的对其他类的所有引用。C初始化:如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块(319)。
7.如果一个类中的域是static final申明的,并且是基本类型,那么在编译这个类的时候就确定了这个值,无序加载这个类就能获取他的值。在访问一个类的static变量的时候,系统会先初始化别的static值,再初始化要获取的值(320)。
8.Class

第15章:泛型

1.一个类申明泛型的方式:在类名后面加上尖括号,以及泛型的名字,接口的申明使用,也跟类一样(354)。
class A{}
class B extends A{} //子类申明的时候,父类泛型使用子类的泛型类型
class B extends A{} //子类申明的时候,不定义父类泛型
class B extends A{}//直接使用一个确定的类填充父类泛型
2.元组就是我们想返回多个对象的时候,但是return只能返回一个对象,这个时候我们就可以通过一个类,包装这两个或者多个对象。然后返回包装对象,通过包装对象获取值(354)。
3.基本类型不能作为泛型参数(360)。
4.如果使用的泛型方法能够取代整个类,那么我们就应该用泛型方法。对于一个static的方法而言,无法访问泛型类的参数类型。所以static方法要想拥有泛型能力,必须自己申明泛型。泛型方法申明如下:
Public T f(T t)(361);
5.在使用泛型类的时候,我们必须指明泛型类型。而在使用泛型方法的时候,我们不必要指明,因为系统会自动找出其类型,这叫类型参数推断。如上面的泛型方法在调用的时候,我们是直接f(a),a如果为int类型,那么T就是Integer了(361)。

第16章:数组

1.数组和容器区别有三方面:效率(数组快,容器慢,但容器用起来方便),类型(容器能使用泛型)和保存基本类型的能力(容器不能保存基本类型)(433)。
2.无论使用哪种数组,数组标识符其实只是一个引用,指向在堆中创建的一个真实对象,这个对象用以保存指向其他对象的引用。可以作为数组初始化语句的一部分隐式创建此对象,或者用new表达式显示的创建。只读成员是length的一部分,他是数组唯一可以访问的字段。对象数组和基本类型数几乎相同的,唯一的区别是对象数组保存引用,基本类型数组保存基本类型的值(434)。
3.数组创建的方式:int[] a = new int[5]; int a[] = {1,2,3} int[] a = new int[5]{}
4.数组创建的时候,会产生默认值,如果是基本类型,那么就是初始化值(数值型初始化为0,char初始化为(char)0,布尔型初始化为false)。对象就被初始化为Null.
5.编译器是不会让我们创建数组泛型的,但是我们可以将一数组的引用复制给泛型数组。List[] ls; List[] a = new List[10]; ls = (List[])la;
6.System.araycopy()用来复制数组。Arrays.equals(a1,a2)用来比较数组,他会根据每一个元素的equals作比较。Arrays.sort(a)用来排序数组,值得注意的是,数组的元素必须实现Comparable。如果一个数组排序好了,那么我们可以通过Arrays.binarySearch()来执行查找,它实际上是一个二分法查找,如果没有排序,那么就查找到不确定的值(二分法查找未排序的数组,那就混乱了)。

第十七章:容器的深入研究

1.享元模式:有时候你可能需要大量的对象,或者你产生某些普通对象占有很多内存。通过享元模式,你可以使得对象一部分被具体化,其实说白了,享元模式就是在你需要对象的时候再初始化。(p464)
2.如何自定义一个map(想不起来的话,可以去看p467页)
3.可选操作是指在定义容器类的时候,有时候我们不想让某些类支持某些方法,但由于我们的容器都是继承collection的,所以我们不得不避免的会带入某些我们不想要的方法,由于有这些方法,用户很容易误用,所以我们在调用这个方法的时候,抛出一个unsupportedOperationException()异常。这就是可选容器操作。
4.Set接口的导出类存入set的元素必须是唯一的,因为set不保存重复元素,加入set的元素必须实现equals方法以保证对象的唯一性。Set与Collection有完全一样的接口。Set不保证元素的次序。HashSet为快速查找而设计的Set存入HashSet的元素必须定义hashCode(),他保存的元素是无序的,也不会按插入顺序保存。TreeSet底层为树结构,使用它可以提取出Set的有序序列,元素必须实现Comparable,它根据Comparable保持排序。LinkedHashSet它具有HashSet的查询速度,且内部使用链表维护插入的顺序。在使用迭代器遍历Set的时候,结果会按照元素的插入顺序显示,元素也必须定义hashCode()方法。
5.TreeSet的直接父类是SortSet,他有三个很有趣的方法:subSet(low,height)用来截取排序后,low到height范围的值。headSet(height)截取height值前面的元素,tailSet(low)用来截取low后面的元素。
6.PriorityQueue根据Compare排序,基本类型会自动转化成包装类型,如果保存的对象没有实现compare接口,那么是会报错的。值得注意的是,我们保存的时候,他是按保存顺序保存的 ,只是在读取的时候,他会排序读出。
7.我们可以通过LinkedList实现双向队列。
8.HashMap之所以有这么快的读取速速度,是因为他在使用get()方法的时候,用了散列存储(p484)。
9.HashMap基于散列的实现,插入和查询”键值对“的开销是固定的。可以通过构造器设置容量(散列数组的容量)和负载因子(散列数组保存了多少百分率后重新调整容量),以调整容器的性能。
10.LinkHashMap它类似于HashMap,但是迭代遍历它时,取得的顺序是其插入的顺序,或者是最少使用的次序。只是比hashMap慢一点,而在迭代访问时反而更快,因为它是用链表维护内部次序。
11.TreeMap:基于红黑树(原理跟二叉树删除一个节点后,重新排序差不多的一个算法),他们会被排序(次序由Comparable或者Comparator决定)。TreeMap的特点在于,所得到的结果是经过排序的。TreeMap是唯一带有subMap()方法的Map<他返回一个子树。
12.WeakHashMap:弱键映射,允许释放所指的对象;这是为了解决某些特殊问题而设计的。如果映射之外没有引用指向某个键,则此键可以被垃圾回收器回收。
13.ConcurrentHashMap是线程安全的map
14.IdentityHashMap:使用==代替equals()对键进行比较的散列映射。转为解决特殊设计而设计的。
15.如果键被用于散列Map,那么他要实现hashCode()和equals()方法。如果被用于TreeMap,那么要实现Comparable接口。
16.SortedMap的唯一实现是SortedMap。这个跟SortedSet有点相似。
17.LinkedHashMap散列化所有的元素,但是在遍历的时候,却又以元素的插入顺序返回键值对。此外,可以在构造器中设定LinkedHashMap,使之采用基于最近最少使用的LRU算法,于是没有被访问的元素就会出现在队列的前面,对于需要定期清理元素以节省空间的程序来说,此功能使得程序很容易实现。
18.正确的equals()方法必须满足下列五个条件:1.反对行。任意x,x.equals(x)一定返回true。2.对称性。任意x和y,如果y.equals(x)返回true,x.equals(z)返回true,则x.equals(z)一定返回true。3.传递性。对于x,y,z如果有x.equals(y)返回true,y.equals(z)返回true,则x.equals(z)一定返回true。4.一致性。对于任意x和y,如果对象中用于等价比较的信息没有改变,那么无论调用x.equals(y)多少次,返回的结果应该保存一致,要么一直是true,要么一直是false。5.对于任何不是null的x,x.equals(null)一定返回false。
19.什么是散列:散列是通过一个固定长度的数组,将Key值保存在其中,他将会计算一个与数组下表位置对应的值直接访问这个数组,所以他的速度非常快。因为数组是固定的,为了保存大于数组的值,所以如果一个数组可能保存多个Key,他是通过List实现的,通过遍历这个list并且用equals()比较就可以获得某个位置确定的值了,这部分查询的速度是比较慢的。这就是为什么散列保存的对象需要实现equals()方法。实际上,对象的散列值是可以重复的(p492)。
20.设计hashCode()时的最重要因素是无论何时,对同个对象调用HashCode()都应该产生同样的值。也就是put()和get()都应该拿到一样的值。
21.两个string,如果值相同,那他们映射的内存区域是同一块区域。
22.对于List,我们首选应该是arrayList。只有我们需要额外的功能时候,或者当程序性能因为经常从表中间插入和删除顺序而变差的时候,才回去选择LinkedList。如果使用的是固定数量的元素,那么既可以选择使用背后有数组支撑的List,也可以选择真正的数组。
23.LinkedHashSet用链表维护顺序,其iterate顺序为插入顺序,HashSet为Hash保存的顺序,TreeSet为排序顺序。在查询和添加元素的时候,HashSet比TreeSet性能要好,因为TreeSet在插入和查询的时候,要对元素进行排序。而在迭代的时候,因为TreeSet用的是链表,所以他访问的顺序要比HashSet块(因为散列的数组是比实际值数量更多的,所以这迭代它肯定要比迭代等数量的链表慢)。对于插入操作,LinkedHashSet比HashSet的代价要高。这是由于维护链表的额外开销造成的。
24.LinkedHashMap在插入元素的时候比HashMap要慢,因为他维护散列数据结构的同时,还要维护链表,以保持插入顺序,正是这个链表,使得其迭代速度更快。
25.HashMap的性能因子。我们可以通过手工调整HashMap来提高其性能。从而满足特定需求,下列是几个我们必须了解的术语:1.容量:表中的桶位数(也就是散列数组的大小)。2.初始容量:在创建时,拥有的桶位数,HashMap和HashSet都具有允许你指定初始容量的构造器。3.尺寸:表中当前存储的项数。4.负载因子:空表的负载因子为0,而半满表的负载因子为0.1.以此类推,负载轻的表产生的冲突可能性小,因此对于插入和查找都是最理想的(但会减慢使用迭代器遍历的过程)。HashMap和HashSet都具有你允许指定负载因子的构造器,当负载情况达到该负载因子的水平时,容器都将自动增加其容量(桶位数),实现的方式是使容量大致加倍,并重新将所有的对象分配到新的桶位几种(这叫再散列)。
26.Java通过clone()来复制一个对象,这个方法是属于object的,他将一个对象赋值给另一个对象(不是将引用复制给另一个对象)。
27.Collections.unmodifiableSet返回一个只读对象,他的原理是在调用这个方法的时候new一个新类。这个类继承collection,并且覆盖原来可以修改容器的方法,在调用修改数据相关方法的时候会抛出异常。
28.我们如果想把一个方法改造成线程同步的,其实我们可以新建一个类,覆盖旧类的所有方法,并且用同步修饰,在新类中调用旧类的代码(p517)
29.有时候我们在使用一个容器的迭代器访问数据(遍历,获取迭代器大小)的时候,我们如果在迭代过程中修改数据(如插入,删除)可能会引发一系列的问题。系统通过抛出ConcurrentModificationException异常来保证我们不会成功的在修改迭代器数据后,还能成功访问数据。事实上,我们在并发中使用同一个容器会很容易造成这种问题。Java设计了ConcurrentHashMap,CopyONWriteArrayList和CopyOnWriteArraySet都可以让我们避免产生ConcurrentModificationException异常(也就是我们可以在获取迭代器后,修改原来容器的数据,并且使用修该前的迭代器数据不会报错)。
例子:Collection c = new ArrayList();
Iterator it = c.iterator();
c.add(“A object”);//在获取迭代器后,我们又修改了数据
String s = it.next();//我们的数据已经改变,这时候通过改变钱获取的迭代器访问数据就会抛出ConcurrentModificationException异常。
30.java的几种引用持有类型:强引用,软引用,弱引用,虚引用的理解。
31.WeakHashMap的运用。
32.Vector是一个被废弃的类,他基本上跟ArrayList类似。
33.HashTable等价于HashMap,他也是一个被废弃的类,他的性能不如HashMap。
34.bitSet是用来存储大容量的true或者false这种类型的“开关”信息。

第十八章:JAVA I/O系统

1.File这个类有一定的误导性,通常情况下,我们会认为他是一个文件类。实际上,他既能代表一个文件,又能代表一个目录下的一组文件的名称。如果他指向要给文件集,那么我们可以对此集合调用List()方法,这个方法会返回一个字符数组。
2.File获取目录与过滤目录(p526):
File file = new File(“filePath”);
file.list(a);//list方法中的a是一个对象,他的类实现了FileFliter接口
3.文件的一些基本方法:f.canRead()用来判断文件是读,f.canWrite()用来判断文件是否可写,f.getName)返回文件名字,f.getParent()返回文件对应的文件夹,fi.getPath()返回文件的路径,f.length()返回文件的长度,f.lastModified()返回文件最近修改的时间,f.isDirectory()返回文件是否是一个目录。
4.文件创建与删除与移动或者重命名的方式:
a)创建跟删除:
File file = new File(“path”);
If(file.exists()){//判断文件是否存在
file.delete();//创建文件夹
}else{
file.mkdirs();//删除文件夹
}
b)移动跟重命名使用renameTo(“路径或者文件名字”)方法。
5.变成语言中的I/O类库中常常使用流这个抽象概念,它代表任何有能力产出数据的数据源对象或者有能力接收数据的接收端对象。流屏蔽了实际I/O设备中处理数据的细节。
6.InputStream的作用是用来表示那些从不同数据源产生输入的类。这些数据源包括:字节数组,String对象,文件,管道,一个由其他种类的流组成的序列,其他数据(如Internet连接等)
7.InputStream跟OutPutStream通过装饰者模式创建其他的类。FilterInputStream为“装饰器”提供基类。其中,装饰器类可以把树形或者游泳的接口与输入流连接在一起。
8.IputStream的几种类型
类 功能 构造器参数
如何使用
ByteArrayInputStream 允许将内存的缓冲区当作InputStream 缓冲区,字节将从中取出
作为一种数据源:将其与FileterInputStream对象相连接以提供有用的接口
StringBufferInputStream 将String转换成InputStream 字符串。底层将实现实际使用StringBuffer
作为一种数据源:将其与FilterInputStream对象连接以提供游泳的接口
FileInputStream 用于从文件中读取信息 字符串,表示文件名,文件或者FileDescriptor对下岗。
作为一种数据源:将其与FilterInputStream对象连接以提供有用的接口
PipedInputStream 产生用于写入相关PipedOutPutStream的数据。实现“管道化”概念 PidedOutputStream。
作为多线程中数据源:将其与FilterInputStream对象相连接以提供有用的接口。
SequenceInputStream 将两个或者多个InputStream转化成单一的InputStream 两个InputStream对象或者一个容纳InputStream对象的容器Enumeration。
作为一种数据源:将其与FilterInputStream对象相连接以提供有用的接口
FilterInputStream 抽象类,作为装饰器的接口。其中“装饰器”为其他InputStream”类提供游泳的功能 InputStream
用来作为装饰器修饰上述InputStream,达到不同效果的InputStream。
9.OutPutStream类型
类 功能 构造器参数
如何使用
ByteArrayOutPutStream 在内存中创建缓冲区,所有送往流的数据都要放置在此缓冲区 缓冲区初始化尺寸(可选的)
用于指定数据的目的地:将其与FilterOutPutStream对象相连接以提供有用的接口。
FileOutPutStream 将信息写至文件 字符串,表示文件名,文件或者FileDescripto对象。
指定数据目的地:将其与FilterOutPutStream对象相连接以提供有用的接口
PiedOutPutStream 任何写入其中的信息都会izidong称为相关PipedInputStream的输出,实现“管道化”概念 PipedInputStream
指定用于多线程的数据的目的地:将其与FilterOutPutStream对象相连以提供有用的接口
FilterOutPutStream 抽象类,作为“装饰器”的接口,其中“装饰器”为其他OutputStream提供有用功能 InputStream类
他是一个修饰器,他的子类用来修饰InputStream以满足更多的需求。
10.FilterInputStream装饰器的一些子类
类 功能 构造器参数
如何使用
DataInputStream 与DataOutPutStream搭配使用,因此我们可以按照可一直方式从流读取基本数据类型(int char long)等 InputStream
包含用于读写基本类型数据的全部接口
BufferInputStream 使用他可以防止每次读取数据到时候都得进行实时写操作。代表”使用缓冲区“ InputStream,可以指定缓冲区达标
本质上不提供接口,只不过是向进程中添加缓冲区所必须的。与接口对象搭配
11.FilterOutputStream类型
类 功能 构造器参数
如何使用
DataOutputStream 与DataInputStream搭配使用,因此可以按照可移植方式向流中写入基本数据类型 OutputStream包含用于写入基本类型数据的全部接口
PrintStream 用于产生格式化输出,其中DataOutputStream处理数据的存储,PrintStream处理显示 OutputStream,可以用boolean值指示是否在每次换行时清空缓冲区,应该是对OutputStream对象的final封装,可能会经常使用到它。
BufferOutputStream 使用它以避免每次发送数据时候都要进行实际的写操作,代表使用缓冲区可以调用flush()清空缓冲区 OutputStream可以指定缓冲区大小
本质上并不提供接口只不过向进程中添加缓冲区所必须的,与接口对象搭配。
12.Reader和Writer类他并不是完全取代InputStream和OutputStream(纵使使用这两个类的子类有时会提出过时警告),InputStream和OutputStream在以向字节形式的I/Oz中存在极大的价值,Reader和Writer则提供面向Unicode与面向字符的I/O功能。
13.有时候我们必须把来自于“字节“层次结构中的类和”字符“层次结构中的类结合起来使用,为了实现这个目的,要用到适配器类:InputStreamReader可以把InputStream转化成Reader(构造器传入inputStream对象),而OutputStreamWriter则可以把OutputStream转化成Writer(构造器传入outputStream对象)。
14.设计Reader和Writer继承层次结构主要是为了国际化。老的I/O流(inputStream和outputStream)继承层次结构仅支持8为字节流,并且不能很好待处理16位Unicode字符.由于Unicode用于字符国际化,所以Reader和Writer继承层次结构是为了在所有的I/O操作中都支持Unicode。另外,新的类比旧的类要快
15.几乎所有的场合都有相应的Reader和Writer来提供天然的Unicode操作。然而在某些场合,面向字节的InputStream和OutputStream才是正确的处理方案,特别是java.util.zip类库就是面向字节的而不是面向字符的。因此最明智的选择就是尽量先尝试使用reader和writer,一旦程序代码无法成功编译,我们在使用面向字节的流。
16.我们需要看看桌子上打印的字符流的类结构图。
17.RandomAccessFile他是一个独立的类,它不是InputStream和OutputStream继承层次结构中的一部分。它实现了DataInput和DataOutput接口(DataInputStream和DataOutputStream也实现了这两个接口)。他的方法大多是完全重写的本地方法,这是因为我们的RandomAccessFile拥有在一个文件向前和向后移动的功能,所以他跟I/O流有着本质的区别。他的getFilePointer用来查找文件当前所处的位置,seek()用来在文件内移动至新的位置,length()用来判断文件的最大尺寸。另外,构造器第二个参用来指示我们是随机读的(“r”)还是既读又写的(“rw”)。它并不支持只写文件。
18.下面我们介绍几个常用的文件读取方法,由于篇幅有限,我们只写实现内容和页码,如果不记得了可以去看看书
a)缓冲区读取文件(p540)
b)从内存输入(p541)
c)格式化的内存输入(p541)
d)基本的文件输出(使用writer542)
e)存储和数据恢复。这DataInputStream写入数据,无论什么平台,DataOutputStream都可以准确的读取数据。(p543)
f)RandomAccessFile的使用(p545)
g)printWriter的文件读写方式(p547)。
h)buffered读取字节流(p548)
19.UTF-8前面两个字节是保存字符串的长度,如果实标准的ASCII码,为了节省空间,直接使用单一字节形式。
20.字节流就是读取文件或者数据的时候,一个byte一个byte读取。而字符流在读取文件的时候,按照字符来读取,比如说UTF-8字符串等,他一次是多个字节的,具体根据使用什么字符来读。字节流读取一般跟文件的文本内容无关,实际上,字符是由字节转换来的,只不过读取的时候,为了更方便的输出文本,才有了字符流这个实现。
21.程序的所有输入都可以来自于标准输入,他的所有输出也都可以发送到标准输出,以及所有的错误信息都可以发送到标准错误。标准I/O的意思在于:我们可以很容易的把程序串联起来,一个程序的标准输出可以成为另一个程序的标准输入。
22.如何重定向标准I/O流到别的流中(p550)
23.我们可以通过java代码执行底层的一些命令,然后通过流的形式返回执行的结果(成功或者异常信息),最简单的就是我们之前写的安卓调用linux命令。(p551)
24.Java在java.nio.*包中引入了新的类库,其目的在于提高性能。Java中的管道是为了两个线程快速通信而存在的。Unix/linux是为了进程通信考虑的。新的I/O类之所以速度得到了提升,就是因为使用了管道和缓冲器。Java中唯一与通道交互的缓冲器是byteBuffer类,他是一个相当基础的类,通过告知分配多少存储空间来创建一个byteBuffer对象。旧的类库中有三个类被修改了,用以产生FileChannel。这三个类分别是FileInputStream,FileOutputStream以及用于既读又写的RandomAccessFile。这些类都是直接草种字节流的,与底层的nio性质一致,Reader和Writer这种字符类不能用于产生通道,但是java.nio.channels.Channels类提供了实用方法,用以在通道中产生Reader和Writer(注意Channel是指通道,他是硬件设备或文件网络嵌套字的开放连接)。
25.RandomAcessFile和FileOutputStream通道和缓冲器的使用(p532)。
26.FileChannel类的基本使用(p552)。FileChannel有个很方便的方法,将一个管道中的的数据读取到另一个管道中:filechannel.transferTo(from,to,out),from是读取数据的起点,to是读取数据的终点,out是另一个管道。
27.牢记byteBuffer类中的allocate(length)方法是代表产生一个指定大小的缓冲器 allocateDirect()方法会产生一个根据实际内存环境分配的缓冲器,但是这种分配开支会更大,并且具体实现也随操作系统不同而不同,所以我们必须在此实际运行程序来查看缓冲是否可以为我们带来速度上的优势。
28.缓冲期容纳的是普通字节,为了把他们转换成字符,我们要么在输入他们的时候对其进行编码,么要么在将其从缓冲期输出的时候对他们进行解码(p554 29标号的例子)。
29.ByteBuffer的一些方法调用以及ByteBuffer的一些基本原理(557-566).
30.文件的加锁机制允许我们同步访问某个作为共享资源的文件,不过值得关注的是,竞争同一个文件的两个线程可能不是在不同的Java虚拟机上,或者一个是java线程,另一个是操作系统中的其他两个本地线程。文件锁对其他的操作系统进程是可见的,因为java的文件锁直接映射到了本地曹祖系统的加锁工具。下面演示枷锁机制的使用方法:
FileOutputStream fos = new FIleOutPutStream(“file.txt”);
FileLock fl = fos.getChannel().tryLock();//实现加锁
If(fl!=null){
fl.release();//释放锁。
fos.close();
}
31.FileLock中的tryLock()方法是非阻塞式的,它将设法获得锁,如果不能获得,它将直接从方法调用返回。Lock()则是阻赛式的,他要阻赛进程直至锁可以获得,或者直至调用lock()的线程中断,或调用lock()的通道关闭。Lock和trylocal方法实际上还可以对文件部分加锁,他的调用方法是lock/tryLock(long position,long size,Boolean shared)。Position和size是文件的起点位置和长度。如果你获得了文件的部分枷锁,当文件增大超出position+size的时候,那么在Position+size之外的部分不会被锁定,无参数的枷锁方法会对整个文件进行加锁,甚至文件变大后也是如此。在有参数的加锁方法中,最后一个参数表示是否共享锁还是独占锁。共享锁必须由底层的操作系统支持,如果操作系统不支持,那么就算传入的参数是true,也表示独占锁。
32.共享锁和独占锁:举个最简单的例子,一个文件如果使用了共享锁,那么就算文件被某个线程加锁,别的线程也可以获取锁,只不过这两个线程都只能同时读取这个文件,不能一个读,一个写。当一个线程需要对这个文件写入的时候,就会由共享锁变成独占锁,别的线程都无法访问,待这个线程写入完毕后,别的线程才能获得共享锁,重新拥有对这个文件的读权限。
33.压缩类的结构(p568)
34.Gzip以及zip压缩的应用(p568)
35.对象的序列化将那些实现了Serializable接口的对象转换成一个字节序列,并且能够在以后这个字节序列完全恢复为原来的对象。这一过程甚至可以通过网络进行,序列化的加入是为了支持两种主要特性,一种是java远程方法调用,使存活于其他计算机上的对象使用起来就像是存活在本机一样。当向远程对象发送消息时,需要通过对象序列化来传输参数和返回值。
36.序列化特别聪明的一个地方是它不仅保存了对象的全景图,而且还能追踪对象内包含的所有引用,并且保存哪些对象。接着又能对对象内包含的每个这样的引用进行追踪。以此类推,这种情况有时被称为对象网,单个对象与之建立连接,而且还包含了对象的引用数组以及成员对象。
37.对象序列化ObjectOutPutStream和ObjectInputStream的使用(p573)
38.Serializable和Externalizable的使用以及区别(p576);
39.如果我们不想让一个类的某些字段不序列化,除了让一个类继承Externaizable手动实现对象的某些字段序列化,还有一种办法就是使用transient关键字来修饰这个字段,他的意思是我不需要你自动保存或恢复数据,我会自己处理。
40.Externalizable对象在默认情况下是不保存任何他的字段的,所以transient关键字只能和Serializable使用,值得关注的是,就算Externalizable就算使用了transent修饰,我们同样可以保存。
41.Externalizable的替代方法:我们可以在一个对象中实现Serializable接口,然后重写private void writeObject(ObjectOutputStrewam stream)throws IOException以及private void readObject(ObjectInputStream stream)throws IOException,ClassNotFoundException这两个方法,系统会通过反射在使用默认Serializable恢复方法之前检查是否实现了这两个方法,如果实现了,那么我们系统会调用这两个方法用来恢复或者保存类数据。在writeObject方法内部,我们可以调用stream.defalutWriteObject()方法来保存非transient字段,在readObjet方法内部,我们可以调用stream.defalutReadObject()来读取之前保存过的非transient修饰的字段。特殊字段,我么可以手动使用stream.ream(Object)和stream.write(Object)来保存和读取字段。值得注意的是,我们通过这种方式实现的序列化保存对象,在还原读取对象的时候,他是不会实现任何类中的任何构造器方法(580)。
42.只要将任何对象序列化单一流中,我们就可以恢复出我们写出时一样的对象网,并且没有任何重复复制出的对象。
43.一个类中的静态变量在序列化保存对象时,是要单独处理的。具体的处理方法请看585页。
44.Java Preference的使用(p588)

原创粉丝点击