书山天道-《Java 核心技术 V2》 - 精读
目标:精读 Java core
要点:
2021-02-21
I / O
读入一个字节序列的对象称作输入流,可以向其中写入一个字节序列的对象称作输出流.
文件,网络连接,内存块。
抽象类 InputStream 和 OutputSteam 构成了 I/O 类层次结构的基础.
面向字节的流不便于处理以Unicode形式存储的信息,所以从抽象类 Reader 和 Writer 中继承出来一个专门处理
Unicode 字符的单独的类层次结构。读写操作都是基于两个字节的Char值(即Unicode码元),而不是基于byte值的.
InputStream 读入一个字节数组。读取流中所有字节的方法. byte[] bytes = in.readAllBytes() ;
OutputStream 可以将一个 byte[] values .. outputStream.write( values )
read 和 write 方法在执行时都将阻塞,直至字节确实被读入或写出。意味着如果流不能被立即访问(通常是因为网络连接忙),那么
当前的线程将被阻塞。
当完成对 输入/输出 流的读写时, close 方法关闭, 否则会占用OS资源,过多的会耗尽资源.
还会 flush 该输出流的缓冲区。
因操作的很少是 原生字节,而是 包含数字,字符串 和 对象.
InputStream 和 OutputStream 可以读写单个字节或字节数组。
读写字符串和数字,需要 DataInputStream 和 DataOutputStream 以二进制格式读写所有的基本Java类型,
ZipInputStram 和 ZipOutputStream 以常见 ZIP 压缩格式读写文件。
Unicode 文本,使用抽象类 Reader 和 Writer 的子类.
java.io.Closeable 接口,实现的可以使用 try-with-resource 语句 。
close 可能会抛出IOException.
某些输入流 FileInputStream 和 由 URL 类的 openStream 方法返回的输入流 可以从文件和其他更外部的位置上获取字节,
而其他的输入流(例如 DataInputStream) 可以将字节组装到更有用的数据类型中.
需要对二者进行组合。
从文件中读入数字,需要先创建一个 FileInputStream, 然后将其传递给DataInputStream构造器。

输入流在默认情况下是不被缓冲区缓存的,每个对 read 的调用都会请求操作系统再分发一个字节。
PushbackInputStream
预读下一个字节,并非所期望的值时将其推回流中: unread(bInt)
ZIP 压缩文件中通过使用下面的输入流序列来读入数字

输入流读入器会假定使用主机系统所使用的默认字符编码方式,
要在 InputStreamReader 的构造器中指定具体的编码方式
Reader 和 Writer 都只有读入和写出单个字符的基础方法。
由处理字符串和数字的子类。
2021-02-22
object serialization
ObjectOutputStream 保存对象, 对象要实现Serializable 接口。
保存对象,可以直接使用 ObjectOutputStream 的 writeObject 方法。
读回对象, ObjectInputStream 对象
每个对象都是用一个序列号 serial number 保存的,这就是这种机制之所以称为 对象序列化的原因.
- 对你遇到的每一个对象引用都关联一个序列号
- 对于每一个对象,当第一次遇到时,保存器对象数据到输出流中
- 如果某个对象之前已经被保存过,那么只写出“与之前保存过的序列号为x的对象相同”
在读 回对象时,整个过程是反过来。
- 对于对象输入流中的对象,在第一次遇到其序列号时,构建它,并使用流中数据来初始化它,然后记录这个顺序号和新对象之间的关联。
- 当遇到“与之前保存过的序列号为x的对象相同” 这一标记时,获取与这个序列号相关联的对象引用。
序列化的一个非常重要的应用是通过网络将对象集合传送到另一台计算机上。
在文件中保存原生的内存地址毫无意义一样,这些地址对于在不同的处理器之间的通信也是毫无意义的。
序列化用序列号代替了内存地址,所以它允许将对象集合从一台机器传送到另一台机器.
java.io.ObjectOutputStream
writeObject(Object obj) : 写出指定对象到 ObjectOutputStream, 这个方法将存储指定对象的类, 类的签名以及这个类及其超类中所有非静态和非瞬时的域的值。
readObject , 执行的反序列化允许恢复多个对象引用。
对象序列化是以特殊的文件格式存储对象数据的。 对于 洞察对象流化的处理过程非常有益。
字符串中的 Unicode 字符被存储为 修订过 的 UTF-8 格式 .
当存储一个对象时,这个对象所属的类也必须存储 ,类的描述 包括 :
1. 类名
2. 序列化的版本版本唯一的ID,它时数据域类型和方法签名的指纹 .
3.描述序列化方法的标志集。
4.对数据域的描述。
指纹, 对类 ,超类,接口,域类型和方法签名按照规范方式排序,然后安全散列算法。SHA 前8个字节。
对象流输出中包含所有对象的类型和数据域.
每个对象都被赋予一个序列号
相同对象的重复出现将被存储为对这个对象序列号的引用。
序列化机制有一种很有趣的用法:提供一种克隆对象简便途径,只要对应的类是可序列化的即可。
直接将对象序列化到输出流中,然后将其读回,这样产生的新对象是对现有对象的一个深拷贝 deep copy.
可以用 ByteArrayOutputStream 将数据保存在字节数组中.
要想得到 clone 方法,只需扩展 SerialCloneable 类就可以了。



2021-02-23
Path 表示的是一个目录名序列,气候还可以跟着一个文件名.
以根部件开始的路径是绝对路径 ,否则就是相对路径。
resolveSibling
如果 workPath 是 /opt/myapp/work
Path tempPath = workPath.resolveSibling("temp") -> /opt/myqpp/temp
Files 类可以使的普通文件操作变得快捷.
如果文件长度比较大,或者二进制文件, 输入/输出流 或者 Reader / Writer
InputStream in = Files.newInputStream(path) ;
OutputStream out = Files.newOutputStream(path);
Reader in = Files.newBufferedReader ( path, charset );
Writer out = Files.newBufferedWriter ( path, charset );


p92 Option 列表
DSYNC 或 SYNC 要求对文件数据 | 数据 和元数据的每次更新都必须同步地写入到存储设备中。
DELETE_ON_CLOSE 当文件被关闭时,尽“可能”地删除该文件.
FileVisitOption 与 find, walk, walkFileTree. 一起使用.


1 Path root = null; 2 Files.walkFileTree(root, new SimpleFileVisitor<Path>(){ 3 4 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException{ 5 Files.delete(file); 6 return FileVisitResult.CONTINUE; 7 } 8 9 public FileVisitResult positVisitDirectory(Path dir, IOException e) throws IOException{ 10 if(e!= null) throw e; 11 Files.delete(dir); 12 return FileVisitResult.CONTINUE; 13 } 14 });
Zip 文件系统 zipname
FileSystem fs = FileSystems.newFileSystem(Paths.get(zipname), null)
将建立一个文件系统,如果知道文件名,从ZIP 文档 中复制出这个文件就会变得很容易.

操作系统可以利用虚拟内存实现来将一个文件或者文件的一部分“映射”到内存中。
这个文件就可以被当作内存数组一样地访问,这比传统的文件操作要快得多。
对于中等尺寸 文件的顺序读入则没有必要使用内存映射.
java.nio 包使内存映射变得十分简单.
计算文件的32位的循环冗余校验和 CRC32 , 这个数值就是经常用来判断一个文件是否已 损坏 的校验和 ,
因为文件损坏 极有可能导致校验和 改变.

缓冲区数据结构
使用内存映射时,单一的缓冲区横跨整个文件或 我们感兴趣的文件区域,还可以使用更多的缓冲区来读写大小适度的信息块。
缓冲区是由具有相同类型的数值构成的数组,Buffer类是一个抽象类,常用的 ByteBuffer 和 CharBuffer.
每个缓冲区都具有
1. 一个容量,它永远不能改变。
2. 一个读写位置,下一个值将在此进行读写.
3. 一个界限,超过它进行读写是没有意义的。
4. 一个可选的标记,用于重复一个读入或写出操作.
0 <= 标记 <= 读写位置 <= 界限 <= 容量
使用缓冲区的主要目的是 执行 “写,然后读入” 循环。
假设我们有一个缓冲区,在一开始,它的位置为0,界限等于容量。
不断地调用 put 将值添加到这个缓冲区中,
当耗尽所有的数据 或者 写出的 数据量达到 容量大小时, 就该切换到读入操作了.
这时调用 flip 方法 将界限设置到当前位置, 并把位置复位到 0,
现在在 remaining 方法返回正数时 ( 它返回的值是 界限 - 位置 ),不断地调用 get,
在我们将缓冲区中所有的值都读入之后,调用 clear 使缓冲区为下一次写循环做好准备 。 clear 方法将位置复位到0,并将界限复位到容量.
重读缓冲区,使用 rewind 或 mark /reset 方法.
获取缓冲区,可以调用诸如 ByteBuffer.allocate 或 ByteBuffer.wrap 静态方法.

2021-02-23
文件加锁 p108
要锁定一个文件, FileChannel 类的 lock ( 会一致阻塞等锁) 或者 tryLock ( 要么返回锁,要么不可得时返回 null)
该文件将保持锁定状态,直至通道关闭,或者在锁上调用了 release 方法。
锁定文件 部分
FileLock lock ( long start, long size, boolean shared) shared 共享锁, false : 独占锁。
文件增长部分可能不会被锁定, 使用 Long.MAX_VALUE 来表示尺寸.

高级主题:
显式 锁
ReentrantLock 不是一种替代内置加锁的方法,而是当内置加锁机制不适用时,作为一种可选择的高级功能.

ReentrantLock 实现了 Lock 接口,提供了与 synchronized 相同的互斥性和内存可见性。
在获取ReentrantLock 时,有着与进入同步代码块 相同的内存语义 .
内置锁的局限性: 无法中一个正在等待获取锁的线程,或者无法在请求获取一个锁时无限地等待下去。
内置锁必须在 获取该锁的代码块中释放 ,简化了操作,与异常处理操作实现了很好的交互,但却无法实现
非阻塞结构的加锁规则。
需要提供更灵活的加锁机制,提供更好的活跃性或性能。
Lock 必须在finally 块中释放锁,否则,如果在被保护的代码中抛出了异常,那么这个锁就永远都无法释放。
当使用加锁时,必须考虑在try块中抛出异常的情况,如果可能使对象处于某种不一致的状态,
就需要更多的 try-catch 或 try-finally 代码块。

可定时的 与 可轮询的 锁获取模式 是由 tryLock 方法实现的,与无条件的所获取模式相比,具有更完善的错误恢复机制。
内置锁中,死锁是一个严重的问题,恢复程序的唯一方法是重启程序, 而防止死锁的唯一方法就是
在构造程序时避免不一致的锁顺序。
可定时与可轮询的锁提供了另一种选择: 避免死锁的发生。
通过 tryLock 来避免锁顺序死锁。 p229

在实现具有时间限制的操作时,定时锁同样非常有用。
当在带有时间限制的操作中调用了一个阻塞方法时,它能根据剩余时间来提供一个时限。
如果操作不能在指定的时间内给出结果,就会使程序提前结束。
当使用内置锁时,在开始请求锁后,这个操作将无法取消,因此内置锁很难实现带有时间限制的操作。
网站中,为寻找结果的 都创建了一个任务,包含某种基于网络的请求机制,例如Web服务请求,
在处理过程中同样需要对紧缺资源的独占 访问,例如通向公司的直连通信线路.
确保对资源进行串行访问的方法:
1. 一个单线程的 Executor。
2. 使用一个独占锁来保护对资源的访问.


浙公网安备 33010602011771号