再读《Java编程思想》(Review 《Thinking in Java 3rd》)(9-12章)
来源:互联网 发布:起航网络 编辑:程序博客网 时间:2024/05/19 11:51
原创文章,转载请注明出处:http://blog.csdn.net/wind5shy/article/details/5345748
第九章异常与错误处理
(By wind5shy:http://blog.csdn.net/wind5shy)
标准异常类的两个构造器
缺省构造器与String作参数将相关信息放入异常对象的构造器。
try与catch
可以将很多可能抛出异常的代码放在一起置于try块中以便集中处理,但每次只抛出最前的一个。抛出的异常交给catch做相应处理,可以有多个catch以处理相应的异常类型;不同于switch的是每个catch只执行其本身,其后的catch并不执行;可以在catch里把捕获的异常重新抛出,其后的catch将被忽略。
重新抛出与异常链
直接重新抛出Exception e则e.printStackTrace()显示的是原抛出点的调用栈信息,无论在哪里抛出都是这样,可以调用继承自Exception父类Throwable的fillInStackTrace()(方法返回Throwable对象引用,这里由于继承返回e),将新的调用栈信息填入e,即更新e的调用将信息。
Throwable(Throwable cause)(其实还有Throwable(String message, Throwable cause)),这个构造器可以用来构造异常链,cause代表原始Throwable,通过传递给新Throwable来构成异常链,这样就可以追踪到异常最初发生的位置。也可以用书上所说的Throwable.initCause(Throwable cause)方法来构建,但是这种方法最多只能调用一次(因为其相当于初始化cause),且只能在通过缺省和Throwable(String message)构造器创建的Throwable对象中调用,如果对象在通过Throwable(String message, Throwable cause) 或Throwable(Throwable cause)构造器创建的则不能使用(这些对象的cause构造的时候就初始化了嘛)。注意:Error、Exception、runtimeException提供了书上Throwable的四种构造器,但他们的子类就不一定全部提供了相应的子类构造器(比如AclNotFoundException就只有缺省构造器,子类的构造器和父类不一定会一一对应),自行定义的异常如果需要带cause的构造器可能需要自行定义。如DynamicFields这个例子用给DynamicFieldsException定义DynamicFieldsException(Throwable cause) {this.initCause(cause);}这个构造器可以达到一样的效果。
错误信息
可以包含在异常对象内部也可以体现在异常类名称上,上一层通过错误信息来决定如何处理异常。(通常错误信息只体现在异常类名称而不包含在异常对象中。)
Error
用于指示合理的应用程序不应该试图捕获的严重问题,如系统错误。大多数这样的错误都是异常条件。
RuntimeException
会自动被JVM抛出,无需进行异常说明。这些异常产生原因是由于程序编写过程中的不周全的代码引起的:比如除数为零,抛出java.lang.ArithmeticException;越界访问数组,抛出java.lang.IndexOutOfBoundsException.。这些都是可以通过更改程序可以避免的错误即可通过在代码中加入对除数是否为零,数组长度的判断来避免运行错误,形象的说法就是这些都是常见的“低级错误”,是由程序员本身的不小心造成的。这些错误难以避免且程序员很难一一全部考虑到,而且就算考虑到手动编写相关代码也会非常非常非常繁琐,所以就让JVM帮我们做——JVM在程序运行的时候检查,检查到错误再告诉我们——从而使我们发现自己犯的“低级错误”然后再改正。
Checked Exception
Error与RuntimeException都属于Unchecked Exception,即我们无需关心无需做相应处理,是系统异常;除了RuntimeException以外的Exception都是Checked Exception,也叫非RuntimeException,也可以理解为自定义异常。系统异常由系统处理,自定义异常当然得由我们自己来处理。这些异常不是由于代码本身引起或者说不能全由代码去控制错误的产生,如读取一个不存在的或格式错误的URL,你对用户输入的url在程序读取此url前并不能判断其是否是正确的,即我们编码的时候没有犯任何“低级错误”,不是我们的问题但由于用户的原因从而程序产生了错误,一般来说就是不满足在方法中我们设定的前置条件。所在我们在编程的时候需要考虑到这种情况(进行异常说明)且在异常产生时必须进行相应处理(try-catch)。实际开发中,一般系统内部就使用Unchecked Exception,而在涉及到外部参数时,使用Checked Exception。
继承中的异常限制
n 子类覆写的方法只能抛出父类方法异常说明中的异常及这些异常的子类,但也可以选择不抛出。使用子类方法的时候,只需捕获子类方法可能抛出的异常;但如果将子类对象转型为父类,则捕获时需加上父类方法可能抛出的异常。但是异常限制不对构造器起作用,即子类构造器可以抛出父类构造器未声明的异常,不过由于创建子类时会调用父类构造器,所以子类构造器的异常说明需包含父类构造器的异常说明——刚好和普通方法相反。值得注意的是子类构造器不能捕获父类构造器抛出的异常,一个例子:
class Except1 extends Exception {
public Except1(String s) {
super(s);
}
}
class BaseWithException {
public BaseWithException() throws Except1 {
throw new Except1("thrown by BaseWithException");
}
}
class DerivedWE extends BaseWithException {
// Gives compile error:
// unreported exception Except1
// ! public DerivedWE() {}
// Gives compile error: call to super must be first statement in constructor:
// ! public DerivedWE() {
// ! try {
// ! super();
// ! } catch(Except1 ex1) {}
// ! }
public DerivedWE() throws Except1 {
super();
}
}
public class ConstructorExceptions {
public static void main(String args[]) {
try {
new DerivedWE();
} catch (Except1 ex1) {
System.out.println("Caught " + ex1);
}
}
}
n 父类构造器要抛出异常,子类构造器用super()调用的时候无法用try-catch来捕获(编译器会提示super()要写在最前)。
n 如创建对象时中构造器中抛出异常则对象不会被创建。
为什么子类方法抛出的异常可以比父类的少?因为多态,所以子类方法抛出的异常父类必须包含——比如父类一个方法有多个子类分别覆写,这些子类方法分别抛出一些不同的异常,显然父类方法需要包含这所有的异常,因为程序会根据父类方法的异常说明将抛出的异常做相应的处理,不包含在异常说明中的异常抛出后程序显然无法处理。异常处理就是一个从上到下逐渐细化的过程。
异常匹配
异常抛出后系统会寻找最近的catch语句来处理。处理父类异常的catch语句会屏蔽其后处理子类异常的catch语句,这时编译器就会认为子类catch永远得不到执行而报错,所以处理父类异常的catch语句要放到最后面。
(By wind5shy:http://blog.csdn.net/wind5shy)
第十章类型检查
(By wind5shy:http://blog.csdn.net/wind5shy)
运行期类型识别(run-time type identification,RTTI)
n 概念:RTTI提供一些方法,如显示类型转换、instanceof、Class.isInstance()等,让用户可以从指向一个实例的基类引用找到实例的确切类型。同多态相关,但是是不同的概念。
n RTTI在编译时需要得到进行类型转换或者比较中涉及的所有类型的class文件。
n 所有类型转换和比较在运行期检查。
Shapes例子中shapeList调用draw()的时候为何要转型?
因为shapeList为Object[],而Object对象是没有draw()的,编译器在编译的时候是没有多态的,而是根据声明的类型进行相应处理,如果声明shapeList为Shape[]再调用draw()就无需转型。
关于转型和多态的一个例子:
class Shape {
void draw() {
System.out.println(this + ".draw()");
}
}
class Circle extends Shape {
public String toString() {
return "Circle";
}
}
class Square extends Shape {
public String toString() {
return "Square";
}
}
public class Tset {
public static void main(String[] args) {
Square sq = new Square ();
((Shape)sq).draw();
sq.draw();
Shape sp = (Shape)sq;//同于Shape s = new Square ();相当于还是将个//一子类Squar的引用传给基类Shape
sp.draw(); //由于多态,所以还是Square.draw()
((Square)sp).draw();
if(sp instanceof Circle) {//sq不能用instanceof Circle,因为Squar
//与Circle直接没有继承关系
((Circle)sp).draw();
}
else if(!(sp instanceof Circle)) {
System.out.println("(Shape)sq is not a Circle");
}
}
}
结果:
Square.draw()
Square.draw()
Square.draw()
Square.draw()
(Shape)sq is not a Circle
类型转换检查
n 类字面常量(Class literal)基本数据的包装类的标准域TYPE:一个引用,指向对应基本数据类型的class对象,即boolean.class等价于Boolean.TYPE、void.class等价于Void.class等。
n instanceof比较对象是否是某类的实例,类与实例的类型必须有继承或实现关系,可以是实例继承类,也可以是类继承实例(没有这些关系的直接在编译的时候就能判断了,不用通用instanceof在运行期再去判断);格式:实例名 instanceof 类名,返回boolean值。class.isInstance(实例)作用相同。
n 直接使用实例的class对象和类的class对象通过“==”比较显然不能检查继承或实现的关系,只能是类完全一样或者不是。
n class.newInstance()必须要有缺省(无参数)构造器。
反射
n 由于一些原因class文件在编译期不能获取,所以在运行期打开和检查class文件,根据class文件获取相应类信息,如构造器、成员或方法等(RTTI中,编译器在编译期打开和检查class文件)。
n 另一种说法:Reflection指的是我们可以于运行时加载、探知、使用编译期间完全未知的classes。换句话说,Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods。这种“看透class”的能力(the ability of the program to examine itself)被称为introspection(内省、内观、反省)。Reflection和introspection是常被并提的两个术语。
关于class类
n 众所周知Java有个Object类,是所有Java类的继承根源,其内声明了数个应该在所有Java类中被改写的方法:hashCode()、equals()、clone()、toString()、getClass()等。其中getClass()返回一个Class对象。
n Class类十分特殊。它和一般类一样继承自Object,其实体用以表达Java程序运行时的classes和interfaces,也用来表达enum、array、primitive Java types(boolean, byte, char, short, int, long, float, double)以及关键词void。当一个class被加载,或当加载器(class loader)的defineClass()被JVM调用,JVM便自动产生一个Class对象。如果你想借由“修改Java标准库源码”来观察Class对象的实际生成时机(例如在Class的constructor内添加一个println())是不可能的,因为Class类没有任何public或protected的构造器。
一个例子:
public final class Class implements java.io.Serializable,
java.lang.reflect.GenericDeclaration,
java.lang.reflect.Type,
java.lang.reflect.AnnotatedElement {
private Class() {}
public String toString() {
return ( isInterface() ? "interface " :
(isPrimitive() ? "" : "class "))
+ getName();
}
...
Class类片段。注意它的private empty constructor,意指不允许任何人经由编程方式产生Class对象。是的,其实例只能由JVM 产生。
生成Class对象的方式
n Object.getClass()
String str = "abc";
Class c1 = str.getClass();
n Class.getSuperclass()
Button b = new Button();
Class c1 = b.getClass();
Class c2 = c1.getSuperclass();
n static method: Class.forName()
Class c1 = Class.forName ("java.lang.String");
Class c2 = Class.forName ("java.awt.Button");
Class c3 = Class.forName ("java.util.LinkedList$Entry");
Class c4 = Class.forName ("I");
Class c5 = Class.forName ("[I");
n .class语法
Class c1 = String.class;
Class c2 = java.awt.Button.class;
Class c3 = Main.InnerClass.class;
Class c4 = int.class;
Class c5 = int[].class;
n primitive wrapper classes的TYPE语法
Class c1 = Boolean.TYPE;
Class c2 = Byte.TYPE;
Class c3 = Character.TYPE;
Class c4 = Short.TYPE;
Class c5 = Integer.TYPE;
Class c6 = Long.TYPE;
Class c7 = Float.TYPE;
Class c8 = Double.TYPE;
Class c9 = Void.TYPE;
(By wind5shy:http://blog.csdn.net/wind5shy)
第十一章对象的集合
(By wind5shy:http://blog.csdn.net/wind5shy)
数组
存储和随机访问效率最高,可以保持基本类型,但大小和生命周期固定,即生成后容量不能改变。
数组标识符
一个引用,指向堆中的一个对象,如果是对象数组,这个对象保存指向其他对象(就是数组中的具体对象元素)的引用;如果是基本类型数组,这个对象直接保存基本类型的值。length是该对象中唯一可以访问的成员,表示对象可以储存多少个元素(即数组的容量而不表示实际存储元素的数量)。“[]”是访问数组中元素的唯一方式。
只有在对象实现了Comparable接口才可以调用Arrays.sort(对象列表)或者对象有相应的Comparator实现了Comparator接口才可以调用Arrays.sort(T[],Comparator<? super T>)。即是说必须有能够比较顺序的方法(Comparable接口中的compareTo()和Comparator接口中的compare()才能排序。只有在排序后才能调用Arrays.binarySearch()。排序时使用了Comparator则查找时用binarySearch(T[] , T , Comparator<? super T> )。
容器
初始化
容器作为成员一般在定义的时候用默认构造器初始化为空容器,如ArrayList al = new ArrayList(),说明al是一个还没有包含任何元素的ArrayList,可以根据以后的需要添加元素;注意不是 ArrayList al = null,这样只是声明了al类型是ArrayList,但其并没有指向任何实际的对象。这与普通成员对象在定义的时候采取的策略不同,普通成员对象在定义的时候一般只声明类型而不让其指向具体对象,只有在需要的时候用构造器或set()给其赋值。
容器成员引用
一个例子:
class Content {
String s;
public Content(String s) {
this.s = s;
}
@Override
public String toString() {
return s;
}
}
public class ArrayListTest {
public static void main(String[] args) {
ArrayList<Content> al = new ArrayList<Content>();
Content c1 = new Content("c1");
Content c2 = new Content("c2");
Content[] cs = {c1,c2};
al.add(c1);
al.add(c2);
c1 = new Content("c11");
System.out.println(al.get(0));
System.out.println(cs[0]);
c2.s = "c22";
System.out.println(al.get(1));
System.out.println(cs[1]);
}
}
结果:
c1
c1
c22
c22
ArrayList和数组只是把元素的引用(确切地说是引用的值,就是对象的地址)复制到相应位置,所以al.get(i)和cs[i]的值就是引用,c1 = new Content("c11")把c1的值变为 Content("c11")对象的地址,但al.get(0)和cs[0]的值并没有变,还是原有的Content("c1")对象的地址,所以显示结果仍为c1;而对Content("c2")对象进行c2.s = "c22"操作后对象的地址并没有改变,只是成员s变为c22,引用c2仍和和al.get(1)、cs[1]的值一致,所以结果为c22。
类型
n Collection:每个位置保存一个元素,包括List和Set。
n List:元素有一定的顺序(列表的概念),按对象进入的顺序保存,不做排序或编辑。
n Set:元素不重复(集合的概念),所以相同对象只接受一次,使用自己内部的排序法保存,不关心元素添加的顺序。
n Map:元素为键值对,键(key)不能重复,值(value)可以重复(映射的概念),也有内置的排序,不关心元素添加的顺序。
容器保存对象@转型的一个例子:
import java.util.ArrayList;
import java.util.List;
class Cat {
private int catNumber;
public Cat(int i) {
catNumber = i;
}
public String toString() {
return "This is Cat #" + catNumber;
}
public int getNumber() {
return catNumber;
}
}
class Mouse {
private int mouseNumber;
public Mouse(int i) {
mouseNumber = i;
}
// Override Object.toString():
public String toString() {
return "This is Mouse #" + mouseNumber;
}
public int getNumber() {
return mouseNumber;
}
}
public class MouseList extends ArrayList {
private List list = new ArrayList();
public void addMouse(Mouse m) {
list.add(m);
}
public void add(Mouse m) {// ArrayList中的add()返回值是boolean,但
// 返回值不是方法用来区别的度量之一,这里写void
//是可以的
System.out.println("add " + m);
super.add(m);
}
public Mouse get(int index) {
return (Mouse)super.get(index);
}
public static void main(String[] args) {
MouseList mice = new MouseList();
for (int i = 0; i < 3; i++) {
mice.add(new Mouse(i));
}
for (int i = 0; i < 3; i++) {
mice.addMouse(new Mouse(i));
}
for (int i = 0; i < 3; i++) {
mice.add(new Cat(i)); //由于多态,执行时调用的是父类ArrayList
//的add()
}
// for (int i = 0; i < 3; i++) {
// mice.addMouse(new Cat(i));//这里会报错,因为addMouse没有多
//态,只能接受mouse类型参数
// }
for (int i = 0; i < mice.size(); i++) {
System.out.println(mice.get(i));//cat不能转型为mouse,报错
}
}
}
结果:
add This is Mouse #0
add This is Mouse #1
add This is Mouse #2
This is Mouse #0
This is Mouse #1
This is Mouse #2
Exception in thread "main"java.lang.ClassCastException: code.Cat cannot be cast to code.Mouse
at code.MouseList.get(MouseList.java:51)
at code.MouseList.main(MouseList.java:72)
迭代器(Iterator)
受限制,只能用来:
n itetator()使容器返回一个Iterator;
n next()返回序列中下一个元素,第一次调用时返回序列中第一个元素;
n hasNext()检查序列中是否还有元素;
n remove()从迭代器指向的 collection中移除迭代器返回的最后一个元素。
List
n ArrayList:由数组实现的List,是List的首选,允许对元素进行快速随机访问,但向队列中间位置插入和移除元素很慢(因为要重新移动插入或移除元素之后的所有元素,但在实际中从一个位置向后顺序插入速度较快,估计JVM使用了缓存机制,先将插入点后的对象取出缓存,在所有新增对象插入后再将这些对象一次性放在新增对象之后,当然也可能是分几次,不过肯定不是插入一个对象移动一次;通常使用的add(Object)是向队列末尾添加,速度较快),size、isEmpty、get、set、iterator和 listIterator 操作都以固定时间运行;add操作以分摊的固定时间运行,即添加 n个元素需要 O(n)时间;其他所有操作都以线性时间运行(大体上讲);ListIterator应只用作由后向前遍历ArrayList,不应用来插入和移除。
n LinkedList:由双向链表实现的List,所以可以当作堆栈、队列或双向队列使用。顺序访问较快,插入和移除操作开销不大,随机访问相对慢。
n Vector过时了?Vector和ArrayList功能上的唯一区别在于Vector是同步的,主要方法都是synchronized的,而ArrayList不是,所以在单线程的情况下性能较好,但是在多线程的情况下Vector就有用武之地了,不过ArrayList也可以通过添加同步控制代码以支持同步,同时也有更大的自由度,控制得好的话性能应该还是有明显优势。(我在TIJ的第一版中没有找到老布同学所谓的Vector的缺点,不过从其他方面收集的资料和源码看来,Vector应该随着JDK的版本提高而不断地在改进,至少JDK的API里还没说Vector是过时的。)HashMap和HashTable也类似。
Set
加入Set的元素必须定义equals()以确保元素的唯一性,使用HashSet还要定义hashCode()。自己的类如果使用到Set时由于Set要维护元素的储存顺序,所以要实现Comparable接口。
n HashSet:以散列函数排序元素,类似HashMap,只不过Key在HashSet内部,而元素=Value不能重复,Set首选。
n TreeSet:用红黑树排序元素。
n LinkedHashSet:用散列以加快查询速度,用链表维护元素的次序。
性能上HashSet > TreeSet > LinkedHashSet但相差不大,在同一数量级。
Map
主要是HashMap、TreeMap、LinkedHashMap,结构和性能类似Set的三个子类。
hashCode()
n 散列函数,对象的某些信息通过函数生成散列码(int值),使对象映射到该散列码上,系统通过散列码来查找对象。
n 不应使hashCode()依赖于唯一性的对象信息,因为hashCode()生成的散列码不需具有唯一性,否则这样会造成无法生成两个key相同而value不同的Map的情况(比如用新的Map来取代旧的Map的情况。唯一性一般和对象身份相关,可以用来实现equals()。)。
n Object里的hashCode()使用地址计算散列码,equals()也是地址比较,相同的对象必须有相同的散列码,所以在自己的类用到Map的时候要同时重载hashCode()和equals()。但如果两个对象不同,则它们的hashCode不一定不同,如果两个不同对象的hashCode相同,这种现象称为冲突,冲突会导致操作哈希表的时间开销增大,所以尽量定义好的hashCode()方法,能加快哈希表的操作。
n 要使hashCode()实用,它必须速度快且有意义,有意义表示为hashCode()根据对象的可以做equals()操作的属性生成散列码。
n 散列表长度为2的整数次方性能较好而不是为质数。注意:散列码的范围和散列表的长度不一定相等,实际中散列表长度一般会比散列码的范围小。
推荐hashCode()方案
1. int result = 17(或其它非零正数)
2. 对对象内每个有意义的属性计算一个int散列码c,具体操作如下表:
域类型
计算(c)
boolean
f ? 0 : 1
byte,char,short,int
(int)f
long
(int)(f^(f>>>32))
float
Float.floatToIntBits(f);
double
long l = Double.doubleToLongBits(f);
c = (int)(l^(l>>>32))
Object,其equals()调用这个域的equals()
f.hashCode()
数组
对每个元素应用上述规则,再用公式3把散列值组合其类
3. 公式:result = result*37 + c
4. 检查是否相同的对象具有相同散列码,如果不是,找出原因,修正错误。
Collections
Collection的工具类,完全由在Collection 上进行操作或返回Collection的静态方法组成,可以提供排序、查找、最大值最小值、同步等功能。
Reference类
特殊的引用类,不同与普通引用,而是普通引用的包装类,用于特殊的目的,如下:
如果对象是“可获得的”(reachable),指此对象可在程序中找到,即内存栈中有一个普通引用直接或者通过中间链接(引用指向某对象,某对象中的某引用又指向另一对象,如此反复直到指向我们讨论的对象)指向该对象,此时垃圾回收器不能释放该对象。反之,对象不是“可获得的”就可以回收。如果某对象程序暂时无法使用,但以后又有可能被用到,所以我们希望能继续持有该对象的引用,但我们又希望在内存耗尽的时候允许释放这个对象,此时,就需要用到Reference类。
n SoftReference:软引用,在响应内存需要时,由垃圾回收器决定是否清除此软引用对象,最常用于实现内存敏感的缓存。
n WeakReference:弱引用,常用于实现规范化映射,可以使对象的一个实例在程序中的多处被同时使用,以节省存储空间,不妨碍垃圾回收器回收Map的“键”或“值”。
n PhantomReference:虚引用(幻影引用),在回收器确定其指示对象可另外回收之后,被加入队列,最常见的用法是以某种可能比使用 Java终结机制更灵活的方式来指派 pre-mortem清除动作。
可到达性
从最强到最弱,不同的可到达性级别反映了对象的生命周期。在操作上,可将它们定义如下:
n 如果某一线程可以不必遍历所有引用对象而直接到达一个对象,则该对象是强可到达对象。新创建的对象对于创建它的线程而言是强可到达对象。
n 如果一个对象不是强可到达对象,但通过遍历某一软引用可以到达它,则该对象是软可到达对象。
n 如果一个对象既不是强可到达对象,也不是软可到达对象,但通过遍历弱引用可以到达它,则该对象是弱可到达对象。当清除对某一弱可到达对象的弱引用时,便可以终止此对象了。
n 如果一个对象既不是强可到达对象,也不是软可到达对象或弱可到达对象,它已经终止,并且某个虚引用在引用它,则该对象是虚可到达对象。
n 最后,当不能以上述任何方法到达某一对象时,该对象是不可到达对象,因此可以回收此对象。
接口中的可选方法
实现类可以选择实现与不实现。
(By wind5shy:http://blog.csdn.net/wind5shy)
第十二章 Java I/O系统
(By wind5shy:http://blog.csdn.net/wind5shy)
File
文件类,准确的说是包含文件和目录的路径名的抽象表示形式。List()以String[]列出目录下的文件名,但不保证有相同字符串的文件名将以特定顺序出现,特别是不保证它们按字母顺序出现。
I/O流
需要close()!
InputStream与OutputStream
面向8位字节流
InputStream类型
类
功能
构造器参数
使用
ByteArrayInputStream
将内存中的缓冲区当作InputStream使用
byte[]
作为数据源与FilterInputStream对象相连以提供有用接口
FileInputStream
从文件中读取信息
File,String(表示文件名),FileDecriptor对象
PipedInputStream
产生用于写入相关PipedOutputStream的数据
PipedOutputStream
SequenceInputStream
将多个InputStream对象转换成单一InputStream对象
两个InputStream对象或一个容纳InputStream对象的Enumeration
FilterInputStream
抽象类,作为修饰器的接口,修饰器为其他InputStream类提供有用功能
注:StringBufferInputStream已过时
OutputStream类型
类
功能
构造器参数
使用
ByteArrayOutputStream
在内存中创建缓冲区,所有送往stream的数据都要放置在此缓冲区
int缓冲区初始化尺寸
指定数据目的地与FilterOutputStream对象相连以提供有用接口
FileOutputStream
将信息写至文件
File,String(表示文件名), FileDecriptor对象
PipedOutputStream
任何写入其中的信息都会自动作为相关PipedInputStream的输出
PipedInputStream
FilterOutputStream
抽象类,作为修饰器的接口,修饰器为其他OutputStream类提供有用功能
原始类与修饰类
原始类提供对某种具体数据类型(如byte,File等)的支持,修饰类提供操作数据的具体方式(如缓存,格式化显示等),两者结合完成对具体数据的具体操作,还可以一个原始类结合多个修饰类完成更复杂的要求。
Reader与Writer
面向16位Unicode字符流,大体结构对应相应的InputStream与OutputStream,个别InputStream与OutputStream例外,没有对应Reader与Writer:
n DataInputStream(需要readLine()时,使用BufferedReader)与DataOutputStream;
n SequenceInputStream;
n StreamTokenizer;
RandomAccessFile
自我独立类,不属于InputStream与OutputStream但数据兼容于DataInputStream与DataOutputStream;适用于由大小已知的记录组成的文件。
标准I/O
为I/O建立一个标准模式,使得所有I/O(输入、输出、错误)都可以由这些标准I/O来处理。
n System.in标准输入未包装的InputStream,使用前要进行包装处理;
n System.out标准输出包装过的PrintStream对象;
n System.err标准错误包装过的PrintStream对象。
相应的标准I/O重定向:
n setIn(InputStream);
n setOut(PrintStream);
n setErr(PrintStream)。
新I/O
为了提高I/O速度的类库,使用通道(Channel)——缓冲器(ByteBuffer)结构。
ByteBuffer
n 数据转换:ByteBuffer里为普通字节,要把其转换为字符串,需要在输入缓冲器进行编码或者输出缓冲器时进行解码——使用java.nio.charset.Charset类。
n 视图缓冲器(ViewBuffer):把ByteBuffer包装成处理其他类型的Buffer(如char、int等),这些Buffer便称作ViewBuffer,比如把ByteBuffer包装成CharBuffer来对待以用来处理char类型数据,但这些Buffer是虚拟的,实际储存仍是ByteBuffer(如同SQL中View和Table的关系)。所以例如在ByteBuffer里存入依次0(00000000)、1(00000001)、2(00000010)、3(00000011),每个数字一个byte(8位),用asIntBuffer读出的时候是66051(10000001000000011)(这是因为int数据是32位,会把0、1、2、3的4个8位二进制表示和成一个32位二进制表示)。使用put()来放入相应类型数据,需注意的是short数据使用put()要进行类型转换,如:ByteBuffer.asShortBuffer().put((short)432423)。
Buffer相关成员和方法
所有以下成员和方法皆为final
n capacity:容量,包含的元素的数量,不能更改。
n limit:限制,第一个不应该读取或写入的元素的索引。
n position:位置,下一个要读取或写入的元素的索引。
n mark:标记,用来设置位置,reset()相关。
以上成员遵循关系式:0 <=标记 <=位置 <=限制 <=容量。
n position():取得此缓冲区的位置。
n limit():取得此缓冲区的限制。
以下方法皆返回Buffer:
n position(int newPosition):设置此缓冲区的位置,如果标记已定义且大于新的位置,则丢弃该标记。
n limit(int newLimit):设置此缓冲区是限制,如果位置大于新的限制,则将它设置为新限制;如果标记已定义且大于新限制,则丢弃该标记。
n mark():在Buffer的当前位置设置标记。
n reset():将此缓冲区的位置重置为以前标记的位置。
n clear():清除此缓冲区,将位置设置为 0,将限制设置为容量,并丢弃标记,在使用一系列通道读取或放置操作填充此缓冲区之前调用此方法。
n flip():反转此缓冲区,首先将限制设置为当前位置,然后将位置设置为 0。如果已定义了标记,则丢弃该标记。在一系列通道读取或放置操作之后调用此方法为一系列通道写入或相对获取操作做好准备。
n rewind():重绕此缓冲区,将位置设置为 0并丢弃标记。 在一系列通道写入或获取操作之前调用此方法(假定已经适当设置了限制)。
文件映射
MappedByteBuffer FileChannel.map(FileChannel.MapMode mode, long position, long size):
将此通道的文件区域直接映射到内存中,用于访问那些太大而不能完全放入内存的文件。
对于大多数操作系统而言,与通过普通的read和write方法读取或写入数千字节的数据相比,将文件映射到内存中开销更大。从性能的观点来看,通常将相对较大的文件映射到内存中才是值得的。
文件加锁
n FileChannel.tryLock():尝试获得文件锁,不管是否获得锁均立即返回。
n FileChannel.lock():获得文件锁,若锁暂时无法获得则阻塞直至获得为止。
n tryLock(long position, long size, boolean shared)和lock(long position, long size, boolean shared)可以对文件的一部分上锁,即使文件大小改变锁定区域也不改变;而tryLock()和lock()则是对整个文件上锁,文件大小改变后锁定区域相应改变。shared决定锁为共享锁还是独占锁,必须由操作系统底层提供。
注意:文件锁定是以整个 Java虚拟机来保持的,即一个JVM里只能有一个线程获得文件锁,不适用于控制同一虚拟机内多个线程对文件的访问的同步,如果一个JVM里有一个线程已经获得文件锁后另一个线程也试图获取相同文件锁则会抛出OverlappingFileLockException。
对象序列化
将实现了Serializable接口(标记接口,无任何方法)的对象转换成字节序列(序列化,使用ObjectOutputStream.writeObject()),并且以后可以将这个序列完全恢复成原始的对象(反序列化,使用ObjectInputStream.readObject(),以存储的二进制位为基础进行重组,不调用构造器)。用通俗的语言说序列化就是将一个对象的状态(各个成员)保存起来,然后在适当的时候再获得。
特点
如果某个类能够被序列化,其子类也可以被序列化。声明为static和transient类型的成员不能被序列化——因为序列化是针对具体对象的概念,所以对象里的静态成员不会被序列化,如果需要序列化静态成员,那么需要自定义方法手动序列化;而transient则表示该对象成员为临时数据,不需要序列化。
使用序列化的时机
n 对象序列化可以实现分布式对象。主要应用例如:RMI要利用对象序列化运行远程主机上的服务,就像在本地机上运行对象时一样。
n 对象序列化不仅保留一个对象的数据,而且递归保存对象引用的每个对象的数据,从而可以将整个对象层次写入字节流中再保存在文件中或在网络连接上传递。利用对象序列化可以进行对象的 "深层复制 ",即复制对象本身及引用的对象本身。序列化一个对象可能得到整个对象序列。
Externalizable接口
用来自定义序列化机制的接口(比如将对象部分序列化),实现该接口的对象在序列化时ObjectOutputStream. writeObject()会自动调用writeExternal(ObjectOutput)取代writeObject()里默认的序列化实现机制(相当于用writeExternal()取代了writeObject(),所以需在writeExternal()里将对象所有需要序列化的数据手动保存),该方法调用 ObjectOutput(接口,实现类是ObjectOutputStream)继承自DataOutput的writeX()(X代表基本类型,如writeInt(int))来保存基本值和 ObjectOutput 的 writeObject方法来保存对象、字符串和数组;反序列化时ObjectInputStream. readObject()首先会自动调用对象的初始化代码和默认构造器(这一点与普通Serializable不同,所以对象的默认构造器必须为public,同时需要注意的是由于会调用默认构造器,所以在对象有其他构造器的情况下必须加上默认构造器以供调用),再调用readExternal(ObjectInput)取代read Object()里默认的反序列化实现机制(即相当于通过调用对象的初始化代码、默认构造器和readExternal()取代了readObject(),所以需要使用对象的初始化代码、默认构造器和readExternal()里手动恢复相结合将对象需要恢复的数据反序列化),该方法调用ObjectInput(接口,实现类是ObjectInputStream)继承自DataInput的readX()来恢复基本值和ObjectInput的 readObject方法来恢复对象、字符串和数组。
一个例子:
import java.io.*;
public class Blip3 implements Externalizable {
private int i = 24;
private String s = "wind5shy ";
{
System.out.println("Initialization");
}
//必须有默认构造器供反序列化时调用,否则将报错
public Blip3() {
System.out.println("Blip3 Constructor");
}
public Blip3(String x, int a) {
System.out.println("Blip3(String x, int a)");
s = x;
i = a;
}
public String toString() {
return s + i;
}
public void writeExternal(ObjectOutput out) throws IOException {
System.out.println("Blip3.writeExternal");
//默认的序列化机制已被取代,所以手动将s和i保存,否则序列化之后的对象里将没有s和i的数据
out.writeObject(s);
out.writeInt(i);
}
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
System.out.println("Blip3.readExternal");
//这里没有将储存的s的值“A String ”和i的值47读出来
//s = (String)in.readObject();
//i = in.readInt();
}
public static void main(String[] args) throws IOException,
ClassNotFoundException {
System.out.println("Constructing objects:");
Blip3 b3 = new Blip3("A String ", 47);
System.out.println(b3);
ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("Blip3.out"));
System.out.println("Saving object:");
o.writeObject(b3);
o.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("Blip3.out"));
System.out.println("Recovering b3:");
b3 = (Blip3) in.readObject();
System.out.println(b3);
}
}
结果:
Constructing objects:
Initialization
Blip3(String x, int a)
A String 47
Saving object:
Blip3.writeExternal
Recovering b3:
Initialization
Blip3 Constructor
Blip3.readExternal
wind5shy 24 //由于readExternal()里没有将储存的s的值“A String ”和i的值47读出,所以这里显示的s和i的值是通过调用b3的初始化代码产生的。
Serialization对象将使用 Serializable和 Externalizable 接口,对象持久性机制也可以使用它们。要存储的每个对象都需要检测是否支持Externalizable接口,如果对象支持 Externalizable,则调用writeExternal方法;如果对象不支持Externalizable但实现了 Serializable,则使用ObjectOutputStream保存该对象。
transient关键字:表示该对象成员为临时数据,不需要序列化,和Serializable对象配合使用。
Externalizable的替代方式
n 在实现Serializable的对象中添加writeObject()和readObject ()供ObjectOutputStream. writeObject()和ObjectInputStream. readObject()调用,这两个方法必须基于以下格式:
private void writeObject(ObjectOutputStream stream) throws IOException;
private void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException;
(混乱的设计,只能当特例记忆:1、private说明了方法不是Serializable接口的一部分,因为接口中的所以方法都是public的,但矛盾的是这种使用方式分明是接口的使用方式;2,private还说明了方法只能供本类对象使用,但实际上方法是供ObjectOutputStream的writeObject()和readObject()调用的,那么ObjectOutputStream对象如何访问到这两个private方法的?)
n 在调用ObjectOutputStream.writeObject()时,会检查传递的Serializable对象是否有自己的writeObject()(使用反射机制搜索方法),如果是则跳过默认序列化机制调用自定义的writeObject(),并可以在方法里使用stream.defaultWriteObject()调用默认序列化机制(这个方法也只能在自定义writeObject()里调用)。readObject()类似。需要注意的是调用默认机制需要把defaultWriteObject()调用作为自定义writeObject()的第一个操作并且让defaultReadObject()调用作为自定义readObject()的第一个操作,即两者必须同时配合使用。
对static成员的序列化:书上的例子让人迷惑(明显是翻译的问题):“所有读回的颜色都是‘3’……看上去似乎static的域根本没有被序列化!”?什么意思?是说读回的例子都是3?但我们实际运行代码的时候发现并不是这样:
无参数时运行的结果(序列化):
[class exercise.code.Circlecolor[3] xPos[32] yPos[66] dim[69]
, class exercise.code.Squarecolor[3] xPos[7] yPos[19] dim[64]
, class exercise.code.Linecolor[3] xPos[90] yPos[70] dim[80]
, class exercise.code.Circlecolor[3] xPos[27] yPos[27] dim[39]
, class exercise.code.Squarecolor[3] xPos[77] yPos[61] dim[47]
, class exercise.code.Linecolor[3] xPos[42] yPos[67] dim[57]
, class exercise.code.Circlecolor[3] xPos[90] yPos[51] dim[65]
, class exercise.code.Squarecolor[3] xPos[96] yPos[74] dim[90]
, class exercise.code.Linecolor[3] xPos[71] yPos[30] dim[19]
, class exercise.code.Circlecolor[3] xPos[88] yPos[65] dim[53]
]
参数为CADState.out时运行的结果(反序列化):
[class exercise.code.Circlecolor[1] xPos[32] yPos[66] dim[69]
, class exercise.code.Squarecolor[0] xPos[7] yPos[19] dim[64]
, class exercise.code.Linecolor[3] xPos[90] yPos[70] dim[80]
, class exercise.code.Circlecolor[1] xPos[27] yPos[27] dim[39]
, class exercise.code.Squarecolor[0] xPos[77] yPos[61] dim[47]
, class exercise.code.Linecolor[3] xPos[42] yPos[67] dim[57]
, class exercise.code.Circlecolor[1] xPos[90] yPos[51] dim[65]
, class exercise.code.Squarecolor[0] xPos[96] yPos[74] dim[90]
, class exercise.code.Linecolor[3] xPos[71] yPos[30] dim[19]
, class exercise.code.Circlecolor[1] xPos[88] yPos[65] dim[53]
]
显然,作者这段话的意思是在代码中说我们已经将所有对象的color都设置为3并序列化,希望反序列化之后这些对象的color还是3,但实际上只有Line对象的color是3——因为代码中手动将其color序列化了,而其他对象color设置的值没有正确保存下来。所以对于static成员,默认的机制是不会将其序列化的,需要我们手动添加方法进行序列化。
正则表达式
字符串 a/b——对应正则表达式 a//b——在Java中将正则表达式写成字符串“a////b”
Java字符串处理时/需要转义,“//”代表普通/;同样正则表达式中的/也需要转义,所以将正则表达式写在Java字符串中处理时一共要转义两次,普通/需要“////”。但对于制表、换行、回车、换页等(/t,/n,/r,/f)等只需转义一次,Java字符串处理这些符号不用转义,所以回车正则表达式写在字符串中就是“//n”。
量词
n Greedy(贪婪):先看整个字符串是不是一个匹配,如果没有发现匹配,它去掉最后字符串中的最后一个字符,并再次尝试。如果还是没有发现匹配,那么再次去掉最后一个字符串,这个过程会一直重复直到发现一个匹配或者字符串不剩任何字符。
n Reluctant(勉强、惰性):先看字符串中的第一个字母是不是一个匹配,如果单独着一个字符还不够,就读入下一个字符,组成两个字符的字符串。如果还没有发现匹配,惰性量词继续从字符串中添加字符直到发现一个匹配或者整个字符串都检查过也没有匹配。
n Possessive(占有、支配):只尝试匹配整个字符串。如果整个字符串不能产生匹配,不做进一步尝试。
Greedy
Reluctant
Possessive
Matches
X?
X??
X?+
X,一次或一次也没有
X*
X*?
X*+
X,零次或多次
X+
X+?
X++
X,一次或多次
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次
Patter
模式,正则表达式的编译表示形式。
典型的调用顺序:
Pattern p = Pattern.compile("a*b");//指定为字符串的正则表达式(这里为a*b)首先被编译为此类的实例。
Matcher m = p.matcher("aaaaab");//与正则表达式匹配的任意字符序列(这里为aaaaab)的匹配器。
boolean b = m.matches();//匹配结果
在仅使用一次正则表达式时,可使用
boolean b = Pattern.matches("a*b", "aaaaab");
等效于上面的三个语句。不过如果要多次使用一种模式,编译一次后重用此模式比每次都调用此方法效率更高。
通俗的例子:比如我们搜索文件,要搜索的文件名以a开头以b结尾中间字母不知道,以正则表达式表示就是a*b,用Pattern把a*b包装成一个对象,然后用这个对象去找那些符合a*b表达式的文件——有一个文件名为aaaaab的文件,用Pattern. matcher()把名字“aaaaab”这个字符串包装成一个Matcher对象,再用Matcher. matches()判断aaaaab是否匹配a*b这个表达式(这里我们知道是符合的,所以返回true)并返回结果。
n Pattern compile(String regex, int flags):按flags代表的处理正则表达式的模式将正则表达式编译成模式。regex:要编译的表达式;flags:可能包括 CASE_INSENSITIVE、MULTILINE、DOTALL、UNICODE_CASE、 CANON_EQ、UNIX_LINES、LITERAL和 COMMENTS的位掩码。
n String[] split(CharSequence input):按模式的匹配作为分隔符拆分字符序列。
Matcher:匹配器。
n matches():判断整个字符序列与模式是否匹配。
n lookingAt():判断字符序列从开始起的任意子序列与模式是否匹配。
n find():尝试查找与模式匹配的字符序列的下一个子序列。此方法从字符序列的开头开始,如果该方法的前一次调用成功了并且从那时开始匹配器没有被重置,则从以前匹配操作没有匹配的第一个字符开始,即如果前一次找到与模式匹配的子序列则这次从这个子序列后开始查找。
组:由圆括号分开的正则表达式,可以根据其组号进行调用,第0组表示整个表达式,第1组表示第1个用圆括号括其类的组,依次类推,例如:A(B(C))D,第0组ABCD;第1组BC;第2组C。
n int groupCount():返回匹配其模式中组的数目,不包括第0组。
n String group():返回前一次匹配操作(如find())的第0组。
n String group(int group):返回前一次匹配操作期间指定的组所匹配的子序列。如果该匹配成功,但指定组未能匹配字符序列的任何部分,则返回 null。
n int start(int group):返回前一次匹配操作期间指定的组所匹配的子序列的初始索引。
n int end(int group):返回前一次匹配操作期间指定的组所匹配的子序列的最后索引+1。
替换
n String replaceFirst(String replacement):用replacement替换字符序列与模式匹配的第一个子序列。
n String replaceAll(String replacement):用replacement替换字符序列与模式匹配的所有部分。
n Matcher appendReplacement(StringBuffer sb, String replacement):用replacement替换前一次匹配操作的第0组并将替换后replacement(包括replacement)之前的字符序列存入StringBuffer sb。
n StringBuffer appendTail(StringBuffer sb):在一次或多次调用appendReplacement()后用来复制剩余的字符序列到sb。
n Matcher reset():重置匹配器,放弃其所有显式状态信息并将其添加位置设置为零。
(这章真恼火,内容多且烦。By wind5shy:http://blog.csdn.net/wind5shy)
- 再读《Java编程思想》(Review 《Thinking in Java 3rd》)(9-12章)
- 再读《Java编程思想》(Review 《Thinking in Java 3rd》)(1-4章)
- 再读《Java编程思想》(Review 《Thinking in Java 3rd》)(5-8章)
- 再读《Java编程思想》(Review 《Thinking in Java 3rd》)(13-16章)
- 再读《Java编程思想》(Review 《Thinking in Java 3rd》)(附录A、B)
- 再读《Java编程思想 》
- Thinking in Java 3rd Edition
- Thinking In Java(java编程思想)
- Thinking in Java (Java 编程思想)
- Thinking in java(java编程思想)(1-6章)
- 再读Thinking in Java(一)
- com.bruceeckel.simpletest in "Thinking in Java 3rd Edition"
- "Thinking in Java 3rd Edition"[JAVA书籍]
- Thinking in java 3RD (Tp0cs翻译版)-->前言
- 我的Thinking in Java 3rd 的学习笔记
- 使用Eclipse运行Thinking in Java 3rd 例子源码
- 【Thinking In Java 3rd】第一章对象引论
- 《Thinking In Java(3rd)》--一切皆对象
- AspNetPager 样式以及使用
- 求纯小数的原码、反码、补码
- 数据库代理技术之MySql Proxy
- java.net.URL的URL构建方式
- 将升级目标对象更新到最新的版本2
- 再读《Java编程思想》(Review 《Thinking in Java 3rd》)(9-12章)
- servlet跳转页面的几种方法
- java soap + tomcat 开发
- 视频捕捉全教程(vc+vfw)
- “赢在中国”对80后的30个忠告
- revise object
- 深入理解java abstract class和interface[ZT]
- 金融IT开发
- GPS的hot start、cold start和warm start