Java编程思想学习笔记 by CZF 完整版

Java编程思想第4

--学习笔记

目录

第 2 章        一切都是对象. 2

第 3 章        操作符. 2

第 4 章        控制执行流程. 4

第 5 章        初始化与清理. 5

第 6 章        访问权限控制. 8

第 7 章        复用类. 9

第 8 章        多态. 10

第 9 章        接口. 12

第 10 章     内部类. 14

第 11 章     持有对象. 16

第 12 章     通过异常处理错误. 21

第 13 章     字符串. 22

第 14 章     类型信息. 25

第 15 章     泛型. 26

第 16 章     数组. 27

第 17 章     容器深入研究. 28

第 18 章     Java I/O 系统. 31

第 19 章     枚举类型. 41

第 20 章     注解. 43

第 21 章     并发. 45

  

第 2 章  一切都是对象

1. Java对象引用存储在堆栈,Java对象存储在堆

2. 基本类型存储在堆栈(boolean/byte/char/short/int/float/long/double/void

boolean不允许任何类型的转换处理。其他基本类型的可以相互转换

3. static变量可以通过一个对象访问,st2.i,也可以通过类名访问StaticTest.i

4. java.lang会被自动导入到每一个Java文件

5. Java文件中必须有一个类,其类名与文件名相同

6. Javac HelloDate.java 编译。java HelloDate 运行

7. Javadoc 可从代码中提取文档,javadoc 命令只能在/***/注释中出现,只能为public protected 成员进行文档注释

javadoc HelloDate.java -d ./Document5-author -version

方式:嵌入式html、文档标签(@see、@linkpackage.class#member label、@author 、@param、@return等)p34

8. 编码规范:采用用驼峰风格:ColorOfDesk ,类名首字母大写,标识符首字母小写

第 3 章  操作符

9. 静态导入import static net.mindview.util.Print.*;  JDK1.5中的新特性。一般导入一个类都用 import com.....ClassName;而静态导入是这样:import static com.....ClassName.*;这里的多了个static,还有就是类名ClassName后面多了个 .* ,意思是导入这个类里的静态方法。也可以只导入某个静态方法,只要把 .* 换成静态方法名就行了。然后在这个类中,就可以直接用方法名调用静态方法,而不必用ClassName.方法名的方式来调用。这种方法的好处就是可以简化一些操作,例如打印操作System.out.println(...);就可以将其写入一个静态方法print(...),在使用时直接print(...)就可以了。

10. 应使用()明确多个操作符的优先级

11. 基本类型赋值是内容赋值,对象赋值是引用赋值,a=b,则a、b指向同一个对象。

12. ★★★★值传递与引用传递:

class Emp {
public int age;
}
public class Main {
public static void main(String[]args) {
//Test A
int[] a = {10, 20};
        System.
out.print(a[0] + " ");
change(a);
        System.
out.println(a[0]);
//Test B
Emp emp = new Emp();
        emp.
age = 100;
        System.
out.print(emp.age + " ");
change(emp);
        System.
out.println(emp.age);
//Test C
String s = new String("lisi");
        System.
out.print(s + " ");
change(s);
        System.
out.println(s);
    }
public static void change(int[] a) {
        a[
0] = 50;
    }
public static void change(Emp emp) {
        emp.
age = 50;
emp=
new Emp();//再创建一个对象
emp.age = 100;
    }
public static void change(String s) {
        s =
"zhangsan";
    }
}

输出结果:

10 50   //显然输出结果为10   50。实际传递的是引用的地址值

100 50  // emp = new Emp()创建对象和源对象不同,不改变源对象的值

lisi lisi  对于String类,由于String类是final修饰的,不可变,它会在内存中在开辟一块新空间。

13. 整数除法会直接去掉结果的小数位,而不是四舍五入

14. Random random=new Random(47);Random 对于特定的种子产生的随机数序列是相同的,如果不传参数,将使用当前时间作为种子产生随机序列。

15. 前缀递增/减(++a或--a)先执行运算,再生成值,而后缀递增/减则相反。

16. 测试对象的等价性:基本类型直接用==和!=即可。对于对象,==和!=比较的是对象的引用,但是比较实际内容可以用equals()【需覆盖此方法】方法。例如 new Integer(47)==newInteger(47)结果为false。而newInteger(47).equals(new Integer(47))结果为true

17. Java逻辑表达式有短路功能【提高性能】:test(1)&&test(2)&&test(3)只有当test(1)为true时,才会计算test(2)……。test(1)||test(2)||test(3) 只有当test(1)为false时,才会计算test(2)……。

18. 0x2F 十六进制 0177八进制。直接常量后缀L/l 标识long,f/F 标识 float,d/D标识double型。

19. Java中数字存储用有符号二进制补码:补码=反码+1  -5(0000 0101)补码为:1111 1011

20. 指数计数法:float f=47e38F;末尾的F必须有,否则编译错误,编译器默认将指数作为双精度处理。e/E 代表10的幂,而不是自然基数2.718

21. boolean不仅可以使用逻辑运算符&&、||、!,还可以使用按位操作符【非操作~除外】|、&、^(异或),效果一样。但使用按位操作符没有短路功能。

22. 移位操作符:

左移(<<)底位补0

“有符号”右移(>>)使用“符号扩展”,符号为正高位补0,否则补1

“无符号”右移(>>>)高位补0

对char/byte/short移位时,会先转成int,然后截断,转化成原来的类型。“无符号”右移时会得到错误结果-1  Page50

23. Java语言没有运算符重载

24. Java 不会自动将int 转换为 boolean型,因此,以下语句编译会出错

int x; inty; while(x = y){}

25. float 、double 转 int时总是舍去小数部分,四舍五入请用Math.round()

26. 表达式中的最大数据类型决定了表达式的最终类型。如float类型的数*double类型的数,结果为double

27. Java中 char 为16位,汉字占一个char长度

28. Java 中 float double 无法进行 逻辑运算 按位操作 移位操作

第 4 章  控制执行流程

29. for 用逗号分隔 多初始化语句,多递增语句

for(int i = 1, j = i + 10; i < 5; i++, j = i * 2) {
  System.out.println("i= " + i + " j = " + j);
}

30. for 的foreach 语法,关键字仍然是for:

float f[] = new float[10];
for(int i= 0; i <10;i++)  f[i] = rand.nextFloat();
for(float x: f)  System.out.println(x);

31. 无穷循环  while(true) 和 for(;;)

32. 用 break ;continue;跳出多层循环 使用  自定义标签Label:标签只能用在迭代语句(for,while)前

tag1:// tag1为代码内容
for (int i= 0; i <5;i++) {
    System.out.println("i= " + i + " ");
for (int j= 0; j <5;j++) {
        System.out.print("j=" + j+"  ");
if (j == 3)   break tag1;//语句一
    }
    System.out.println();
}

输出结果为:

i= 0

j= 0  j= 1 j= 2  j= 3 

若语句一为  break 则输出结果为:

i= 0

j= 0  j= 1 j= 2  j= 3 

i= 1

j= 0  j= 1 j= 2  j= 3 

i= 2

j= 0  j= 1 j= 2  j= 3 

i= 3

j= 0  j= 1 j= 2  j= 3 

i= 4

j= 0  j= 1 j= 2  j= 3 

33. 在java1.7后switch实现字符串比较的功能(之前仅支持int 、short 、byte 、char这些与整数类型兼容的类型)。实现的机制是:将字符串之间的比较转换为其哈希值的比较

34. int i = 0 + 'a';//获得 字母 a 的ASCII 码
System.out.print(i);

第 5 章  初始化与清理

35. 方法重载(Overload):方法名相同,以参数列表不同做区分(无法仅根据返回值[有无返回值,或返回值类型不同]来区分重载方法)

方法重写(Override):覆盖父类方法

36. 方法重载-类型提升,如果传入的数据参数(实参)类型小于方法中申明的参数类型,就会把实际数据类型自动提升。char 类型比较特殊,如果无法刚好找到接受char类型参数的方法,就会把char 直接提升至int型。

void f2(bytex) { printnb("f2(byte) ");}
void f2(short x){ printnb("f2(short) "); }
void f2(int x){ printnb("f2(int) "); }
void f2(long x){ printnb("f2(long) "); }

char x = 'x'; f2(x);// 输出 f2(int) 

37. 如果类中定义了构造器,编译器就不会自动创建默认构造器。

38. 在构造器中调用其他构造器,使用 this(agr0,arg1);  只可调用一次,且必须将构造器调用置于最起始处。除构造器外,不能在类的其他方法中调用构造器。

39. ★★★★★ 清理:终结处理和垃圾回收p89

垃圾回收器只回收那些由new 分配的内存,其他方式获得的“特殊”内存区域无法自动释放:例如:调用本地方法(Java语言调用非Java代码,目前仅支持c/c++,但c/c++可调用其他语言代码)

finalize() 方法原理:垃圾回收器准备回收当前对象时,会先调用其 finalize()方法,可在该方法中做一些重要清理。应假定基类中finalize()也需要执行总要清理,需在方法末尾调用super.finalize();

Java对象可能不被垃圾回收(System.gc()强制执行终结动作,垃圾回收不保证一定会发生),只要程序没有濒临存储空间用完的那一刻,对象占用的空间就总也得不到释放。(因:垃圾回收本身也有开销)

引用计数(简单、速度慢、无法处理循环引用[对象应该被清理,但引用计数不为0]):当有引用连接至对象时,引用计数加1,当引用离开作用域或置为null时,引用计数减1,垃圾回收器在全部对象上遍历,发现引用计数为0,就释放其占有的空间。引用计数只用于说明垃圾回收原理, 未应用 于JVM

垃圾回收器一面回收空间,一面使堆中的对象紧促排列。

垃圾回收器如何工作:JVM采用一种“自适应”的垃圾回收技术

一、任何“活”的对象,一定能最终追溯到其存活在堆栈或静态存储区之中的引用,这个引用可以穿个数个对象层次。从堆栈和静态存储区开始遍历所有引用,就能找到所有“活的”对象。以下两种处理方法自适应:

二、停止-复制(stop and copy):暂停程序,将所有存活对象从当前堆复制到另一个堆,没有被复制的全是垃圾,新堆保持紧凑排列。缺点1:维护空间比实际需求多一倍。部分JVM从堆中分配较大块(以块为单位),复制动作在这些大块内存之间。缺点2:程序稳定后,垃圾较少,此方法仍需复制大量内存。为此,JVM会进行检查,若产生新垃圾较少,转入“标记-清扫”方式

三、标记-清扫(mark and sweep):找“活”对象时做标记,全部标记完成后,开始清理释放没有被标记的对象。不会发生复制动作,剩下空间不连续,JVM需要重新整理。

四、自适应方式:JVM进行监视,若所有对象稳定,垃圾回收器效率降低,切换到“标记-清理”方式,同样,JVM会监视该方式效果,若堆空间出现很多碎片,就会切换回“停止-复制”方式。

40. Java尽力保证所有变量在使用前得到初始化,局部变量不初始化使用会报错,类的基本类型成员如果没手动赋值,都为该类型的初始值,类的对象成员引用默认为null

41. 类中,变量定义的先后顺序决定了初始化的顺序。静态初始化只有首次生成这个类的实例,或者首次访问累的静态数据成员时,才发生。初始化的顺序是先静态成员,后非静态成员。

42. 静态初始化子句(静态块):显式静态初始化,只执行一次。

class Cups {
static Cup cup1;
static Cup cup2;
static {
cup1 = newCup(1);
cup2 = newCup(2);
  }
  Cups() {
print("Cups()");
  }
}

43. 实例初始化子句:与静态初始化子句类似,少了static关键字,在构造器之前执行,每实例化一个对象,执行一次。

class Cups {
  Cup cup1;
  Cup cup2;
  {
cup1 = newCup(1);
cup2 = newCup(2);
  }
  Cups() {
  }
}

44. 定义数组 int[] a1; int a2[]  都可以,优先第一种。定义时不允许制定数组大小p99,new 初始化数组时,数组大小可由变量确定:

int[] a;
Random rand = new Random(47);
a = new int[rand.nextInt(20)];

{1,2,3,4,5}这种初始化方式必须在创建数组的地方使用。

数组赋值为引用赋值a2=a1; 对a2的修改会改变a1。a1 、a2只是同一数组的别名。

45. 可变参数列表,放于参数列表的末尾:

static void f(int required, String... trailing) {
    String sss = trailing[0];
for (String s : trailing)
        System.out.print(s + "");

}
调用 f(2, "two","three"); 或   f(0);

static void f2 (String... trailing) {}

上面的方法 可以 f2()这样调用

46. 枚举类型 :enum 事实上是一个类,有自己的方法例如 静态方法values与非静态方法ordinal()、toString():

public enum Spiciness {
NOT, MILD,MEDIUM, HOT,FLAMING
}

for (Spiciness s : Spiciness.values())
    System.out.println(s + ", ordinal " + s.ordinal());

第 6 章  访问权限控制

47. 控制访问权限的原因:一、对调用者隐藏细节,只暴露出他们需要的部分,简化他们对类的理解。二、让类库设计者可以改进类的内部工作方式而不影响调用者之前代码的运行。

48. ★★★从最大权限到最小权限依次为:public 、protected 、包访问权(默认)、private 。protected也提供包访问权限,也就是相同包内的其他类可以访问protected元素

49. 一个.java文件为一个编译单元,至多只能有一个public类(可以没有,文件名随意,但不常用),该类名必须与文件名完全一致。

50. java包命名全小写,用反序Internet域名

51. 类 只能用 public 修饰或者,默认包访问权限。不能用private或protected。可以将所有构造器都设置为private,只在静态方法中创建并返回该类对象,从而阻止其他人创建该类的对象。(单例模式)

class Soup2 {

private static Soup2 ps1 = new Soup2();
private Soup2() { }
public static Soup2 access() {
return ps1;
    }

}

第 7 章  复用类

52. 代码复用的两种方式:组合(在新类中产生现有类的对象)、继承(新类继承现有类),一般应优先选择组合,只在确实必要时才使用继承,因组合更灵活。

53. 继承关键字:extends,子类调用基类方法用super.methodName();

54. 初始化过程:由基类à导出类à导出类依次调用 构造器初始化。Java 会自动在导出类的构造器中插入对基类默认构造器的调用(若基类没有默认构造器,则需手动在导出类的构造器起始位置用super显示调用的基类的某一个构造器)。

55. 清理动作一般置于finally 子句中,以预防异常的出现。清理方式与初始化方式相反,首先执行类的清理动作,其顺序与初始化时的顺序相反,然后调用基类的清理方法。

56. @Override注解:当需要覆写(重写)某个方法时,可以加这个注解,当意外重载了该方法而不是覆写时,编译器会生成错误提示。

57. 组合与继承:组合表达“has a”(有一个)的关系,继承表达“is a”(是一个)的关系

58. 静态方法BaseClass.staticMethod()也会被继承,也可以被覆写:BaseClass. staticMethod()、baseObject.staticMethod()、SubClass.staticMethod()、subObject. staticMethod()均可调用该静态方法。

59. 向上转型:将导出类对象作为基类对象使用,会默认进行向上转型。

60. ★★★final关键字:

final数据:对于基本类型,final使数值恒定不变,对于对象引用(数组也是),final使引用恒定不变,但对象自身可以被修改。

static final 的域(编译器常量)采用大写字母,下划线分割各单词。

static final int INT_5 = rand.nextInt(20);
// Can be compile-time constants:
private final int valueOne = 9;
// Cannot be compile-time constants:
private final int i4 = rand.nextInt(20);

INT_5编译之后就不能变,i4每个对象不一样,对象初始化之后不能变

必须在域的定义处或者每个构造器中对final域进行赋值,以保证其在使用前被初始化了。

final参数:无法修改参数(基本类型),无法修改引用的指向(对象引用),即无法new。

final方法此方法不会被覆写,确保在继承中使方法的行为保持不变。所有private方法隐含是final的,可以加final修饰,但无意义。private方法不能被覆盖,导出类可以写一个同名方法,但实际不是覆写,而是重新定义了一个方法。p143

final类:无法从final类继承

61. ★★★★★ 有继承时初始化顺序

class BaseClass {
private static int x1=            printInit("static BaseClass.x1 initialized");
private int i =printInit("BaseClass.i initialized");
    BaseClass() {
print("BaseClass constructor");
    }
static int printInit(String s) {
print(s);  return 47;
    }
}
public class SubClass extendsBaseClass {
private static int x2=            printInit("static SubClass.x2 initialized");
private int k =printInit("SubClass.k initialized");
public SubClass() {
print("SubClass constructor");
    }
public static void main(String[] args) {
print("main start:");
        SubClass b = new SubClass();
    }
} /* Output:
static BaseClass.x1 initialized
//先静态
static SubClass.x2 initialized
main start:
BaseClass.i initialized
BaseClass constructor//先基类构造器
SubClass.k initialized//
后导出类域初始化
SubClass constructor
*/

第 8 章  多态

62. ★★面向对象三大基本特征:封装(数据抽象)、继承、多态

封装:通过合并特征和行为来创建得数据类型,通过“私有化”将实现隐藏,把实现与接口分离。

继承:继承允许将对象视为其本身类型或其基类类型来处理。

多态:(后期绑定、动态绑定、运行时绑定)多态通过方法动态绑定来实现。通过分离“做什么”和“怎么做”,将接口和实现分离开来。多态的作用是消除类型之间的耦合关系。

63. 绑定:将方法调用同方法主体关联起来

前期绑定:在程序执行前进行绑定(由编译器和链接程序实现),例如C语言只有前期绑定。

后期绑定:在运行的时候根据对象的类型进行绑定

64. ★★★只有非private方法才可被覆盖:

public class PrivateOverride {
public static void main(String[] args) {
        PrivateOverride po = new Derived();
        po.f();
        ((Derived) po).f();
    }
private void f() {
print("private f()");
    }
}
class Derived extends PrivateOverride {
public void f() {
print("public f()");
    }
}//输出:  private f()   public f()

private方法默认是final的,在导出类中是屏蔽的,Derived中的f()方法是一个全新的方法,不是重写,不具多态性

65. 域与静态方法不具多态性,即对象P实际是导出类对象,如果用基类对象引用它,调用域/静态方法时,调用的是基类的域/静态方法,而不是导出类同名的域/静态态方法,不具多态性。只有普通非private方法才具多态性。p156

66. Java中清理需定义dispose方法,清理顺序与域的定义顺序相反,导出类dispose方法中需在末尾调用super.dispose()执行基类清理

67. ★★★基类构造器内部的多态调用:

class Glyph {
    Glyph() {
print("Glyph() before draw()");
        draw();
print("Glyph() after draw()");
    }
void draw() {        print("Glyph.draw()");    }
}
class RoundGlyph extends Glyph {
private int radius = 1;
    RoundGlyph(int r) {
radius = r;
print("RoundGlyph.RoundGlyph(), radius = " + radius);
    }
void draw() {print("RoundGlyph.draw(), radius = " + radius); }
}
public class PolyConstructors {
public static void main(String[] args) {   new RoundGlyph(5);  }
}//输出:

Glyph()before draw()

RoundGlyph.draw(), radius= 0  //此时还没有执行 下划线的初始化语句

Glyph() afterdraw()

RoundGlyph.RoundGlyph(),radius = 5

初始化实际过程:1.分配给对象的存储空间初始化为二进制0

2.基类域初始化-》基类构造器-》导出类域初始化-》导出类构造器

3.因此在基类构造器中调用导出类的方法时,导出类的域还没有被初始化,为0

68. 协变返回类型:导出类中被覆盖的方法可以返回基类方法的返回类型的某种导出类型。

69. 向下转型必须强制转换,若该类型确实不是要转换的类型,报ClassCastException异常。

第 9 章   接口

70. 抽象方法:仅有申明而没有方法体的方法是抽象方法 abstract void f();

71. 抽象类(可以有部分实现):包含抽象方法的类。如果一个类包含一个或多个抽象方法,则该类必须是抽象的。反之,抽象类可以没有抽象方法(只用于阻止创建该类对象),可以有非抽象方法。无法产生抽象类的对象。如果继承一个抽象类,必须实现基类中的所有抽象方法,否则改导出类也必须声明为抽象类。

72. 接口:所有方法只有声明,没有方法体。完全抽象,只提供形式,没有具体实现。

接口可以包含域,但是这些域隐式地是public staticfinal 的,且必须定义时初始化。接口所有方法都是public的,即便没有显示使用public修饰。

73. 继承类用关键字entends,实现接口用关键字 implements。

74. 策略设计模式(接口常见用法:创建一个更够根据所传递参数对象的不同而具有不同行为的方法。这个方法包含索要执行的算法中固定不变的部分,而“策略”包含变化的部分。策略就是传递进去的参数对象。下面示例中2中不同策略应用到了String类型上:

class Processor {
    Object process(Object input) {        return input;    }
}
class Upcase extends Processor {
 String process(Object input) {return ((String) input).toUpperCase();}
}
class Splitter extends Processor {
    String process(Object input) {
return Arrays.toString(((String) input).split(" "));
    }
}
public class Apply {
public static String s ="Disagreement with is by definition incorrect";
public static void process(Processor p, Object s) {
 //执行不变部分
        print(p.process(s)); 
 }
public static void main(String[] args) {
process(new Upcase(), s);
process(new Splitter(), s);
    }
}

75. 接口实现完全解耦:如果一个方法操作的是类而非接口,那么只能使用这个类及其子类。但是方法参数传入的是接口的话,可以放宽这个限制。使用接口可以编写出可复用性更强的代码。

76. 适配器设计模式:经常碰到无法修改想要使用的类,类库一般只可以使用,无法修改。适配器中的代码接受所拥有的接口/类(生成对象作为域,或继承它),并产生(实现)所需要的接口。p177

77. Java多继承:继承至多一个类,实现多个接口。可以向上转型为基类和每个接口。

class Hero extends ActionCharacter implements CanFight, CanSwim, CanFly {
public void swim() {}
public void fly() {}
}

78. 接口多继承语法:接口可以用extends 扩展多个接口

interface Vampire extends DangerousMonster, Lethal {
void drinkBlood();
}

79. 接口里面可以嵌套接口,其访问权限可以有包权限、public权限,不能private。

嵌套在类A里面的接口I可以用private定义,可以在A里面实现I接口。实现某个接口时,无需实现其内部的嵌套接口

80. 恰当的设计原则是优先选择类而不是接口

第 10 章 内部类

81. 内部类允许把一些逻辑相关的类组织在一起,控制位于类内部的类的可视性,使用内部类写出的代码更加优雅清晰。

82. 内部类(非static)拥有其外部类所有元素(即便是private)的访问权:当外围类对象创建了一个内部类对象时,内部类对象会捕获到执行外围类对象的引用,通过这个引用来访问外围类成员。

83. 在内部类中,使用外部类类名.this 来获取外部类对象的引用,this 指的是内部类对象引用。

84. 内部类调用:普通内部类:publicclass A {  public class B {     } }; 

用外部类实例调用:A a= new A();  A.B ab = a.new B();

嵌套类(静态内部类):public class A {  public staticclass B {     }  };

调用:A.B ab = new A.B();

85.内部类与向上转型p194:当将内部类向上转型为其基类,尤其转换为一个接口的时候,内部类就有了用武之地,因为内部类(某个接口的实现)更够完全不可见,并且不可用。所得到的只是一个指向基类或者接口的引用,很方便的隐藏了实现细节

86.可以在方法里面或者任意作用域内定义内部类(其有效范围与局部变量有效范围类似。用途:用内部类实现了某个接口,创建并返回该类对象(向上转型为接口)、定义一个不想公开的类来辅助解决复杂问题。

87.匿名(内部)类:contents方法表示“创建一个继承自Contents的匿名类的对象”,通过new 表达式返回的引用被自动向上转型为对接口Contents的引用。

public interface Contents {
int value();
}
public class Parcel7 {
public static void main(String[] args) {
        Parcel7 p = new Parcel7();      Contents c = p.contents();
    }
    public Contents contents() {
return new Contents() { // Insert a class definition
private int i = 11;
public int value() {
return i;
            }
        };// Semicolon required in this case
}
}

88.匿名类不能有构造器。如果匿名类基类构造器有参数,则定义匿名类时需传递合适参数:

public class Wrapping {
private int i;
public Wrapping(int x) { i = x; }
public int value() { return i; }
}
使用:
public Wrapping wrapping(int x) {
// Base constructor call:
return new Wrapping(x) { // Pass constructor argument.
public int value() {
return super.value() * 47;
    }
  }; // Semicolon required
}

89. ★★匿名内部类使用在其外部定义的对象时,编译器要求其对象引用必须是final的。

90. 匿名内部类只能扩展一个类,或者实现一个接口,不可兼得。

91. 普通内部类与嵌套类(static)区别:

Ø  要创建嵌套类的对象,并不需要其外围类对象

Ø  不能从嵌套类的对象中访问非静态的外围类对象

Ø  普通内部类不能有static方法与static字段,嵌套类可以有

92. 嵌套类可以置于接口里面(想要创建公共代码,可以被接口的不同实现所公用)。接口里面的任何类默认都是public static 的,内部类可实现外围接口。

93. 内部类标识符:编译嵌套类,会生成类似TestBed$Tester.class的程序,可用javaTestBed$Tester 来运行。匿名类用数字做标识符,会产生TestBed$1.class文件

94. ★★★为什么需要内部类?p204

Ø  实现“多重继承”:可以继承多个具体的或抽象的类。如果一个类classA,重写了父类baseClass中的方法f(),又需要实现接口I,但I中有方法f(),baseClass中的f()与接口中的f()方法毫无关系,就必须使用内部类。在单个外围类中,可以以不同的方式实现同一个接口(继承同一个类),灵活。

Ø  可提供闭包功能闭包:是一个可调用的对象,它记录了来自于创建它的作用域的一些信息。内部类是面向对象的闭包。Java实现闭包(内部类+接口):让内部类实现通用接口,然后将内部类对象向上转型为接口类型

Ø  通过多继承可实现“控制框架”。

95.内部类可以被继承:

class WithInner {    class Inner {    }  }
public class InheritInner extends WithInner.Inner {
//! InheritInner() {} // Won't compile
InheritInner(WithInner wi) {//必须定义这么一个构造器
wi.super();
    }
public static void main(String[] args) {
        WithInner wi = new WithInner();
        InheritInner ii = new InheritInner(wi);
    }
}

96. 内部类无法被覆盖(当ClassA继承了某个外围类ClassB的时候,ClassA又定一个与ClassB里面同名的一个内部类,这两个类完全独立,命名空间不同)。但新定义的内部类可以明确继承某个内部类,extends ClassB.InnerClassC

97. 局部内部类与匿名内部类:

Ø  局部内部类可以有构造器(多个),而匿名内部类只能使用初始化块

Ø  用局部内部类可以得到多个对象,而匿名内部类只能得到一个内部类的对象。

第 11 章 持有对象

98. Java基本容器:加框的最常用,Vector、Hashtable、Stack已过时,不应使用。

Collection

接口

List接口

ArrayList随机访问快,插入移除慢

LinkedList随机访问慢,插入移除快,添加了可以用作栈、队列、双端队列的方法。实现Deque接口(Deque实现Queue接口)

Set接口 每个值只保存一个对象,不能存在重复

HashSet[散列函数-无序]最快

TreeSet[存储在红-黑树中,有序排列]

LinkedHashSet[按添加顺序排序]使用了散列

Queue接口

Map

接口

将某些对象与其他对象关联起来的关联数组

HashMap[键无序]最快

TreeMap[键排序]

LinkedHashMap[按添加顺序]保留了HashMap查询速度

99. @SuppressWarnings("unchecked");此注解放在方法首部会抑制“不受检查的异常”警告

100. 最基本最可靠的容器 ArrayList 有add(),get(int index),size()方法,ArrayList apples = new ArrayList();可以添加所有对象(Object),而ArrayList<Apple> apples = new ArrayList<>();只可以添加Apply或其子类(向上转型),类型安全。

101.List<Integer> list = Arrays.asList(16,17, 18, 19, 20);// Arrays.asList()可以接受一个数组或者可变参数,将其转换为一个List对象,但底层为数组实现,无法调整大小

List<Snow> snow4 = Arrays.<Snow>asList(new Light(), new Heavy());

显示指定Arrays.asList需要转换的List的类型

102.List获取子列表:List<Pet> sub = pets.subList(fromIndex, toIndex);//包含formIndex、不包含toIndex。sub元素排序后,源列表pets元素顺序跟着改变。

103.List<Pet> pets =……;

Object[] o = pets.toArray();//默认toArray()方法返回 object[]
Pet[] pa = pets.toArray(new Pet[0]);//指定类型,如果参数数组太小,会自动创建合适大小的数组
print("23: " + pa[3].id());

104. 使用迭代器的好处:能够将遍历序列的操作与序列底层的结构(List或Set等)分离。迭代器统一了对容器的访问方式

105. Iterator可以移除next()方法产生的最后一个元素,在调用remove()方法之前必须点调用next()方法。

106. ListIterator是Iterator的子类型,只能用于List类,可以双向移动。

107. Queue接口中的6个方法区别

获取队首元素

peek(),队为空时返回null

element()抛异常

入队

boolean offer(E e),队列达到最大容量时,插入失败返回false

boolean add(E e) 抛异常

出队

poll();队为空时返回null

remove();抛异常

108. Deque接口(继承自Queue)中的方法

 

Deque

等价于Queue中的方法

可以当栈使用

Insert类方法

addFirst

空间不够,插入失败抛异常

push

offerFirst

插入失败返回false

 

addLast

add

 

offerLast

offer

 

Remove类方法

removeFirst

remove

pop

pollFirst

poll

 

removeLast

 

 

pollLast

 

 

Examine类方法

getFirst

element

 

peekFirst

peek

peek

getLast

 

 

peekLast

 

 

109. Set具有与Collection完全完全一样的接口(所有方法相同),没有任何额外功能。实际上Set就是Collection,只是行为不同,Set不保存重复的元素。

110. String.CASE_INSENSITIVE_ORDER这个比较器可以进行忽略大小写比较

111. 多维Map,值可以为List:Map<Person,List<? extends Pet>>

<?extends E>上限限制,只有E或者E的子类可以使用

<?super E>下限限制,只有E或者E的父类可以使用

112.实现Iterable接口的类可用于Foreach语法implements Iterable<T>.数组可以用于Foreach语法,但不实现Iterable

113.Integer[] ia = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
List<Integer> list1 = 
new ArrayList<Integer>(Arrays.asList(ia));

List<Integer> list2 =Arrays.asList(ia);

对list1的修改不会影响ia,而对list2的修改会影响到 ia

114.java容器UML图如下:

Vector Stack Hashtable已过时


Vector Stack Hashtable已过时


第 12 章 通过异常处理错误

115.异常类型的根类型是Throwable 类,Exception类继承自Throwable类,创建自定义异常可继承Exception类,自定义异常类的类名就可以提供足够多的信息,大多数情况classSimpleException extends Exception { }这种定义已经够用

116.Throwable有两个子类:Error、Exception。Exception是可以被抛出的基本类型。Error用来表示编译时和系统错误,通常不用关心。RuntimeException是“不受检查的异常”(编译器不强制用try catch 、或进行方法抛异常说明)

117.使用java.util.logging.Logger类记录异常

Logger logger =  Logger.getLogger("LoggingExceptions2");
try {    throw new NullPointerException();
} catch (NullPointerException e) {    logException(e);}
static void logException(Exception e) {
  StringWriter trace = new StringWriter();
    e.printStackTrace(new PrintWriter(trace));
logger.severe(trace.toString());//严重错误 输出调用堆栈
}

118.异常说明:public void f() throws SimpleException, SimpleException2 {}

119.将catch(Exception e){}放在处理程序列表末尾可以捕获所有异常。

120.在catch语句中重新抛出异常时,throw (Exception) e.fillInStackTrace();// 调用fillInStackTrace方法的那一行成为异常新发地。

121.异常链:在捕获一个异常之后抛出另一个异常,并且希望把原始异常的信息保留下来。可以使用initCase方法传入原始异常,或使用带(Throwable case)参数的构造器构造异常(Error、Exception、RuntimeException支持)。

Ø  DynamicFieldsException dfe = new DynamicFieldsException();
dfe.initCause(new NullPointerException());

Ø  catch (NoSuchFieldExceptione) { // Use constructor that takes"cause":
throw new RuntimeException(e);//将“被检查异常”转换为“不检查异常”
}

122.finally子句:无论是否发生异常都执行里面的代码。finally用于清理内存之外的资源:已经打开的文件或网络连接,在屏幕上画的图,或外部开关等。

123.finally子句异常丢失:finally子句里面使用return语句或者 finally里面产生了异常,就会丢失try{}块里面的未处理异常p269。finally里面return的返回值,会覆盖掉try里面return的返回值

124.异常的限制:当覆盖方法的时候,只能抛出(可以不抛出异常)在基类方法的异常说明里列出的那些异常(或其子异常)。异常限制对构造器的覆盖不起作用(导出类构造器可以比基类多抛异常,不能少抛,因为需调用基类构造器)

125. Java获取当前路径:System.getProperty("user.dir")

第 13 章 字符串

126. ★★String对象是不可变的

127. String的+和+=是Java中仅有的两个重载过的操作符,java不允许重载操作符。

128. 多字符串相加编译器会自动使用StringBuilder优化处理,但是在循环中累加字符串时,需要自己创建一个StringBuilder对象,否则会产生一大堆需要垃圾回收器清理的对象,影响程序效率。

129.★★★StringBuffer是线程安全的,开销大。StringBuilder非线程安全,更快。编程证明如下:

public class Test {
public static void main(String[] args) {
        StringBuffer sbf = new StringBuffer();
        StringBuilder sb = new StringBuilder();        
for (int i = 0; i <10; i++) {//10个线程
new Thread(new TestThread(sbf, sb)).start();
        }}}
class TestThread implements Runnable {
    StringBuffer sbf;    StringBuilder sb;
    TestThread(StringBuffer sbf, StringBuilder sb) {
this.sb = sb;    this.sbf = sbf;
    }
public void run() {
for (int i = 0; i <100; i++) {
try {    Thread.sleep(10);
            } catch (InterruptedException e) {   e.printStackTrace();}
sb.append("1");sbf.append("1");
            System.out.println(sb.length() + "/" + sbf.length());
        }}}
输出结果:…… 926/998927/999928/1000

130.JDK自带反编译工具javap -c ClassName 可以生产JVM字节码

131. 格式化输出format()方法可用于PrintStream和PrintWriter对象,包括System.out(PrintStream对象),printf()方法等同于format()方法。

System.out.format("Row 1: [%d %f]\n", x, y);

132.Formatter f = new Formatter(System.out);//f.format(……)可输出到控制台,Formatter还可以输出到File、OutputStream、PrintStream等

133. 格式化说明符f.format("%-15.15s %5d %10.2f\n", name, qty, price);//默认右对齐,-表示左对齐。.号前面的数字表示最小宽度,.点号后面的数字用于字符串时表示最大宽度,用于浮点数时,表示小数点位数。

134. String.format("(t%d, q%d)%s", transactionID,queryID, message);//返回字符串,内部调用Formatter

135.★★★正则表达式

js中的正则表达式可以以/开始,以/结束,反斜杠\不需要转义,java中需要转义(java中\\表示正则表达式中的\ ,java中的\\\\表示正则表达式中的普通字符\)

字符:

B

指定字符B

\f

换页符

\\

反斜线符号

\xhh

十六进制值为0xhh的字符

\t

制表符tab

\uhhhh

十六进制为0xhhhh的Unicode字符

\n

换行符

\e

匹配转义字符\u001B

\r

回车符

 

 

字符类:

.

任意字符

[abc]

包含a,b,和c的任何字符,同[a|b|c]

[^abc]

除了a,b,c之外的字符【否定】

[a-zA-z]

从a到z,或A到Z的任何字符【范围】

[abc[hij]]

任意a,b,c,h,i,j字符【合并】

[a-z&&[hij]]

任意h,i,或j【交】

\s

空白符(空格,tab,换行、回车、换页)

\S

非空白符[^\s]

\d

一位数字 [0-9]

\D

非数字[^0-9]

\w

词字符[a-zA-z0-9]

\W

非词字符[^\w]

逻辑操作符

XY

Y在X后面

X|Y

X或Y,例如  chenzhifu|haiyan  匹配任意一个单词

(X)

捕获组,在表达式中用\i引用第i个捕获组

组号为0表示整个表达式,组号为1表示被第一对括号括起来的组:

A(B(C))D  组0为:ABCD,组1为BC,组2为C

边界匹配符

^

一行的起始

$

一行的结束

\b

词的边界

\B

非词的边界

\G

前一个匹配结束

 

 

量词

贪婪型

(最大匹配)

勉强型

(最小匹配)

占有型(仅Java)

(完全匹配)

 

X?

X??

X?+

0个或1个X

X*

X*?

X*+

0个或多个X

X+

X+?

X++

1个或多个X

X{n}

X{n}?

X{n}+

恰好出现n次

X{n,}

X{n,}?

X{n,}+

至少n次

X{n,m}

X{n,m}?

X{n,m}+

至少n次,至多m次

String str="<aaa<bbb>ccc<ddd>eee>";
System.out.println(str.replaceAll("<.+>","####"));
System.out.println(str.replaceAll("<.+?>","####"));
System.out.println(str.replaceAll("<.++>","####"));
System.out.println(str.replaceAll("<d++>","####"));

输出为:

####
####ccc####eee>
<aaa<bbb>ccc<ddd>eee> // .++ 已经匹配到结尾了,没有字符来匹配 >
<aaa<bbb>ccc####eee>

模式标记:

PatternPattern.compile(String regex,int flag);//其中flag来自以下常量:

例如【(?m)】中文方括号里面的内容加入到正则表达式首部,表示多行匹配

Pattern.CANON_EQ

当两个字符的完全规范分解相匹配时,就认为是匹配的。默认不考虑规范的等价性,如果指定这个标志,a\u030A就会匹配字符串\u00E5【理解:Unicode中有不同的编码表示相同字符】

Pattern.CASE_INSENSITIVE(?i)

大小写不敏感匹配(适合于ASCII),结合Pattern.UNICODE_CASE时,开启基于Unicode的大小写不敏感匹配

Pattern.COMMENTS(?x)

忽略空白符,以#开头直到行尾的注释被忽略

Pattern.DOTALL(?s)

.匹配所有字符,包括行终结符。默认不包括行终结符

Pattern.MULTILINE(?m)

^和$默认仅匹配完整字符串的开始与结束。用该标记后,^和$还可以表示一行的开始与结束。

Pattern.UNICODE_CASE(?u)

结合Pattern.CASE_INSENSITIVE使用

pattern.UNIX_LINES(?d)

在.、^和$行为中,只识别行终结符\n

常用示例:

整数:(-|\\+)?\\d+

136.PatternMatcher 的使用:

Pattern p = Pattern.compile("(abc)+");
Matcher m = p.matcher("abcabcabcdefabc");
while (m.find()) {
 print("Match \"" + m.group() + 
 "\" at positions " +m.start()+"-"+(m.end()- 1));
}

输出:Match "abcabcabc" atpositions 0-8

Match "abc" atpositions 12-14

★find()方法可以在输入字符串的任意位置定位正则表达式,而lookingAt()方法只有在正则表达式与输入字符串的最开始处就匹配时才会成功。matches()只有整个输入都匹配时正则表达式时才会成功(开始处匹配,结尾处也要匹配)

第 14 章 类型信息

137. 运行时识别对象和类的信息的两种方式:

Ø  RTTI(Run-Time Type Identification):它假定在编译时已经知道了所有类型

Ø  反射:允许在运行时发现和使用类的信息

138. 获取Class对象:

Ø  Class classA = Class.forName("typeinfo.Gum");

Ø  Class classB = Gum.class;//类字面常量

.class可用于基本类型,接口或数组,boolean.class等价于Boolean.Type……

Ø  Class classC = new Gum().getClass();

print((classA == classB)+ " "+(classC == classB)+" "+(classC == classA));

输出:true true true

139. Class classB = Gum.class; obj = classB.newInstance();//使用newInstance()创建对象的类,必须有默认构造器

140. Class 对象的cast()方法转型语法:

class Building {}class House extends Building {}
Building b =
new House();
Class<House> houseType = House.
class;
House h = houseType.cast(b);
h = (House)b;
// ... or just do this. 

141. instanceof关键字:对象类型检测if(x instanceof Dog){……}

142. 动态 instanceof  Class对象的isInstance(Object obj)方法提供了一种动态地测试对象的途径

143. 利用反射调用方法:

callHiddenMethod(Object a, String methodName) throws Exception {
  Method g = a.getClass().getDeclaredMethod(methodName);
  g.setAccessible(true);
  g.invoke(a);

}

144. 反射可以修改private字段,不可修改final字段:

WithPrivateFinalField pf = new WithPrivateFinalField();
Field f = pf.getClass().getDeclaredField("i");
f.setAccessible(true);
f.setInt(pf, 47);

第 15 章 泛型

145. 元组(tuple)实现多值返回:

public class TwoTuple<A, B> {
public final A first;
public final B second;
public TwoTuple(A a, B b) {        first = a;second = b;    }
public String toString() {    return "(" + first + ", " + second + ")";    }
}
static TwoTuple<String, Integer> f() {
// Autoboxing converts the int to Integer:
return new TwoTuple<String, Integer>("hi", 47);
}

146. 泛型方法非泛型类中可以有泛型方法,将泛型参数列表置于返回值前:

public <T, V>void f(T x, V b) {
    System.out.println(x.getClass().getName());
}

147. 在泛型中创建数组:Array.newInstance(Class<?> componentType, int length),该函数返回Object,需强制转换为 T[] 数组。

publicGenericArrayWithTypeToken(Class<T> type, int sz) {

        T[] array = (T[]) Array.newInstance(type, sz);

}

148. 不能创建泛型数组:T[] array = new T[SIZE];         // Error

在任何想要创建泛型数组的地方使用ArrayList

还可以创建一个Object数组,再转型array = (T[]) new Object[SIZE];

149. 泛型边界(子类型边界),extends 指定限定T继承的类,多限定用& 符号,先类后接口,单个类多个接口

class ColoredDimension<T extends Dimension&HasColor>{}//HasColor是接口

150. 通配符?被限制为单一边界(后面不能使用&符号)p389

class Fruit {}
class Apple extendsFruit {}

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

flist可以持有引用,但是不能添加任何对象

ArrayList<Apple>可以向上转型为List<? extends Fruit>,但是不可以向上转型为List<Fruit>

151. 泛型边界(超类型边界):List<? superApple> apples

152. 泛型问题

1.任何基本类型(int等)都不能作为参数类型,可用其包装类型(Integer等)

2.一个类不能实现同一个泛型接口的两种变体(由于擦除的原因,这两个变体会成为相同的接口)

3.受检查的容器:  List<Dog> dogs2 = Collections.checkedList(newArrayList<Dog>(), Dog.class);向dogs2中插入对象时,如果不是Dog 会抛出ClassCastException异常

第 16 章数组

153. 数组是一种效率最高的存储和随机访问对象引用序列的方式。

154. 数组只读成员length是数组对象唯一一个可以访问的字段或方法。

155. 对象数组里面保存的是引用(指向在堆中的对象),基本类型数组里直接保存值。

156. Java数组可以直接当返回值

157. 数组定义int[] b = {1,2,3};int[] a = new int[5];

多维数组:int[][]c = { {1,2,3},{4,5,6} }; int[][] d = new int[2][3];

多维数组输出:System.out.println(Arrays.deepToString(c));

158. System.arraycopy(Object src,int srcPos,Object dest, int destPos, intlength)可以复制数组,对于对象数组,只是引用的复制(浅复制)

159. Arrays.equals(a1, a2)可比较两个数组是否相同(内容比较,调用每个元素的equals方法比较)

160. 使对象数组可以排序的方法:<1>.实现java.lang.Comparable接口 <2>.创建一个实现Comparator接口的类,并传递该类的对象作为Array.sort(a,new CompTypeComparator())的第二个参数。

161. 反转自然排序:Arrays.sort(a, Collections.reverseOrder());

162. Java基本库中的排序算法:对于基本类型采用“快速排序”,针对对象采用“稳定归并排序”

163. 数组查找:(前提是待查找数组必须已经排好序)

int location= Arrays.binarySearch(array0, value);

如果找到,location>=0 ,否则带插入点位置可由( -location- 1 )式计算。

164. 数组与容器,优先使用容器。只有在性能成为问题的时候,才是用数组提高效率。

第 17 章容器深入研究

165.

166. 简化后:

167. Arrays.asList产生的List元素可以修改,结构不能修改(不支持retainAll/removeAll/clear/add/addAll/remove,抛UnsupportedOperationException异常),因为它基于一个固定大小的数组,仅支持那些不会改变数组大小的操作。

★★★Collections.unmodifiableList( new ArrayList<String>(list))产生的List元素不能修改(不支持set方法),结构也不能修改。对于Map与Set有类似的方法。

168. ★★★Collection或者Map自动同步控制:对于Map与Set有类似的方法。

List<String>list = Collections.synchronizedList(new ArrayList<String>());

169. Set,HashSet最快最常用。Set不保存重复元素,加入Set 的元素必须定义equals()方法以确保对象的唯一性,Set接口不保证维护元素次序

HashSet*

速度快,加入HashSet的元素必须定义hashCode()方法。可以放一个null

LinkedHashSet

具有HashSet速度,内部使用链表维护元素按插入顺序排列。元素必须定义hashCode()方法。可以放一个null

TreeSet

保存次序的Set,底层为树结构。元素必须实现Comparable接口。不可以放null

170. SortedSet<String> sortedSet = new TreeSet<String>();

TreeSet 是 SortedSet接口的唯一实现。

171. 队列

队列

Queue< String> queue= new LinkedList<String>();

使用offer pollpeek方法操作

优先级队列

PriorityQueue<ToDoItem> toDoList = new  PriorityQueue<ToDoItem>();//ToDoItem必须实现Comparable<ToDoItem>接口,该列表的排序顺序根据Comparable接口进行控制。

双端队列

Dueue< String> queue= new LinkedList<String>();//使用offerFirst/offerLast pollFirst/pollLast peekFirst/peekLast方法操作

 

图1队列 UML图

172. Map :HashMap最快最常用。任何键都必须具有equals()方法,如果是散列Map,那么必须具有 恰当的hashCode()方法,如果是TreeMap,必须实现Comparable接口

HashMap*

基于散列表实现,非线程安全

LinkedHashMap

迭代遍历时的顺序是插入顺序或者最近最少使用(LRU)顺序

// Least-recently-used order:
        LinkedHashMap linkedMap =new LinkedHashMap<Integer,String>

accessOrder参数为true表示使用LRU顺序,长时间没访问过的键值对,放在了最前面。

TreeMap

基于红黑树实现,查看“键”或“键值对”时,会被排序

WeakHashMap

弱键映射,如果映射之外没有引用指向某个“键”,则此“键”可被垃圾回收期回收。

ConcurrentHashMap

代替Hashtable线程安全

IdentityHashMap

使用==代替equals()对“键”进行比较的散列映射

173. 散列与散列吗:Object中的hashCode()使用对象的地址计算散列码,equals() 也是比较对象的地址。正确的equals()方法应满足:

1)自反性:x.equals(x)必须返回true

2)对称性:y.equals(x)等于x.equals(y)

3)传递性:若x.equals(y)=true,y.equals(z)=true则x.equals(z)应为true

4)一致性:如果对象中用于等价比较的信息没有变,无论条用x.equals(y)多少次,x.equals(y)要么一直是true,要么一直是false

5)对于任意非null值x.x.equals(null)必须是false

174. 通用hashCode()的计算方法,P496

175. 持有引用:java.lang.ref包的UML图如下:

如果想继续持有对某个对象的引用,希望以后还能够访问到该对象,但是也希望能够允许垃圾回收器释放,这时应该使用Reference对象。

SoftReference、WeakReference、PhantomReference由强到弱排列,对应不同级别的“可获得性”。

176. BitSet高效存储“开/关”信息,最小容量是64位,会动态增长:(EnumSet是更好的选择,因为它允许按照名字而不是数字位的位置进行操作)

BitSet bb = newBitSet();
for(int i= 7; i >= 0;i--){

if(i%2 != 0)    bb.set(i);
else    bb.clear(i);

}

第 18 章Java I/O 系统

177. File类:public String[] list(FilenameFilterfilter)方法可以根据FilenameFilter实现列出文件的过滤:

class DirFilter implements FilenameFilter {
private Pattern pattern;
public DirFilter(String regex) {
pattern = Pattern.compile(regex);
  }
public boolean accept(File dir, Stringname) {
return pattern.matcher(name).matches();
  }
}

则list =  new File(".").list(newDirFilter("[^.]+.iml"));可列出以.iml结尾的文件列表。匿名内部类就是好,改写后如下:

final String regex ="[^.]+.iml";
String[] list =
new File(".").list(
newFilenameFilter() {
privatePattern pattern = Pattern.compile(regex);
@Override
publicboolean accept(File dir, String name) {
returnpattern.matcher(name).matches();
            }
        });

178. 输入输出:输入输出是针对计算机程序而言的,任何自InputStream或Reader派生而来的类都有read()基本方法读取单个字节或字节数组,任何自OutputStream或Writer派生而来的类都有write()方法写单个字节或字节数组。

179. java.io包UML类图:

180. PrintStream未完全国际化,不能以平台无关的方式处理换行动作。而PrintWriter没有此问题。设计Reader和Writer继承层次结构主要是为了国际化,支持16位的Unicode,老的IO流层次结构只支持8位字节流。

181. InputStream/OutputStream是面向字节形式的IO,而Reader/Writer则是面向字符的IO,并且兼容Unicode

182. InputStreamReader和OutputStreamWriter为适配器类,可分别把InputStream/OutputStream转化为Reader/Writer

183. 自我独立的类RandomAccessFile 适用于由大小已知的记录组成的文件,在jdk1.4中,该类大部分功能被nio存储映射文件所取代

184. IO经典使用方式:

1.缓冲输入文件:

String filename = "F:\\BufferedInputFile.java";

BufferedReader in = new BufferedReadernew FileReader(filename));
String s;StringBuilder sb =
new StringBuilder();
while((s =in.readLine())!= null)  sb.append(s + "\n");
in.close();
System.out.print(sb.toString());

2.从内存(String)输入:

StringReader in = new StringReader("IDEA_Projects嘻嘻哈哈\r\neyInput6513dsaf");
intc;  while((c =in.read()) != -1)  System.out.print((char)c);

3.基本文件输出:

PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter("BasicFileOutput.out")));

//或者直接使用PrintWriter out = new PrintWriter("BasicFileOutput.out");
out.println("
文本文件内容~"); out.close();

4.存储和恢复数据(可跨平台)

DataOutputStream out = new DataOutputStream(
new BufferedOutputStream(new FileOutputStream("Data.txt")));
out.writeDouble(3.14159);
out.writeUTF("That was pi");//以UTF-8编码,才可以跨平台
out.close();
DataInputStream in = new DataInputStream(
new BufferedInputStream(new FileInputStream("Data.txt")));
System.out.println(in.readDouble());
System.out.println(in.readUTF());
in.close();

5.读写随机访问文件:(应使用nio内存映射文件)

staticString file ="rtest.dat";

RandomAccessFile rf= new RandomAccessFile(file, "rw");
for(int i = 0; i <7; i++)  rf.writeDouble(i*1.414);
rf.writeUTF("The end of the file");rf.close();display();
rf = new RandomAccessFile(file, "rw");
rf.seek(5*8);//单位byte,Double占8字节(修改第6个Double值)
rf.writeDouble(47.0001);rf.close();

voiddisplay() throws IOException{//读取
  RandomAccessFile rf = new RandomAccessFile(file,"r");
for(int i = 0; i <7; i++)

System.out.println("Value " + i + ":" + rf.readDouble());
  System.out.println(rf.readUTF());
  rf.close();
}

6.读取二进制文件:

public static byte[] read(FilebFile) throws IOException{
  BufferedInputStream bf = new BufferedInputStream(

new FileInputStream(bFile));
try {   byte[] data = new byte[bf.available()];
    bf.read(data);    return data;

  } finally {    bf.close(); }
}

185. Java标准I/O,System.in,System.out,System.err

利用System.in(InputStream对象,没有ReadLine方法)读取行数据:

BufferedReader stdin = new BufferedReader(
new InputStreamReader(System.in));
String s;
while((s = stdin.readLine()) != null && s.length()!= 0)
  System.out.println(s);
// An empty line or Ctrl-Z terminates the program

186. 标准IO重定向:

PrintStream console = System.out;
BufferedInputStream in = new BufferedInputStream(
new FileInputStream("F:\\Redirecting.txt"));
PrintStream out = new PrintStream(
new BufferedOutputStream(
new FileOutputStream("test.out")));
System.setIn(in);  System.setOut(out);      System.setErr(out);
BufferedReader br = new BufferedReader(

new InputStreamReader(System.in));
String s;
while((s = br.readLine()) != null)
  System.out.println(s);
out.close(); // Remember this!
System.setOut(console);

187. NIO(新IO),jdk1.4的java.nio.*包进入了新的Java I/O 类库,其目的在于提高速度,但旧I/O也已经使用nio重新实现过,以便充分利用这种速度提高。速度的提高来自于所使用的机构更接近于操作系统执行I/O的方式,通道和缓冲器。旧I/O中有三个类 (FileInputStream、FileOutputStream、RandomAccessFile)被修改了,用以产生FileChannel(调用getChanel()方法产生)。ByteBuffer:可存储未加工字节的缓冲器:

188. 使用通道和缓冲器进行文件复制:

FileChannel in = new FileInputStream("sourcefilePath").getChannel(),
              out = new FileOutputStream("destfilePath").getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
while(in.read(buffer) != -1) {
buffer.flip(); // Prepare for writing
out.write(buffer);
 buffer.clear();  // Prepare forreading
}

//使用通道互联简化后:

FileChannel   in = new FileInputStream("sourcefilePath").getChannel(),
              out = new FileOutputStream("destfilePath").getChannel();
in.transferTo(0, in.size(), out);// Or:
// out.transferFrom(in, 0, in.size());

189. 使用系统默认文件编码来解码字符串:

String encoding = System.getProperty("file.encoding");
Charset.forName(encoding).decode("adsgStr阿斯蒂芬"));

190. 字节存放顺序:

big endian(高位优先,大端存储)将高位字节(最重要的字节)放在地址最低的存储器单元。

little endian(地位优先,小端存储):将低位字节放到低地址的存储单元。

ByteBuffer默认是以高位优先的形式存储数据的,可用参数修改:

ByteBuffer bb = ByteBuffer.wrap(new byte[12]);
bb.order(ByteOrder.BIG_ENDIAN);
bb.asCharBuffer().put("abcdef");
print(Arrays.toString(bb.array()));bb.rewind();
bb.order(ByteOrder.LITTLE_ENDIAN);
bb.asCharBuffer().put("abcdef");
print(Arrays.toString(bb.array()));

输出:

[0, 97, 0, 98, 0, 99, 0, 100, 0, 101, 0,102]

[97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102,0]

图2大端模式与小端模式

191. ByteBuffer是往channel里读写数据的唯一途径,而且你只能创建这一种byte基本类型的缓冲器ByteBuffer,其余基本类型的缓冲器要用"as" 方法来获取。Java NIO类库的各个类关系图如下图所示:

192. 缓冲器(Buffer)的细节:Buffer=数据+四个索引。正是四个索引才是的数据可以高效访问,这四个索引是:mark(标记),position(位置),limit(界限),capacity(容量)。标记、位置、限制和容量值遵守以下不变式:

0 <= 标记 <= 位置 <= 限制 <= 容量

以下是设置和复位索引以及查询的方法:

capacity():返回此缓冲区的容量。

clear()清除此缓冲区。position设为0,limit设为capacity。写入缓冲区前调用

flip():反转此缓冲区。将limit设置为position,position设为0。写入完成准备读取时调用

limit():返回此缓冲区的限制。limit(intnewLimit):设置此缓冲区的限制。

mark():在此缓冲区的位置设置标记,将mark设置为position。

position():返回当前位置。position(intnewPosition):设置位置。 
remaining():返回当前位置与限制之间的元素数(limit-position)。

hasRemaining():若有介于position与limit之间的元素返回true.
reset():将此缓冲区的位置重置为以前标记的位置,将position设为mark。
rewind():重绕此缓冲区,设置position = 0;  mark = -1;。

图3Java NIO类库的各个类关系图

193. 内存映射文件:允许创建和修改那些因为太大而不能放入内存的文件。有了内存映射文件,可以假定整个文件都放在内存中,完全可以把它当做非常大的数组来访问。

MappedByteBuffer out =new RandomAccessFile("test.dat", "rw")

    .getChannel().map(FileChannel.MapMode.READ_WRITE,0, length);
for (int i= 0; i <length;i++) out.put((byte) 'x');

print("Finished writing");
for (int i= length / 2;i <length / 2 + 6; i++)

printnb((char) out.get(i));

194. 文件加锁

FileOutputStream fos = new FileOutputStream("file.txt");
FileLock fl = fos.getChannel().tryLock();//非阻塞式,lock()为阻塞式
if (fl != null){
    System.out.println("Locked File");
    System.out.println("isShard:" + fl.isShared());
    TimeUnit.MILLISECONDS.sleep(100);
    fl.release();
    System.out.println("Released Lock");
}
fos.close();

对映射文件部分加锁

FileChannel fc= new RandomAccessFile("test.dat", "rw").getChannel();

FileLock fl = fc.lock(start, end,false);

fl.release();

195. 文件压缩:GZIP/ZIP

GZIP压缩:适合单文件压缩

BufferedReader in = new BufferedReader(newFileReader("fName.txt"));
BufferedOutputStream out = new BufferedOutputStream(
new GZIPOutputStream(new FileOutputStream("test.gz")));
int c;
while((c = in.read()) != -1) out.write(c);
in.close();out.close();

System.out.println("Reading file");
BufferedReader in2 = new BufferedReader(

new InputStreamReader(

new GZIPInputStream(   

new FileInputStream("test.gz"))));
String s;
while((s = in2.readLine()) != null) System.out.println(s);

ZIP压缩:适合多文件P569

196. 对象序列化:ObjectOutputStream/ObjectInputStream

public class Worm implements Serializable {}//必须实现此接口

Worm w = new Worm();

ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("worm.out"));
out.writeObject("Worm storage\n");out.writeObject(w);
out.close(); // Also flushes output
ObjectInputStream in = new ObjectInputStream(
new FileInputStream("worm.out"));
String s = (String)in.readObject();Worm w2 = (Worm)in.readObject();

197. 序列化的控制:需要序列化的类实现Externalizable接口(继承自Serializable),则序列化时自动调用对象的writeExternal()方法,反序列化时,自动调用默认构造器(没有public构造器会报错)和readExternal()方法。需在这两个方法中手动写入与恢复数据

writeExternal(ObjectOutput out) throws IOException {

out.writeObject(s);out.writeInt(i);// You must do this:

}

readExternal(ObjectInput in) throws IOException,ClassNotFoundException {

s = (String) in.readObject();i = in.readInt();//You must do this:

}

198. transient(瞬时)关键字可以关闭某个字段的序列化(实现Serializable时)

199. 延时一秒TimeUnit.SECONDS.sleep(1); 或者TimeUnit.MILLISECONDS.sleep(1000);

200. 只要将任何对象序列化到单一流中,就可以恢复出我们写出时一样的对象网,并且没有任何意外重复复制出的对象。(如果将两个对象序列化—它们都具有指向第三个对象的引用—进行序列化,恢复时第三个对象只出现一次。除非这两个对象被序列化成独立的文件,会产生不同的对象网)

201. 尽管Class类是Serializable的,static字段无法自动序列化,需自己实现。

202. XML序列化:使用开源XOM类库p586

203. Preferences利用操作系统资源(Windows中用注册表)存储和读取用户的偏好以及程序的配置。Preferences是一个键值集合,通常创建以类名命名的单一节点,只能存储基本类型与字符串(不超过8K)。

Preferences prefs = Preferences.userNodeForPackage(PreferencesDemo.class);
prefs.put("Footwear", "Ruby Slippers");
prefs.putInt("Companions", 4);
prefs.putBoolean("Are there witches?",true);

for(String key : prefs.keys())
print(key + ": "+prefs.get(key, null));

第 19 章  枚举类型

204. 创建enum时,编译器会自动生成一个继承自java.lang.Enum的类。调用values()方法可以遍历enum实例:

enum Shrubbery { GROUND, CRAWLING, HANGING}

for(Shrubbery s : Shrubbery.values()) {
print(s +
" ordinal: " + s.ordinal());  //顺序,0开始

print(s.name());

}

205. 当enum实例向上转型为Enum时,values()方法不可访问,可以通过Class对象的getEnumConstants()方法遍历:

Enum e = Shrubbery.GROUND; //Upcast

for(Enum en :e.getClass().getEnumConstants())System.out.println(en);

206. 字符串转enum:

Shrubbery shrub = Enum.valueOf(Shrubbery.class, "CRAWLING");//或者:

Shrubberyshrub = Shrubbery.valueOf("CRAWLING");

207. 静态引入importstatic enumerated.Shrubbery.*;可以将enum实例的标识符带入当前命名空间,即可直接使用GROUND, CRAWLING, HANGING

Switch的case语句使用enum时,也可直接使用GROUND, CRAWLING,HANGING

208. 除不能继承自一个enum之外,基本可以将enum看做class,可为enum添加方法,enum也可以实现其他接口。

public enum OzWitch {

// Instances must be defined first, beforemethods:
WEST
("description1"), NORTH("description2");
private String description;
// Constructor must be package orprivate access:
private OzWitch(String description) {
this.description = description;
  }
public String getDescription() { return description; }
public static void main(String[] args) {
for(OzWitch witch : OzWitch.values())
print(witch +
": " + witch.getDescription());
  }
}

209. 使用接口组织enum:下例中的所有枚举都可以向上转型为Food

public interface Food {
enum Appetizer implementsFood {
SALAD, SOUP,SPRING_ROLLS;
  }
enum MainCourse implementsFood {
LENTILS, HUMMOUS,VINDALOO;
  }

}

210. 使用EnumSet代替传统的基于int的“位标志”(BitSet),EnumSet中的元素必须来自于一个enum:

public enum AlarmPoints {
STAIR1, STAIR2, LOBBY,OFFICE1, OFFICE2,BATHROOM, UTILITY, KITCHEN
}

EnumSet<AlarmPoints>points =EnumSet.noneOf(AlarmPoints.class);//空集

points.add(BATHROOM);print(points);
points.addAll(EnumSet.of(
STAIR1, STAIR2, KITCHEN));print(points);

211. EnumMap是一种特殊的Map,它要求其中的键必须来自一个enum:

interface Command {    void action(); }

EnumMap<AlarmPoints, Command> em = new EnumMap<AlarmPoints,Command>(AlarmPoints.class);
em.put(KITCHEN, () ->print("Kitchen fire!"));
em.put(BATHROOM, () ->print("Bathroom alert!"));
for (Map.Entry<AlarmPoints,Command> e : em.entrySet()) {
printnb(e.getKey() + ": ");    e.getValue().action();
}

212. 常量相关方法:允许为enum实例编写方法,从而为每个enum实例赋予各种不同的行为。可以为enum定义抽象方法,然后为每个enum实例实现该抽象方法。或者enum实例定义方法来重写enum定义的方法。

public enum ConstantSpecificMethod {

CLASSPATH {

String getInfo() {

return System.getenv("CLASSPATH");

}},
VERSION {

   String getInfo(){

return System.getProperty("java.version");}};
public static void main(String[] args) {
for (ConstantSpecificMethod csm : values())
            System.out.println(csm.getInfo());
    }
abstract String getInfo();
}

213. 通过相关常量方法,可以实现一个简单的职责链(P606)。在职责链(Chain of Responsibility)设计模式中,程序员以多种不同的方式来解决一个问题,然后将它们连接在一起。当一个请求到来时,它遍历这个链,知道链中的某个解决方案能够处理该请求。

214. 使用enum实现状态机P609。一个状态机可以具有有限个特定的状态,通常根据输入,从一个状态转移到下一个状态,每个状态都具有某些可以接受的输入,不同的输入回事状态机从当前状态转移到不同的新状态。enum适合表现不同的状态输入

215. 使用enum实现多路分发P613

第 20 章  注解

216. 又称“元数据”,以@开头,为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后的某个时刻非常方便的使用这些数据。

217. 三种标准注解、四种元注解(定义注解的注解)

标准注解

@Override

显示表明,当前定义的方法覆盖超类中的方法

@Deprecated

过时(弃用)警告

@SuppressWarnings

关闭不当的编译器警告信息

元注解

@Target

TYPE:类、接口、enum

FIELD:域声明、包括enum实例

METHOD:方法声明

PARAMETER:参数声明

CONSTRUCTOR:构造器的声明

LOCAL_VARIABLE:局部变量声明

ANNOTATION_TYPE:注解定义处

PACKAGE:包声明

@Retention

表示需要在什么级别保存该注解信息。

SOURCE:将被编译器丢弃(只在源码中)

CLASS:将被VM丢弃(在class文件中)

RUNTIME:VM运行时保留信息,允许通过反射获取信息

@Documented

将此注解包含在Javadoc中

@Inherited

允许子类继承父类中的注解

218. (自定义)注解定义:

@Target(ElementType.METHOD)//多个值以逗号分隔

@Retention(RetentionPolicy.RUNTIME)
public @interfaceUseCase {
public int id();
public String description() default"no description";
}

注解使用:

public class PasswordUtils {
@UseCase(id = 47, description = "Passwordsmust contain numeric")
public boolean validatePassword(Stringpassword) {
return (password.matches("\\w*\\d\\w*"));
    }
@UseCase(id = 48)
public String encryptPassword(Stringpassword) {
return new StringBuilder(password).reverse().toString();
    }
}

注解跟踪:

for(Method m : PasswordUtils.class.getDeclaredMethods()){
UseCase uc = m.getAnnotation(UseCase.class);
if(uc != null){

System.out.println("Found UseCase:" +uc.id() +" " +uc.description());
  }
}

219. 注解快捷方式:如果注解中定义了名为value的元素,并且在使用该注解时,如果该元素是唯一一个需要赋值的元素的,那么使用的时候无需使用(名=值)方式,只需要在括号中给出value的值即可。

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interfaceSQLString {
int value() default 0;
  String name() default "";
}

public class Member {
@SQLString(30) String firstName;

}

220. 访问者模式:一个访问者会遍历某个数据结构或者一个对象的集合,对其中的每一个对象执行一个操作,该数据结构无需有序,而你对每个对象执行的操作,都是特定于此对象的类型。这就将操作与对象解耦,也就是说,你可以添加新的操作,而无需向类的定义中添加方法。

221. 基于注解的单元测试P634。Java著名单元测试工具Junit,Junit4支持注解

222. assert有两种用法,Java在执行的时候默认是不启动断言检查的(这个时候,所有的断言语句都将忽略!),如果要开启断言检查,则需要用开关-enableassertions或-ea来开启

1、assert <boolean表达式>

如果<boolean表达式>为true,则程序继续执行。

如果为false,则程序抛出AssertionError,并终止执行。 

2、assert <boolean表达式> : <错误信息表达式>

如果<boolean表达式>为true,则程序继续执行。

如果为false,则程序抛出java.lang.AssertionError,并输入<错误信息表达式>。

第 21 章  并发

223. Java产生线程方式:继承Thread实现Runnable接口并传给Thread构造函数

Thread t = new Thread(new Runnable() {
public void run() {
for (int i= 0; i <10;i++) System.out.print(i);
   }
});

//或者:Thread t = new Thread(() -> {

for (int i = 0; i <10; i++)System.out.print(i);
});
t.start();

224. 使用Executor【优选】,其中LiftOff类实现了Runnable接口

ExecutorService exec = Executors.newCachedThreadPool();
for(int i= 0; i <5;i++)
  exec.execute(new LiftOff());
exec.shutdown();//防止新任务被提交给这个Executor

使用ExecutorService exec = Executors.newFixedThreadPool(5);时,可以限定5个线程,一次性预先执行代价高昂的线程分配,可以节省时间

使用ExecutorService exec = Executors.newSingleThreadExecutor();时,线程个数为1,多任务排队执行

225. 从任务中产生返回值。如果希望任务完成时能够返回一个值,可以实现Callable<T>接口(T是返回类型)而不是Runnable接口:

class TaskWithResult implements Callable<String>{
private int id;
public TaskWithResult(int id) {   this.id= id;    }
public Stringcall(){return "result of TaskWithResult " + id; }
}

ExecutorService exec = Executors.newCachedThreadPool();
ArrayList<Future<String>> results = new ArrayList<Future<String>>();
for (int i= 0; i <10;i++)

results.add(exec.submit(new TaskWithResult(i)));
for (Future<String> fs : results)//fs.isDone()可查看任务是否完成
System.out.println(fs.get());// get() blocks until completion:
exec.shutdown();

226. 休眠:Thread.sleep(100);或者TimeUnit.MILLISECONDS.sleep(100);【优选】

227. 优先级设定:在run()方法中使用如下语句来设定优先级:

Thread.currentThread().setPriority(priority);

优先级1,2,……10,可以使用Thread.MIN_PRIORITYThread.NORM_PRIORITY(默认)Thread.MAX_PRIORITY设定,优先级分别是1,5,10

228. 让步:Thread.yield();//已完成一次循环迭代,建议(不一定会被采纳)具有相同优先级的其他线程可以运行。不能依赖yield()方法控制线程运行顺序。

229. 后台线程:是指在程序运行时在后台提供一种通用服务的线程,并且不属于程序不可或缺的一部分。当所有非后台线程结束时,程序也就终止了,同时会杀死所有后台线程【立即关闭,就连finally子句也不执行就终止run方法】。执行main()的是一个非后台线程。如果一个线程是后台线程,那么它创建的任何线程都将被自动设置为后台线程[不可手动设置为非后台,会报异常]。

Thread daemon = newThread(new SimpleDaemons());
daemon.setDaemon(true); //设为后台进程 Must call before start()
daemon.start();

230. 加入一个线程:一个线程可以在其他线程之上调用join()方法,其效果是等待一段时间知道第二个线程结束才继续执行。调用join()时可以带一个超时参数,如果目标线程在这段时间到期时还没结束,join方法总能返回。

231. 捕获异常:使用Thread.UncaughtExceptionHandler处理线程中未处理的异常

class MyUncaughtExceptionHandlerimplements Thread.UncaughtExceptionHandler{
public void uncaughtException(Thread t,Throwable e) {
        System.out.println("caught" + e);
    }
}

class HandlerThreadFactory implements ThreadFactory{
public Thread newThread(Runnable r) {
     Thread t = new Thread(r);

    t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
return t;
   }
}

class ExceptionThread2 implements Runnable{
public void run() {    throw new RuntimeException();    }
}

ExecutorService exec = Executors.newCachedThreadPool(new HandlerThreadFactory());
exec.execute(new ExceptionThread2());

如果要在代码中各处使用相同的异常处理器,可以将该处理器设为默认的未捕获异常处理器:Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler());

232. ★★★synchronized关键字(内建的形式):防止资源冲突,当任务要执行被synchronized关键字保护的代码片段的时候,它将检查是否可用,然后获取,执行代码,释放

synchronizedvoid f(){/*……*/}

syschronizedvoid g(){/*……*/}

所有对象都自动含有单一的锁(所有synchronized方法共享一个)。当在对象上调用其任意synchronized方法时,此对象都被加锁,这时该对象上的其他synchronized方法只有等到前一个方法调用完毕并释放锁以后才能调用。

    针对每个类,也有一个锁,synchronized static方法可以在类的范围内防止对static数据的并发访问。

233. ★★★使用显示的Lock对象(相对于synchronized更灵活,可以尝试获取锁tryLock(),在规定时间内尝试获取锁):

public class MutexEvenGenerator {
private int currentEvenValue= 0;
private Lock lock =new ReentrantLock();
public int next() {
lock.lock();

//boolean captured = lock.tryLock();

//boolean captured = lock.tryLock(20, TimeUnit.SECONDS);
try {
      ++currentEvenValue;
      Thread.yield(); // Cause failure faster
++currentEvenValue;
return currentEvenValue;
    } finally{
lock.unlock();
    }
  }
 }

234. volatile关键字:Java提供了volatile关键字来保证可见性(可视性。当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

volatile关键字能禁止指令重排序,所以volatile能在一定程度上保证有序性(volatile修饰的语句顺序不会被重排序),volatile关键字无法保证操作的原子性。

235. 原子性:原子操作是不能被线程调度机智中断的操作,一旦操作开始,那么它一定可以在可能发生的“上下文切换”(切换到其他线程执行)之前执行完毕。

原子性可以应用于出long和double之外的所有基本类型的“简单操作”(赋值与返回)。因为JVM将64位(long和double)的变量的读取和写入当做两个分离的32位操作来执行,因此定义long和double时需使用volatile关键字保证简单赋值与返回操作的原子性。

236. 原子类:AtomicInteger、AtomicLong、AtomicReference等,提供原子性条件的更新操作addAndGet等

237. 延时关闭程序:new Timer().schedule(new TimerTask() {
public void run() {
        System.err.println("Aborting");
        System.exit(0);
    }
}, 5000); //Terminate after 5 seconds

238. 临界区(同步控制块):如果只是希望多个线程同时访问方法内部的部分代码而不是整个方法(可提高性能),这部分代码段被称为“临界区”,使用synchronized关键字修饰。

synchronized(syncObject){//此处代码同一时间只有一个线程执行

}//进入代码前,必须得到syncObject对象的锁

239. 线程本地存储(java.lang.ThreadLoacl):线程本地存储是一种自动化机制,可以为使用相同变量的每个不同的线程都创建不同的存储。ThreadLocal通常当做静态域存储。下例中,在不同的线程中调用value.get()将得到不同的值。

privatestatic ThreadLocal<Integer>value =
new ThreadLocal<Integer>() {
private Random rand = new Random(47);
protected synchronized IntegerinitialValue() {
return rand.nextInt(10000);
            }
        };

240. 定时终结任务:ExecutorServiceexec = Executors.newCachedThreadPool();exec.awaitTermination(250, TimeUnit.MILLISECONDS);//等待每个任务结束,如果所有任务在超时之前全部结束,返回true,否则返回false

241. 线程状态

新建:线程创建时,短暂的处于这种状态,已分配必要系统资源,并执行了初始化。

就绪(可运行):这种状态下,只要调度器把时间片分配给线程,线程就可以运行

运行:正在运行

阻塞:线程能够运行,但某个条件阻止它运行。不分配CPU时间

死亡:任务结束,run运行完返回或者被中断

242. 线程进入阻塞状态的情况:

1)调用sleep等方法  【可中断,需处理InterruptedException】

2)调用了某对象的wait()方法

3)任务等待某个输入输出完成 【不可中断】

4)任务试图在某个对象上调用其同步控制(synchronized)方法,但是对象所不可用,因为另一个任务已经获取了这个锁【不可中断】

243. 中断(终结阻塞线程):调用Thread类对象的interrupt()方法,或者Future的cancel方法。调用Executor的shutdownNow()可发送interrupt()给它启动的所有线程。

244. 在ReentrantLock上阻塞的任务具备可以被中断的能力,这与在synchronized方法或临界区上阻塞的任务完全不同。

Lock lock= new ReentrantLock();

//已经在其他任务(线程)上执行了lock.lock();

try {

lock.lockInterruptibly(); // 此阻塞可中断
print("lock acquired in f()");
} catch (InterruptedException e) {
print("Interrupted from lockacquisition in f()");
}

245. 检查中断:Thread.interrupted()方法,返回当前线程是否被中断,该方法相当于Thread.currentThread().isInterrupted(true);//该参数决定中断参数是否被清除。

246. wait() 、notify()、notifyAll()方法(基类Object的方法):线程调用wait后,需要别的线程执行notify/notifyAll才能够重新获得CPU执行时间。由于具有释放锁的特性,只能在同步方法或同步控制块里调用wait()、notify()、notifyAll()、方法。

247. sleep()、wait()方法区别:sleep()睡眠时,保持对象锁,仍然占有该锁,而wait()睡眠时,是否对象锁。

248. scheduleAtFixedRate :

Timer timer = newTimer();
timer.scheduleAtFixedRate(new TimerTask() {
    publicvoid run() {  ……   }
}, delay:400, period:400);// 400毫秒后,每400毫秒执行一次

249. 使用Lock与Condition同步:

private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();

使用condition.await(); condition.signal(); condition.signalAll();方法同步,使用之前必须获得锁

250. 生产者消费者队列BlockingQueue接口),其实现有LinkedBlockingQueue(无限缓冲)、ArrayBlockingQueue(有限缓冲)、SynchronousQueue(没有缓冲),DelayQueue(放置实现Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走), PriorityBlockingQueue,不同线程使用put(),take()方法进行生产消费时,将自动同步。

251. 管道【为旧版,应优先使用BlockingQueue:任务间使用管道进行输入/输出:

private PipedWriter out = new PipedWriter();

private PipedReader in =new PipedReader(out);

在不同线程中使用out.write(c);和in.read()即可实现输入输出同步

252. 死锁:任务之间相互等待,都无法继续

死锁发生的四个必要条件:(防止死锁只需破坏一个)

1)互斥:任务使用的资源至少有一个是不能共享的

2)请求与保持:至少有一个任务它必须持有一个资源且正在等待获取一个当前被别的任务持有的资源

3)不可剥夺:资源不能被任务抢占

4)循环等待:【最容易破坏,哲学家进餐问题可使最后一个哲学家先拿左边的筷子(其他哲学家先右后左)解决死锁】

253. CountDownLatch:同步一个或多个任务,强制他们等待由其他任务执行的一组操作完成。设置计数值N,每个分任务完成后调用countDown()减小这个数值,在此过程中其他任务调用await()将阻塞自己,直到数值减为0

254. CyclicBarrier:适用于这样的情况:创建一组任务,他们并行地执行工作,然后在进行下一个步骤之前等待,直至所有任务都完成,它使得所有任务都在栅栏处列队,因此可以一致的向前移动。类似与CountDownLatch,CountDownLatch只触发一次事件,而CyclicBarrier可以多次重用。构造函数如下

Creates a newCyclicBarrier that will trip when the given number of parties (threads) arewaiting upon it, and which will execute the given barrier action when thebarrier is tripped, performed by the last thread entering the barrier.

Parameters:

parties - the number ofthreads that must invoke await()before the barrier is tripped

barrierAction - thecommand to execute when the barrier is tripped, or null if there is no action

public CyclicBarrier(int parties,Runnable barrierAction)

255. DelayQueue:是一个无界的BlockingQueue,放置实现Delayed接口(继承自Comparable,用于比较延时时间长短)的对象,其中的对象只能在其到期时才能从队列中取走,如果没有任何延迟到期,take()将返回null元素。队列有序,队头的对象延迟到期的时间最短。P726

256. TimeUnit.NANOSECONDS.convert(25414, MILLISECONDS);//时间转换(毫秒转纳秒)

257. PriorityBlockingQueue:优先级队列,具有可阻塞(读取时不必考虑此队列是否有元素,如果没有元素,将直接阻塞读取者)的读取操作

258. ScheduledThreadPoolExecutor:在预定时间运行任务。通过调用schedule方法(运行任务一次),或者使用scheduleAtFixedRate方法(每隔规定的时间重复执行任务),你可以将Runnable对象设置为在将来的某个时刻执行。

public ScheduledFuture<?> schedule(Runnable command,

                                   long delay,

                                   TimeUnitunit)

public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,

                                             long initialDelay,

                                              long period,

                                             TimeUnit unit)

259. Semaphore:信号量。正常的锁(java.util.concurrent.locks包里面的锁或者synchronized锁)在任何时刻都只允许一个任务访问一项资源。而Semaphore计数信号量允许n个任务同时访问这个资源。调用acquire()请求一个资源,如果资源不足则阻塞,release()释放一个资源

260. Exchanger:是两个任务(一般为生产者、消费者)之间交换对象的栅栏。当两任务进入栅栏时,它们各自有一个对象,当它们离开时,它们都将拥有之前任务持有的对象。

public Vexchange(V x);

当其中一个任务调用exchange()方法时,它将阻塞直至对方任务调用它自己的exchange()方法,此时,两个exchange()方法全部完成,对象互换即完成。

261. ★★互斥技术比较:

Ø  使用Lock通常会比synchronized要高效许多,而且synchronized的开销变化范围太大(synchronized代码块里面的运算规模越大,synchronized开销迅速增长),而Lock相对比较一致。

Ø  synchronized产生的代码,可读性较Lock高很多

Ø  Atomic性能好,但只要在非常简单的情况下才有用这些情况通常只有一个要被修改的Atomic对象,并且这个对象独立于其他所有的对象)。

★如果能用Atomic则使用;synchronized优先于Lock,只有在性能调优时才替换为Lock对象

262. ★★CopyOnWriteArrayList中,写入将导致创建整个底层数组的副本,而原数组将保留在原址,是的复制的数组在被修改时,读取操作可以安全地执行。当修改完成时,一个原子性的操作将把心得数组换入,是的心得读取操作可以看到这个新的修改。允许在列表被遍历时修改列表(调用remove方法),而不会抛出ConcurrentModification Exception异常。类似容器还有CopyOnWriteArraySet、ConcurrentHashMap、ConcurrentLikedQueue。

263. 乐观加锁(Atomic对象)P760:当执行某项计算时,实际上没有使用互斥,但是在这项计算完成,并且你准备更新这个Atomic对象时,你需要使用一个称谓compareAndSet()的方法,将旧值与新值一起提交给这个方法,如果旧值与Atomic对象现值不一致,则这个操作就失败——这意味着某个其他的任务已经于此操作执行期间修改了这个对象。

java.util.concurrent.atomic.AtomicInteger

public final boolean compareAndSet(int expect,int update)

Atomically sets the value to the givenupdated value if the current value == the expected value.

Parameters:

expect- the expected value

update- the new value

Returns:

true ifsuccessful. False return indicates that the actual value was not equal to theexpected value.

264. ReadWriteLock接口(实现为ReentrantReadWriteLock):对有多个任务读取某数据结构,但是写入不频繁这类情况进行了优化。ReadWriteLock使得你可以同时有多个读取者,只要它们都不试图写入即可。如果写锁已经被其他任务持有,那么任何读取者都不能访问,直到这个写锁被释放为止。P761.

public interface ReadWriteLock {
    /**     * @return the lock used forreading     */
   
Lock readLock();
    /**     * @return the lock used forwriting     */
   
Lock writeLock();
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值