第18章 Java I/O系统
18.1 File 类
18.1.1 目录列表器
18.1.2 目录实用工具
18.1.3 目录的检查及创建
18.2 输入和输出
在Java 1.0中类库的设计者限定于输入有关的类从InputStream继承;而与输出有关的所有类都应该从OutputStream继承
18.2.1 InputStream类型
18.2.2 OutputStream类型
18.3 添加属性和有用的接口
18.3.1 通过FilterInputStream从InputStream读取数据
18.3.2 通过FilterOutputStream向OutputStream写入
18.4 Reader和Writer》兼容Unicode与面向字符的I/O
18.4.2 更改流的行为
18.4.3 未发生变化的类
18.5 自我独立的类:RandomAccessFile》它适用于大小已知的记录组成的文件
18.6 I/O流的典型使用方式
18.6.1 缓冲输入文件》BufferedReader
BufferedReader in=new BufferedReader(new FileReader(filename));
18.6.2 从内存输入》StringReader
StringReader in=new StringReader(BufferedInputFile.read("xxx.txt"));
18.6.3 格式化的内存输入》DataInputStream
DataInputStream in=new DataInputStream(new ByteArrayInputStream("xxxxx.java").getBytes());
18.6.4 基本的文件输出》FileWriter可以向文件中写入数据
Printwriter out=new PrintWriter(new BufferedWriter(new FileWriter("xxxx.out")));
文本文件的输出的快捷方式
PrintWriter out=new PrintWriter("xxxx.out");
18.6.5 存储和恢复数据》使用DataOutputStream写入数据,使用DataInputStream读取数据
DataOutputStream out=new DataOutputStream(new BufferedOutputStream(new FileOutputStream("data.txt")));
DataInputStream in=new DataInputStream(new BufferedInputStream(new FileInputStream("data.txt")));
18.6.6 读写随机访问文件》RandomAccessFile,可以利用seek()在文件中到处移动;其限制是必须知道文件的排版
18.6.7 管道流》PipedInputStream,PipedOutputStream,PipedReader及PipedWriter
18.7 文件读写的实用工具
18.7.2 读取二进制文件
18.8 标准I/O
18.8.1 从标准输入中读取》System.in,System.out和System.err
18.8.2 将System.out转换成PrintWriter》System.out是一个PrintStream,而PrintStream是一个OutputStream
PrintWriter out=new PrintWriter(System.out,true);
18.8.3 标准I/O重定向
18.9 进程控制》要想运行一个系统程序,需要向OSExecute.command()传递一个command字符串,它与在控制台中运行该程序键入的命令相同
18.10 新的I/O》java.nio.*
FileChannel 以及java.nio.channels.Channels
18.10.1 转换数据
18.10.2 获取基本类型》ByteBuffer虽然只能保存字节类型数据,但是它具有从其所包含的字节中产生出不同基本类型值的方法
18.10.3 视图缓冲期》例如:IntBuffer操作ByteBuffer中的int型数据
18.10.4 用缓冲器操纵数据》想把一个字节数组写入到文件中,在nio类中,用ByteBuffer.wrap()把字节数组包装起来,然后用
getChannel()方法在FileOutputStream上打开一个通道,接着将ByteBuffer的数据写到FileChannel中
18.10.5 缓冲期的细节
18.10.6 内存映射文件
18.10.7 文件加锁
public class FileLocking{ public static void main(String[] args){ FileOutputStream fos=new FileOutputStream("file.txt"); FileLock fl=fos.getChannel().tryLock(); if(fl!=null){ System.out.println("Locked File"); TimeUnit.MILLISECONDS.sleep(100); fl.release(); System.out.println("Release File"); } fos.close(); } }
18.11 压缩
18.11.1 用GZIP进行简单压缩
public class GZIPcompress{
public static void main(String[] args){
BufferedReader in=new BufferedReader(new FileReader("xxxx.txt"));
BufferedOutputStream out=new BufferedOutputStream(new GZIPOutputStream(new FileOutputStream("text.gz")));
int c;
while((c=in.read())!=-1){
out.write(c);
}
in.close();
out.close();
BufferedReader in2=new BufferedReader(new InputStreamReader(new GZIPInputStream(
new FileInputStream("test.gz"))));
String s;
while((s=in2.readLine())!=null)
System.out.println(s);
}
}
18.11.2 用Zip进行多文件保存
GZIP或Zip库的使用并不仅仅局限于文件————它可以压缩任何东西,包括需要通过网络发送的数据
18.11.3 Java档案文件
18.12 对象序列化
要序列化一个对象:创建某些OutputStream对象》封装到一个ObjectOutputSteam对象内》
调用writeObject()即可序列化》将其发送给OutputStream(因为对象序列化是基于字节的,
因要使用InputSteam和OutputSteam继承层次结构);要反向进行该过程,需要将一个InputStream
封装在ObjectInputStream内,然后调用readObject()
18.12.1 寻找类
18.12.2 序列化控制》Externalizable继承了Serializable,同时新增了writeExternal和readExternal
18.12.3 使用“持久性”
18.13 XML
18.14 Preferences
Preferences API与对象序列化相比,前者与对象持久性更密切,因为它可以自动存储和读取信息。不过,它只能用于小的,
受限的数据集合————只能存储基本类型和字符串,并且每个字符串的存储长度不能超过8K
Preferences用户存储和读取用户的偏好(preferences)以及程序配置项的设置
第19章 枚举类型
19.1 基本enum特性
19.1.1 将静态导入用于enum
public enum Spiciness{ NOT,MILD,MEDIUM,HOT,FLAMING }
19.2 向enum中添加新方法》除了不能继承自一个enum之外,基本上可以将enum看作一个常规的类
注意:如果打算在一个enum中定义自己的方法,那么必须在enum实例序列的最后添加一个分号。同时,Java中必须先定义enum实例。
如果在定义enum实例之前定义了任何方法或属性,那么在编译时就会得到错误信息.
public enum OzWithc{ WEST("Miss Gulch,aka the Wicked witch of the west"), NOTRH("Glinda,the good witch of the north"), EAST("Wicked witch of the east"), SOUTH("Good by inference,but missing"); private String description; private OzWithc(String description){ this.description=description; } public String getDescription(){ return description; } public static void main(String[] args){ } }
19.2.1 覆盖enum的方法
19.4 values的神秘之处
19.5 实现,而非继承
enum CartoonCharacter implements Generator<CartoonCharacter>{ SLAPPY,SPANKY,PUNCHY,SILLY,BOUNCY,NUTTY,BOB; private Random rand=new Random(47); public CartoonCharacter next(){ return values()[rand.nextInt(values().length] } }
19.6 随机选取
19.7 使用接口组织枚举
有时想将枚举元素分类组织,可以在一个接口内部创建实现该接口的枚举,以此将元素进行分组
public interface Food{ enum Appetizer implements Food{ SALAD,SOUP,SPRING; } enum MainCourse implements Food{ LASAGNE,BURRITO,PAD,LENTILS,HUMMOUS; } .... }
19.8 使用EnumSet替代标志
19.9 使用EnumMap
19.10 常量相关的方法
public enum ConstantSpecificMethod{ DATE_TIME{ String getInfo(){ return DateFormat.getDateInstance().format(new Date()); } }, CLASSPATH{ String getInfo(){ return System.getProperty("CLASSPATH"); } }, VERSION{ String getInfo(){ return System.getProperty("java.version"); } } abstract String getInfo(); public static void main(String[] args){ for(ConstantSpecificMethod csm: values){ System.out.println(csm.getInfo()); } } }
19.11 多路分发
第20章 注解
20.1 基本语法
20.1.1 定义注解
注解和其他任何Java接口一样,注解也会编译成class文件
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Test()
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface UseCase{ public int id(); public String description() default "no description"; }
public class PasswordUtils{ @UseCase(id=47,description="Password must contain at least one numeric") public boolean validatePassword(String password){ return (password.matches("\\w*\\d\\w*")); } @UseCase(id=48) public String encrytPassword(String password){ return new StringBuilder(password).reverse().toString(); } }
20.2.1 注解元素
注解元素可用的类型如下:
所有的基本类型(int,float,boolean等)
String
Class
enum
Annotation
以上类型的数组
20.2.2 默认值限制
1.元素不能有不确定的值,也就是说元素必须要么具有默认值,要么在使用注解时提供元素的值
2.对于非基本类型的元素,无论是在源代码中声明时,或者是在注解接口中定义默认值时,都不能以null作为其值
20.2.3 生成外部文件
20.2.4 注解不支持继承》不能使用关键字extends来继承某个@interface
20.4 将观察者模式用于apt
20.5 基于注解的单元测试
第21章 并发
21.1 并发的多面性
并发编程令人困惑的一个主要原因就是:使用并发时需要解决的问题有多个,而实现并发的方式也有多种,并
且在这两者之间没有明显的映射关系(而且通常只具有模糊的界限)
21.1.1 更快的执行
实现并发最直接的方式是在操作系统级别使用进程.进程是运行在它自己的地址空间内的自包容的程序
编写多线程程序最基本的困难在于协调不同线程驱动的任务之间对这些资源使用,以使得这些资源不会同时被多个任务访问
21.2 基本的线程机制
线程模型为编程带来了便利,它简化了咋单一程序中同时交织在一起的多个操作的处理。在使用线程时,CPU将轮流给每个任务
分配其占用的时间[当系统使用时间切片机制时,情况确实如此(Windows)。Solaris使用了FIFO并发模型:除非有高优先级的线程被
唤醒,否则当前线程将一直运行,直至它被阻塞或终止.这意味着具有相同优先级的其他线程在当前线程放弃处理器之前,将不会运行]
21.2.1 定义任务
线程可以驱动任务,而描述任务的方式由Runnable接口提供,只需要实现Runnable接口并编写run()方法
public class LiftOff implements Runnable{ protected int countDown=0; private static int taskCount=0; private final int id=taskCount++; public LiftOff(){} public LiftOff(int countDown){ this.countDown=countDown; } public String status{ return "#"+id+"("+(countDown>0?countDown:"Liftoff!")+")"; } public void run(){ while(countDown-->0){ System.out.println(status); Thread.yield(); } } }
在run()中对静态方法Thread.yield()的调用是对线程调度器(Java线程机制的一部分,可以将CPU从一个线程转移给另一个线程)
的一种建议,它在声明:“我已经执行完生命周期中最重要的部分了,此刻正是切换给其他任务执行一段时间的大好时机”。这完全是
选择性的,但是这里使用它是因为它会在这些示例中产生更加有趣的输出
当从Runnable继承出来的类,它必须具有run()方法,但是这个方法并无特殊之处---它无任何内在的线程能力。要实现线程行为,
必须显式地将一个任务附着在线程上。
21.2.2 Thread类
将Runnable对象转变为工作任务的传统方式是把它提交给一个Thread构造器
public class BasicThreads{ public static void main(String[] args){ Thread t=new Thread(new LiftOff()); t.start(); } }
21.2.3 使用Executor
Java SE5的java.util.concurrent包中的执行器(Executor)将为你管理Thread对象,从而简化并发编程;Executor在客户端
和任务执行之间提供了一个间接层,与客户端直接执行任务不同,这个中介对象将执行任务.Executor允许你管理异步任务的执行
,而无须显示的管理线程的生命周期.Executor在Java SE5/6中是启动任务的优选方法;ExecutorService对象是使用静态的Executor
方法创建的,这个方法确定其Executor类型
public class ThreadDemo { public static void main(String[] args) { ExecutorService executorService=Executors.newCachedThreadPool(); //ExecutorService executorService=Executors.newFixedThreadPool(5);//有限的线程集 for (int i = 0; i <5; i++) { executorService.execute(new LiftOff()); } executorService.shutdown(); } }
CachedThreadPool在程序执行过程中通常会创建于所需数量相同的线程,然后在它回收旧线程时停止创建新的线程,因此它是合理
的Executor的首选.只有当这种方式会引发问题时,才需要切换到FixedThreadPool
SingleThreadExecutor相当于线程数量为1的FixedThreadPool,如果想SingleThreadExecutor提交了多个任务,那么这些任务将排
队,每个任务都会在下一个任务开始之前结束,所有的任务将使用相同的线程.
21.2.4 从任务中产生返回值
Runnable不返回任何值.如果希望在任务完成时能够返回一个值,那么可以实现Callable接口而不是Runnable接口。在Java SE5中引
入的Callable是一个具有类型参数的泛型,它的类型参数表示是从方法call()[而不是run()]中返回的值,并且必须使用ExecutorService.submit()
方法调用
class TaskWithResult implements Callable<String>{ private int id; public TaskWithResult(int id){ this.id=id; } public String call(){ return "result of TaskWithResult "+id; } } public class CallableDemo{ public static void main(String[] args){ ExecutorService exec=Executors.newCachedThreadPool(); ArrayList<Future<String>> results=new ArrayList<Future<String>>; for(Future<String> fs:results){ //get() blocks untils completic System.out.println(fs.get()); exec.shutwodn(); } } }
21.2.5 休眠
影响任务行为的一种简单方法是调用sleep()
public class SleepingTask extends LiftOff{ public void run(){ try{ while(countDown-->0){ System.out.print(status()); TimeUnit.MILLISECONDS.sleep(100); } }catch (InterruptedException e){ System.err.println('Interrupted..'); } } public static void main(String[] args){ ExecutorService exec=Executors.newCachedThreadPool(); for(int i=0;i<5;i++){ exec.executor(new SleepingTask()); } exec.shutdown(); } }
21.2.6优先级》Thread.currentThread().setPriority() ;getPriority();
尽管JDK有10个优先级,但是与多数操作系统都不能很好的映射; Windows有7个优先级;Sun的Solaris有
2的31次方个优先级;唯一可以移植的是MAX_PRIORITY,NORM_PRIORITY和MIN_PRIORITY
21.2.7 让步》通过调用yield()方法做出一个让步暗示
21.2.8 后台线程》所谓的后台线程是指在程序运行的时候在后台提供一种通用服务的线程,并且这种线程并
不属于程序中不可以或缺的部分
public class SimpleDaemon implements Runnable{ public void run(){ try{ while(true){ TimeUnit.MILLISECONDS.sleep(100); print(Thread.currentThread()+" "+this); } }catch(InterruptedException e){ print("sleep() interrupted") } } public static void main(String[] args){ for(int i=0;i<10;i++){ Thread daemon=new Thread(new SimpleDaemon()); daemon.setDaemon(true);//must call before start daemon.start(); } } }
21.2.6优先级》Thread.currentThread().setPriority() ;getPriority();
尽管JDK有10个优先级,但是与多数操作系统都不能很好的映射; Windows有7个优先级;Sun的Solaris有
2的31次方个优先级;唯一可以移植的是MAX_PRIORITY,NORM_PRIORITY和MIN_PRIORITY
21.2.7 让步》通过调用yield()方法做出一个让步暗示
21.2.8 后台线程》所谓的后台线程是指在程序运行的时候在后台提供一种通用服务的线程,并且这种线程并
不属于程序中不可以或缺的部分
21.2.9 编码的变体
21.2.10 术语》Java的线程机制基于来自C的低级的p线程方式
21.2.11 加入一个线程》一个线程在另一个线程之上调用join()方法,其效果是等待一段时间知道第二个线程
结束才继续执行。如果某个线程在另一个线程t上调用t.join(),此线程将被挂起,直到目标线程t结束才恢复(
t.isAlive()返回为假)
21.2.12创建有响应的用户界面
21.2.13 线程组》用来自Joshua Bloch[曾是Sun公司的软件架构师]的话来总结:
“最好把线程组看成是一次不成功的尝试,你只要忽略它就好”
21.2.14捕获异常
21.3 共享受限资源
可以把单线程程序当作问题域求解的单一实体,每次只能做一件事情
21.3.1 不正确的访问资源
21.3.2 解决共享资源竞争
防止多个多个任务同时访问相同的资源的方法就是当资源被一个任务访问时,在其上加锁
只有该资源解锁时才能让其他任务访问它
基本上所有的并发模式在解决线程冲突时都是采用序列化访问共享资源的方案,也就是在
给定时刻只允许一个任务访问共享资源
Java提供关键字synchronized为防止资源冲突提供了内置支持,当任务要执行被synchronized
关键字保护的代码块时,它将检查锁是否可用,然后获取锁,执行代码,释放锁
注意,在使用并发时,将域设置为private是非常重要的,否则,synchronized关键字就不能防止
其他任务直接访问域,这样就会产生冲突
一个任务可以多次访问获得对象的锁;每当一个任务首次在这个对象上获得锁后,在没有解锁
之前有调用了这个对象上的另一个方法,那么锁计数就会增加
针对每个类,也有一个锁,所以synchronized static 方法可以在类范围内防止对static数据的并发访问
应该在什么时候使用同步呢?可以运用Brian的同步规则
“如果你正在写一个变量,它可能接下来将被另一个线程读取,或者正在读取一个上一次已经被另一个线程
写过的变量,那么你必须使用同步,并且,读写线程都必须用相同的监视器同步”
如果类中有超过一个方法在处理临界数据,那么必须同步所有相关的方法,每个访问临界共享资源的方法都
必须被同步,否则它们就不会正确的工作
Thread.yield() / java.util.concurrent.locks / synchronized
21.3.3 原子性与易变性》用原子性替代同步是很危险的想法
原子性可以应用于long和double之外的所有基本类型之上
volatile 关键字
使用volatile而不是synchronized的唯一安全情况就是类中只有一个可变的域.再次提醒,你的
第一个选择应该使用synchronized关键字,这是最安全的方式,尝试其他任何方式都是有风险的
21.3.4 原子类
21.3.5 临界区
如果只希望防止多个线程同时访问方法内部的部分代码而不是防止访问整个方法,通过这种方式分
离出来的代码被称为临界区,它也可以使用synchronized建立
synchronized(syncObject){
...
}
21.3.6 在其他对象上同步
21.3.7 线程本地存储》线程本地存储就是为相同的变量在每个不同的线程上创建不同的存储