Java核心技术读书笔记-输入与输出
IO流
InputStream与OutputStream设计的目的是处理字节流的数据;而Reader和Writer是专门用于处理Unicode字符的类层次结构。
read和write方法在执行时都将阻塞,直至字节确实被写入或写出,这就意味着如果流不能立即被访问(网络问题,IO异常),那么当前线程将被阻塞。
调用close方法关闭输出流时,所有临时被缓存在缓冲区的数据都会用一个更大的包的形式送出。
jar与 zip 唯一的区别就是在 jar 文件的内容中,包含了一个 META-INF/MANIFEST(货单).MF 文件。该文件是在生成 jar 文件的时候自动创建的,作为jar里面的"详情单",包含了该Jar包的版本、创建人和类搜索路径Class-Path等信息,当然如果是可执行Jar包,会包含Main-Class属性,表明Main方法入口。
Closeable拓展了AutoCloseable,因此实现了Closeable的接口都可以使用try-with-resoure语句。
结尾以able结尾的,都是代表一些功能的接口。
Lambda表达式写的比较表达式,实际上是实现了Comparator接口,而Comparable接口需要在类中实现。前者参数为(x, y)用于排序等方法,后者用于两者比较大小。
CharBuffer类拥有按顺序或者随机地进行读写访问内存的方法。
Appendable接口用于追加单个字符或者字符序列,实现这个接口的类,这个接口的方法在实现时,需要保证线程安全。
CharSequence接口,即为字符序列,String、CharBuffer、StringBuilder等类都实现了它,算是前身。
Java通过组合输入/输出流层层嵌套来为流增加功能,同时,经过测试,DataOutputStream的方法,需要注意不要传非字符串,因为编码后读取会出现异常,比如1.22它会读成乱码。Java的默认字符编码格式是UTF-16,而如果要设置流的编码格式,需要使用Reader和Writer的大类。
PrintWriter底层并没有实现OutputStream,而是组合了OS的功能。而在实际的应用中,通过PW拓展OS的功能即可,最后传输的效果都是一致的。
Scanner和PrintWriter是IO流更新迭代后的产物,比原始的IO流有着更加方便的文件读写功能。
字节源的字节可能来自世界上的任何国家或地区,因此,应该总是明确指定编码方式。Springboot接受Web请求时,会根据网页的content-type来修改HttpServletRequest的UTF-8默认编码格式来解析数据。
字符编码格式是相对于字符流而言的,因为字符流解析字符需要编码格式,要摒弃思维惯性字节流在非转换为字符时没有必要、也无法设置编码格式。
码点>码元,一个码点可能由1-2个码元组成。str.length()返回的是码元的个数,即char类型的个数。
63-67页,RandomAccessFile类可以实现文件的随机读写,这个类有一个表示下一个将被读入或者写出的字节所处位置的文件指针,通过指针可以在文件的任意位置查找或写入数据。与String不同的是,它的length()方法返回的是字节总数。
//read zip try (var zin = new ZipInputStream(new FileInputStream(zipFilePath))){ ZipEntry entry; while ((entry = zin.getNextEntry()) != null){ entry.getName(); zin.closeEntry(); } } //write zip File[] files = new File[0]; try(var zout = new ZipOutputStream(new FileOutputStream(zipFilePath + "1.zip"))) { for (var file : files) { var zipEntry = new ZipEntry(file.getPath()); zout.putNextEntry(zipEntry); zout.closeEntry(); } }
ZipFile可以不通过流直接读Zip文件,但不能写。
序列化
Java中实现序列化只需要实现Serializable接口,这个接口中没有任何抽象方法。
Object流是序列化流。B对象中包含了A对象,C对象中也包含了A对象,那么对A、B、C这三个对象的序列化就是,A对象序列化的序列号为1,B中存储了A对象的序列号1,记为序列号2同理C也是如此。采用序列号而不采用内存地址的原因是,序列化的目的是为了将对象存储到本地硬盘或者传输给其他的主机,此时对象的内存地址指向的就不再是这个对象了,所以只能采用序列号的方式。
transient(转瞬即逝的)作用于域(即类属性),被 transient修饰的变量不参与序列化和反序列化。用于避免敏感信息序列化存储。
static静态变量是不会参与序列化的。
79-82页,序列化的坑和难点
实现了Serializable的类,可以重写writeObject和readObject来修改默认的序列化机制。
Externalizable 接口继承了 Serializable 接口,所以实现 Externalizable 接口也能实现序列化和反序列化。Externalizable 接口中定义了 writeExternal 和 readExternal 两个抽象方法,这两个方法其实对应Serializable接口的 writeObject 和 readObject 方法。可以这样理解: Externalizable 接口被设计出来的目的就是为了抽象出writeObject 和 readObject 这两个方法,但是目前这个接口使用的并不多。实际上这两个接口的作用是一模一样的。
如果需要维护遗留的枚举代码(私有构造函数的自创枚举)或单例设计模式的代码,总而言之就是如果目标对象是唯一的,在序列化时,默认的序列化机制是不适用的。因为尽管构造函数是私有的,反序列化后仍会创建出一个新的对象,那么就造成了单例模式下却存在两个不同的对象,为了解决这个问题,需要自己重写readResolve()方法。
对象输入流会拒绝读入具有不同指纹的对象,当类进行版本更新后,指纹就会变化。这时,就需要设置固定的序列化指纹,通过自定义readObject订正不兼容问题。参考82-84页。
通用深拷贝可以通过序列化来实现,参考84。
文件操作
IO流关注的是文件的内容,而Path和Files关注的是文件在磁盘上的存储。Path和Files类比起File类要方便的多。大多本地文件的读写操作都可以用Files类完成不再需要使用输入/输出流或者读入/写入器。
File是最开始的文件操作类,但是在Java 7时,被Path和Files类完成了功能拆分。
类Unix系统的路径分隔符用/,Windows的路径分隔符用\。
如果开头是跟部件,那么就是绝对路径;否则就是相对路径,也就是说不需要./
Path和Paths主要是处理文件路径的问题。
Files.copy(),调用copy方法时,最好添加StandardCopyOption,原子性、复制所有文件属性及若存在则替代。
walk方法遍历目录树,所以会遍历所有的子目录;list方法仅读取第一层子目录。
通过调用newDirectoryStream方法可以获得一个DirectoryStream集合,相比walk方法可以对文件有更细粒度的控制,DirectoryStream是Iterable的子接口,并不是Stream的子接口,它可以进行增强for循环。同时还可以利用glob模式高效的过滤文件。P96
增强自己阅读源码的能力,如果出现了BUG,不要急着上网问,先看堆栈记录,至底向上一步步看,绝对能看懂问题出现的原因。
FileSystem文件系统可以在不解压zip文件的情况下遍历zip文件。
内存映射文件
NIO (New lO)也有人称之为java non-blocking lO是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java lO API。NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的、基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作。NIO可以理解为非阻塞IO,传统的IO的read和write只能阻塞执行,线程在读写IO期间不能干其他事情,比如调用socket.read()时,如果服务器一直没有数据传输过来,线程就一直阻塞,而NIO中可以配置socket为非阻塞模式。
多数操作系统都可以利用虚拟内存实现将一个文件或者文件的一部分"映射"到内存中。然后,这个文件就可以当作是内存数组来访问,这比传统的文件要快得多。所以叫缓冲区,因为是缓存文件到缓冲区。
Java中通过建立FileChannel类,利用map方法将文件映射到内存中,同时指定映射的文件区域和映射模式。
99-108,讲了NIO中的Channel和Buffer,讲得不深,但够用。
mark:可选的标记,用于重复一个读入或者写出操作。
position:读写位置,下一个值将在此进行读写。
limit:界限,超过它进行读写是没有意义的。
capacity:容量,它永远不能改变。
当多个线程同时修改同一个文件时,需要对文件加锁以保护文件不会损坏。注意,在某些系统中,文件加锁是建议性的,没有强制性;在某些系统中,加锁的文件不能映射到内存中;文件锁是由整个JVM持有的,因此多个Java应用程序只能有其中一个获得同一个文件的文件锁;在同一个锁定文件上尽量避免使用多个通道。
文件锁最好使用try-with-resource语句,自动释放文件锁。
Pattern(模式)和Matcher(匹配器)是正则表达式的应用类。