流
如何从能够发送字节序列的任何数据源取得输入,如何将输出发送到能够接收字节序列的任何目的地;这些字节序列的源和目的地可以是文件、网络连接甚至是内存块;本质上讲,处理方法是相同的
Java中,一个可以读取字节序列的对象称为输入流,可以写入字节序列的对象称为输出流
专门处理Unicode字符的流对象类继承自抽象类Reader和Writer,它们的读写操作都是基于双字节的Unicode代码单元
InputStream类的read方法和OutputStream类的write方法都能阻塞线程:当流不能被立即读取或写入时,Java虚拟机会挂起这个调动线程,读/写方法进入等待,直到流再次可用
流读取或写入结束后,应调用close将其关闭,以便释放占用的操作系统资源,避免系统资源被耗尽;关闭输出流时也可以调用flush方法刷新输出流占用的缓存区,将临时存储在缓存区中等待形成较大数据包后再一起发送的那些字符序列立即发送出去
如果不关闭文件,最后一个字节包可能永远不会被发送,除了调用flush方法手动刷新输出
流结构:四个抽象类构成了流类的基础:InputStream,OutputStream,Reader,Writer;其它超过60个流类都是这四个类的子类,层次结构图略
通常,不用创建这些类的实例,而是通过其它的方法返回这些实例
流分层过滤器:Java中,可以将一个已经存在的流传递给另一个流的构造体,将这两个流结合起来,结合后的流被称为过滤流;过滤流具备这原先两个流的职责,并且仍然利用原有的流对象与字节源(或目的地)之间的联系读取(或写入)数据;装饰模式
数据流:DataInput、DataOutput接口
支持Java基本数据类型(数值、字符、布尔值、字符串)的读写
随机存取文件流:RandomAccessFile类
能够在文件的任何位置查找或写入数据,它同时实现了数据流的接口;实例化时通过构造体的第二次形参指定操作的类型(r→读,w→写,rw→读写)
随机存取文件提供文件指针,总是指向下一条要进行读写操作的位置;调用seek(long)方法能设定文件指针位置;getFilePointer方法能返回当前文件指针位置
文本流:通过Unicode字符进行流的读取和写入
InputStreamReader,OutputStreamWriter
FileReader,FileWriter
字符集:Charset类使用的是IANA字符集注册的标准字符集名
静态方法CharsetCharset.forName(String name)能够通关一个字符集的官方名称或任何一个别名获得这个名称的字符集Charset实例;获得的实例可用于某些类的encode(Charset)方法进行按该字符集编码,也可以用于某些类的decode(Charset)方法进行按该字符集解码;若按该字符集编码,有的Unicode字符无法显示,则转换为“?”
ZIP文件流:Java中,通常将一个FileInputStream实例传递给ZipInputStream的构造体,产生一个流过滤器对象来读取一个ZIP压缩文件
ZipInputStream类中的getNextEntry返回一个描述压缩文件每个独立条目的ZipEntry类型的对象;read方法读取该条目的内容,直到该条目末尾返回-1;随后必须调用closeEntry方法来读取下一条目;通常不使用read方法,而是使用更适宜的流过滤器中的方法
向ZIP文件中写入时,需要先创建一个ZipEntry对象,将需要压缩的文件名传入它的构造体;ZipOutputStream类中的putNextEntry方法接收这些ZipEntry对象,将它们写入ZIP文件中,每传递一个,需要调用closeEntry方法以指向下一个插入的位置
JAR文件也是ZIP文件,它通过JarInputStream和JarOutputStream读写它的框架条目
字符串记号处理器:StringTokenizer类
StringTokenizer类的构造体接收两个String参数:第一个是需要处理的字符串;第二个是分隔符组成的字符串,该字符串中的任意字符都可以作为分隔符
StringTokenizer类的hasMoreTokens方法表示是否还存在可用的记号;nextToken方法返回一个String类型的记号
StringTokenizer类的替代方案是String类的split(String)方法,它的参数也是分隔符组成的字符串;还可以使用正则表达式
对象流:ObjectOutputStream类提供writeObject方法,能将对象直接写入文件中
ObjectInputStream类提供readObject方法,能取回对象,但类型是Object,需要强制类型转换
对于对象整体而言,只能使用writeObject和readObject对其进行读写
对于任何需要在对象流中存储和恢复的类,都必须实现Serializable接口(标志接口),表示该类可以对象序列化
对象序列化可以自动解决一个对象在若干个其它对象中被共享的情况,所以不用担心一个共享对象在存储时被保存多份,即保证磁盘上的对象布局与内存中的对象布局相一致(持久性)
有些数据域不应该被序列化,可以用transient关键字标记它们,这样当对象被序列化时,标注为transient的域会被跳过
同时,可以在要被序列化的类中定义如下的两个方法:
private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException;
private void writeObject(ObjectOutputStream out) throws IOException;
这样,这个类的数据域在对象流的读写中不会自动进行序列化,而是调用这些方法进行对象的读写
上述的ObjectInputStream对象中有defaultReadObject方法,ObjectOutputStream对象中有defaultWriteObject方法,可以按照对象流中的readObject和writeObject方法序列化读写对象中的未标记为transient的数据域,但要求对象所在的类实现了Serializable接口
结合上述三点可以修改默认的序列化机制
另外,类可以通过实现Externalizable接口自定义对象读写机制,而不再需要对象序列化,此处略
类需要指出自己兼容于其早期版本,以避免类修改过之后SHA指纹发生改变导致对象流拒绝读取其早期版本的对象;当类中具有一个自动生成的public static final long serialVersionUID成员时,类将不再计算SHA指纹,而是将它作为不同版本的同一个类的唯一标识
对于不同版本的类对象,如果只有方法变了,不会影响数据域的读取;如果新的版本中有数据域添加,则该数据域在读取后会被置为null;如果新版本中有数据域被去除,则对应的数据被忽略
可以说,对于不需要对象流处理的类,上述版本相关的内容没有作用
将一个类的对象序列化到输出流(ByteArrayOutputStream)中,然后读回,就可以将一个可序列化的对象进行深拷贝克隆,但比显示构造新对象进行深拷贝克隆慢很多
文件管理
File类中封装了对用户机器的文件系统进行操作的功能;流类关注的是文件的内容,而File类关注的是文件在磁盘上的存储
File类的构造体:
publicFile(String filename); //根据完整的文件名创建File对象
public File(Stringpath, String filename); //根据目录名和文件名创建File对象
public File(Filedir, String filename); //根据目录的File对象和文件名创建File对象
File不仅能表示文件,也能表示目录,通过isFile或isDirectory方法判断是文件或者路径;表示目录的File对象可以调用list方法返回该目录下的所有文件名字符串数组
如果创建File对象时指定的文件(目录)名对应的文件(目录)不存在,该File对象仍然调用方法打印这个不存在的文件(目录)的路径,但是调用exists方法时将会返回false,表示不存在;同时,该File对象并不会为这个不存在的文件(目录)名创建一个新的文件(目录);创建这个不存在的文件需要调用createNewFile方法,创建不存在的目录需要调用mkdir方法
File类的其它方法参见Java API
新的I/O
Java 1.4引入的改进I/O处理的新特性,包含在java.nio包中
内存映射文件:大多数操作系统都可以利用虚拟内存将文件和文件的一段区域“映射”到内存中;内存映射文件的速度比缓冲和随机存取都要快;java.nio建立内存映射的步骤:
1. 为文件获取通道:FileInputStream、FileOutputStream、RandomAccessFile类中的FileChannel getChannel()方法
2. 通过FileChannel类的map方法获取缓冲区MappedByteBuffer
3. 一旦获取了缓冲区,就可以用ByteBuffer类和Buffer父类的方法进行读写数据
缓冲区数据结构:内存映射中建立一个横跨整个文件或者整个文件中一个感兴趣的区域的缓冲区;一个缓冲区是一个具有相同类型值的数组,如IntBuffer、ByteBuffer、CharBuffer、DoubleBuffer等,它们共同的父类是抽象类Buffer
一个缓冲区具有如下的特性:
1. 一个决不会改变的容量
2. 一个下一数值读取或写入的位置
3. 一个限制,超出限制的读写无意义
4. 可选的,一个重复进行读写操作的标志
文件锁定:多个程序同时需要对同一个文件进行修改时,为防止文件被破坏,需要文件锁控制对文件或者文件一部分字节的访问;FileChannel类的lock方法和tryLock方法可以锁定一个文件并返回FileLock类,FileLock类的release方法可以释放该锁
泛型
泛型类的定义:泛型类是指具有一个或多个类型变量的类;在类中,泛型对象声明在类名后,用尖括号括起
类型变量使用大写且较短;Java库中,E表示集合的元素类型,K和V分别表示表的关键字和值的类型,T(及U、S)表示任意类型
实例化泛型类时需要将类型变量指定为具体的类型
泛型方法:在普通类或泛型类中可以定义具有类型变量的方法;类型变量放在修饰符(public、static、final等)之后,方法返回类型之前,用尖括号括起
调用泛型方法时,需要在方法名前的尖括号中指定具体的类型;如果编译器有足够的信息可以推断出类型变量在当前方法中所代表的具体类型,上述添加可省略
类型变量的限定:声明类型变量时可以遵照如下的格式对类型变量进行限定:
<T extends BoundingType [& BoundingType2]>
表示类型变量T必须是绑定类型的子类型;T和BoundingType可以是类,也可以是接口,但关键字必须是extends;多个绑定类型可用&隔开(逗号“,”用于隔开多个类型变量)
泛型代码与虚拟机:虚拟机中没有泛型对象,所有的对象都来自普通类
无论何时定义一个泛型类型,相应的原始类型(即去掉了类型变量T等的泛型类型的名字,如T extends Comparable中的Comparable)都会被自动提供;在类或方法的内部,类型变量被擦除,并用其限制类型(若无,则用Object)替换
翻译泛型表达式:调用包含泛型对象的表达式时,编译器自动先将泛型对象按上述的限定类型处理,再按实际的类型参数进行强制类型转换
翻译泛型方法:某已经指定的具体类型变量的泛型类的子类中会出现多个同名方法(因为原型类型和指定后的类型的方法同时存在),此时需要采用桥方法保持该子类具有正确的多态性
约束与局限性:(大多数由类型擦除引起)
1. 基本数据类型不能作为类型参数,但它们的包装器类型可以
2. 所有的类型查询(instanceof或强制类型转换中的类型检测)都只产生原始类型
3. 不能抛出也不能捕获泛型类的对象,事实上泛型扩展Throwable类都不合法
4. 不能声明泛型对象的数组,因为数组必须记得数组元素的类型,而在虚拟机中,泛型对象的原始类型可能只是Object
5. 不能实例化仍含有类型变量(即类型参数未指定具体类)的泛型类
6. 静态域和静态方法中不能使用类型变量
7. 当泛型类型被擦除时,创建条件不能产生冲突
泛型类型的继承:两个具体泛型类中,即便它们的具体泛型类型存在继承关系,这两个泛型类本身也没有关系
通配符类型:通配符类型A<? extends B>表示类型参数是B的子类的任意泛型A类型
不同于固定类型参数的泛型类型,若C、D分别是B的子类,则A<C>和A<D>是A<? extends B>的子类,但是A<? extends B>的实例的访问器方法(如void setX(? extends B))是不安全的,会出现编译错误,即带有子类型限定的通配符不能作为参数,但是可以作为返回值
通配符的限定与类型变量的限定类似,但多了一个“超类型限定”:? super B,表示B的所有父类型,超类型限定的通配符可以作为参数,但不能作为返回值
无限定通配符<?>相当于<?extends Object>,?同样不能作参数,作返回值时也只能赋给Object类型的变量
此外,通配符并不是类型变量,所以在代码中不能将?作为类型使用;如果确实要用?作为类型,必须用真正的类型变量(T等)捕获?
反射与泛型:Class<T>
通过类型变量T来使返回的类型更具针对性,省去了强制类型转换
集合
集合接口:迭代器接口Iterator<E>:next、hasNext、remove方法
Iterable<E>接口:iterator方法,返回一个Iterator对象
Collection<E>接口:继承自Iterable<E>接口
具体的集合:Collection:接口,集合,可迭代
├ List:接口,列表,有序(元素位置重要)
│ ├ ArrayList:类,动态数组
│ ├ LinkedList:类,双向链表
│ └ Vector:类,旧版本的动态数组,线程安全
│ └ Stack:类,栈
├ Set:接口,集合,无序(随机访问)
│ ├ HashSet:类,散列表
│ └SortedSet,接口,可排序(比较)
│ └ TreeSet:类,红黑树
└ Queue:接口,队列
└PriorityQueue:类,优先级队列,最大堆
Map:接口,映射表
├ HashMap:类,散列映射表,key或value可为null
├ TreeMap:类,树形映射表,红黑树
└ Hashtable:类,旧版本的散列映射表,线程安全,key或value不可为null
视图与包装器:包装器方法可以将一个集合对象按照特定的要求转换成另一个集合,转换后的结果叫视图
1. Arrays类的静态方法asList可以将一个普通数组转换成一个List视图,该视图可修改,但大小不能改变
2. List、SortedSet、Map对象调用它的subX、headX、tailX方法可以获取它的子范围视图
3. Collections类拥有一套静态方法,可以构建某集合的不可修改视图,形如unmodifiableX(如static <E> List unmodifiableList(List<E>);),对视图进行修改会抛出UnsupportedOperationException
4. Collections类拥有一套静态方法,可以构建某集合的同步视图,形如synchronizedX(如static <K, V> Map synchronizedMap(Map<K, V>);),可以自动赋予视图同步锁使其线程安全,也因此废弃了Vector、Hashtable等线程安全的集合
5. Collections类拥有一套静态方法,可以构建某集合的被检验视图,形如checkedX(如static <E> List checkedList(List<E>, Class<E>);),会对视图中新插入元素的类型进行检验,若与类型参数不匹配则会抛出ClassCastException