第18章 Java I/O系统
18.1 File类
File类不仅仅指文件,还能代表一个目录下的一组文件。
18.1.1 目录列表器
public class Test {
public static void main(String[] args) {
File file = new File("E:\\test");
String[] strings = file.list(new DirFilter(".*\\.java"));
// 忽略大小写排序
Arrays.sort(strings,String.CASE_INSENSITIVE_ORDER);
for (String string : strings) {
System.out.println(string);
/*
test.java
user.java
*/
}
}
}
// Filename过滤器
class DirFilter implements FilenameFilter{
private Pattern pattern;
public DirFilter(String regex){
pattern = pattern.compile(regex);
}
// File.list()方法为每一个文件调用该方法
@Override
public boolean accept(File dir, String name) {
return pattern.matcher(name).matches();
}
}
// 使用匿名内部类代替实现类
static FilenameFilter f(final String /*必须是final的*/regex){
return new FilenameFilter() {
private Pattern pattern = Pattern.compile(regex);
@Override
public boolean accept(File dir, String name) {
return pattern.matcher(name).matches();
}
};
}
public static void main(String[] args) {
File file = new File("E:\\test");
// 直接使用匿名内部类
String[] strings = file.list(new FilenameFilter() {
Pattern pattern = Pattern.compile(".*\\.java");
@Override
public boolean accept(File dir, String name) {
return pattern.matcher(name).matches();
}
});
Arrays.sort(strings,String.CASE_INSENSITIVE_ORDER);
for (String string : strings) {
System.out.println(string);
/*
test.java
user.java
*/
}
}
18.1.2 目录实用工具
public final class Directory {
// 产生由指定文件构成的数组
public static File[] local(File dir,final String regex){
return dir.listFiles(new FilenameFilter() {
Pattern pattern = Pattern.compile(regex);
@Override
public boolean accept(File dir, String name) {
return pattern.matcher(name).matches();
}
});
}
// 重载方法,接收一个文件地址
public static File[] local(String path,String regex){
return local(new File(path),regex);
}
public static class TreeInfo implements Iterable<File>{
// 为什么要设置为public的?
// walk()方法访问的是对象元组,它包含了两个List,可以让客户端选择使用哪一个
// 目录集合
public List<File> dirs = new ArrayList<>();
// 文件集合
public List<File> files = new ArrayList<>();
@Override
public Iterator<File> iterator() {
return files.iterator();
}
// 增加其他对象的集合
public void addAll(TreeInfo other){
files.addAll(other.files);
dirs.addAll(other.dirs);
}
public static TreeInfo walk(File start, String regex){
return recurseDirs(start,regex);
}
public static TreeInfo walk(String path,String regex){
return recurseDirs(new File(path),regex);
}
public static TreeInfo walk(File start){
return recurseDirs(start,".*");
}
@Override
public String toString() {
return "目录:"+print(dirs)+"\n文件:"+print(files);
}
// 格式化输出集合
// 针对长度为0和1的集合做了特俗处理
private static <T>String print(Collection<T> c){
if (c.size() == 0){
return "[]";
}else {
StringBuilder sb = new StringBuilder("[");
for (T t : c) {
if (c.size() != 1){
sb.append("\r\n");
}
sb.append(t);
}
if (c.size() != 1){
sb.append("\r\n");
}
sb.append("]");
return sb.toString();
}
}
// 核心的处理方法
private static TreeInfo recurseDirs(File starDir, String regex){
TreeInfo result = new TreeInfo();
for (File file : starDir.listFiles()) {
// 判断是否是文件夹
if (file.isDirectory()) {
result.dirs.add(file);
// 递归循环
result.addAll(recurseDirs(file,regex));
}else {
if (file.getName().matches(regex)){
result.files.add(file);
}
}
}
return result;
}
}
}
public static void main(String[] args) {
File[] files = Directory.local("E:\\test",".*java");
System.out.println(Arrays.toString(files));
// [E:\test\test.java, E:\test\user.java]
Directory.TreeInfo treeInfo = Directory.TreeInfo.walk(new File("E:\\test"));
System.out.println(treeInfo);
/*
目录:[E:\test\com]
文件:[
E:\test\a.txt
E:\test\test.java
E:\test\user.java
]
*/
}
在目录中穿行,并处理指定的文件。
// 遍历文件中的文件,并可根据Strategy对其进行处理
public class ProcessFiles {
public interface Strategy{
void process(File file);
}
// 策略
private Strategy strategy;
// 后缀
private String ext;
public ProcessFiles(Strategy strategy, String ext) {
this.strategy = strategy;
this.ext = ext;
}
public void start(String[] paths) throws IOException {
// 如果不含参数,默认为当前文件目录
if (paths.length == 0){
processDirectoryTree(new File("."));
}else {
// 遍历传入地址
for (String path : paths) {
File file = new File(path);
// 判断是否为文件目录
if (file.isDirectory()){
processDirectoryTree(file);
}
// 文件直接处理
else {
// 为什么要这样处理?岂不是每个文件都会被处理了
if (!path.endsWith("."+ext)){
path += "."+ ext;
}
File canonicalFile = new File(path).getCanonicalFile();
strategy.process(canonicalFile);
}
}
}
}
// 文件路径解决方案
private void processDirectoryTree(File rootFile) throws IOException {
Directory.TreeInfo treeInfo = Directory.TreeInfo.walk(rootFile./*获取绝对路径*/getAbsolutePath(),".*\\."+ext);
for (File file : treeInfo.files) {
// 为什么不直接传入File?
//区别是该方法内的获得规范路径:getCanonicalPath(),它会返回文件的绝对路径,并替换掉各操作系统相应的符号
strategy.process(file.getCanonicalFile());
}
}
}
public static void main(String[] args) throws IOException {
ProcessFiles processFiles = new ProcessFiles(new ProcessFiles.Strategy() {
@Override
public void process(File file) {
System.out.println(file);
}
},"java");
String[] paths = {"E:\\test"};
processFiles.start(paths);
}
/*
E:\test\com\home.java
E:\test\test.java
E:\test\user.java
*/
18.1.3 目录的检查及创建
public class Test {
public static void main(String[] args) throws IOException {
File dir = new File("e:/test/b");
// 创建文件夹
dir.mkdirs();
File file = new File("e:/test/b/a.txt");
// 创建新文件
file.createNewFile();
File oldFile = new File("e:\\test\\a.txt");
File newFile = new File("e:\\test\\b.txt");
// 将旧文件移动到新文件,并改名
oldFile.renameTo(newFile);
fileData(newFile);
}
private static void fileData(File file) throws IOException {
System.out.println("绝对地址:"+ file.getAbsolutePath());
System.out.println(file.getCanonicalPath());
System.out.println("是否可读:"+file.canRead());
System.out.println("是否可写:"+file.canWrite());
System.out.println("文件名:"+file.getName());
System.out.println("上级目录:"+file.getParent());
System.out.println("地址:"+file.getPath());
System.out.println("大小:"+file.length());
System.out.println("最后修改时间:"+file.lastModified());
System.out.println("是否为文件:"+file.isFile());
System.out.println("是否为目录:"+file.isDirectory());
System.out.println("是否存在:"+file.exists());
}
/*
绝对地址:e:\test\b.txt
E:\test\b.txt
是否可读:true
是否可写:true
文件名:b.txt
上级目录:e:\test
地址:e:\test\b.txt
大小:2
最后修改时间:1605599850616
是否为文件:true
是否为目录:false
是否存在:true
*/
}
18.2 输入和输出
流 任何有能力产出数据的数据源对象或有能力接收数据的接收端对象。
输入流: InputStream Reader,表示从不同数据源产生输入的类。
输出流: OutputStream Writer,决定了输出要去往的目标。
数据源:
- 字节数组;
- String对象;
- 文件;
- 管道 从一段输入,从另一端输出;
- 多个流组成的序列;
- 其他数据源。
InputStream
OutputStream
18.3 添加属性和有用的接口
FilterInputStream和FilterOutputStream用来提供装饰器类接口以控制特定输入流和输出流。
18.4 Reader和Writer
Reader和Writer在JDK1.1中出现,提供兼容Unicode与面向字符的I/O功能,其比旧的I/O更快,其设计的主要目的是国际化。
适配器类InputStreamReader可以把InputStream转为Reader。
字节流和字符流对应关系
与FilterInputStream和FilterOutputStream对应的类
未发生变化的类
18.5 自我独立的类:RandomAccessFile
其与字节流没有任何关系,直接继承自Object类,方法大多是本地的,适用于大小已知的记录文件。
18.6 I/O流的典型使用方式
字节数组接收数据
public static void main(String[] args) throws IOException {
File file = new File("e:/test/a.txt");
InputStream in = new FileInputStream(file);
byte[] bytes = new byte[512];
int len; // 读取的字节数
while ( (len = in.read(bytes)) != -1){
// 将字节数组转为字符串,字节数组的起始位置为0,长度为len
System.out.print(new String(bytes,0,len));
}
in.close();
}
缓冲输入文件
public static void f(String path) throws IOException {
// 为了提升速度,使用缓冲读取文件
// 编码只有在InputStreamReader/OutputStreamWriter才有
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(path),"gbk"/*指定编码*/));
StringBuilder sb = new StringBuilder();
String s ;
// 读取一行,并判断是否为null
while ((s = br.readLine()) != null){
// 注意:这里需要换行,因为readLine()将换行符去掉了
sb.append(s).append("\n");
}
// 关闭读取流
br.close();
System.out.println(sb.toString());
}
从内存输入
public static void main(String[] args) throws IOException {
// 读取字符串
StringReader sr = new StringReader("呵呵\nhello");
int c;
// 每次读取一个字节
while ((c = sr.read()) != -1){
System.out.print((char)c);
}
}
格式化的内存输入
public static void main(String[] args) throws IOException {
InputStream in = new ByteArrayInputStream("12.3ae".getBytes());
DataInputStream di = new DataInputStream(in);
try {
while (true) {
System.out.println((char) di.readByte());
}
} // 通过异常结束输出
catch (EOFException e) {
System.out.println("输出完毕!");
}
}
public static void main(String[] args) throws IOException {
InputStream in = new ByteArrayInputStream("12.3ab".getBytes());
DataInputStream di = new DataInputStream(in);
while (/*查看还有多少可供读取的数*/di.available() != 0) {
System.out.println((char)di.readByte());
}
}
基本的文件输出
public static void main(String[] args) throws IOException {
// 通过使用缓冲提高速度
PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter("e:/test/c.txt")));
for (int i = 0; i < 5; i++) {
// write()会转为ascii输出
// print()会原样输出
pw.println("这是第"+i+"行");
}
pw.close();
}
public static void main(String[] args) throws IOException {
File file = new File("e:/test/e.txt");
// 快捷方式,减少装饰工作,仍会进行缓冲
PrintWriter pw = new PrintWriter(file);
for (int i = 0; i < 5; i++) {
pw.println(i);
}
pw.close();
}
储存和恢复数据
public static void main(String[] args) throws IOException {
DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("e:/test/a.txt")));
out.writeInt(12);
out.writeDouble(12.5);
// java针对UTF-8的一种变体
out.writeUTF("丁");
out.close();
DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream("e:/test/a.txt")));
System.out.println(in.readInt());
System.out.println(in.readDouble());
System.out.println(in.readUTF());
in.close();
}
// 问题:存储和读取数据顺序必须一致
读写随机访问文件
public class Test {
static String path = "e:/test/a.txt";
public static void main(String[] args) throws IOException {
RandomAccessFile ra = new RandomAccessFile(path, "rw");
for (int i = 0; i < 5; i++) {
// 写
ra.writeDouble(i * 1.44);
}
ra.writeUTF("末尾");
ra.close();
f(path);
/*
0.0
1.44
2.88
4.32
5.76
末尾
*/
ra = new RandomAccessFile(path, "rw");
// 跳到某个位置
// double是8字节,2*8表示跳到第2个数的末尾,第3个数的起点
ra.seek(2 * 8);
// 这里会覆盖第3个double数
ra.writeDouble(47.47);
ra.close();
f(path);
/*
0.0
1.44
47.47
4.32
5.76
末尾
*/
}
public static void f(String path) throws IOException {
// r:只读 rw:读写
RandomAccessFile ra = new RandomAccessFile(path, "r");
for (int i = 0; i < 5; i++) {
// 读
System.out.println(ra.readDouble());
}
System.out.println(ra.readUTF());
}
}
其他
public static void main(String[] args) throws IOException, InterruptedException {
File file = new File("e:/test/a.txt");
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file,/*追加而不是重写*/true));
out.write("呵呵".getBytes());
// 刷新缓冲区,将数据写入到基础流中(写入文本中)
out.flush();
}
18.7 文件读写的实用工具
读写文本文件
/ 实现读写文本文件,并将读取的文件以固定形式分割放入List中
public class TextFile extends ArrayList<String> {
// 读取
public static String read(String fileName) {
StringBuilder sb = new StringBuilder();
try (BufferedReader br = new BufferedReader(new FileReader(fileName))) {
String s;
while ((s = br.readLine()) != null) {
sb.append(s).append("\n");
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return sb.toString();
}
// 写
public static void write(String fileName, String txt, boolean append) {
try (PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(fileName, append)))) {
pw.print(txt);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public TextFile(String fileName,String regex){
// 以固定形式分割文件并读到List中
super(Arrays.asList(read(fileName).split(regex)));
if ("".equals(get(0))) {
remove(0);
}
}
// 默认以换行分割
public TextFile(String fileName){
this(fileName,"\n");
}
// 将List中的元素写入到文件中
public void write(String fileName,boolean append){
try (PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(fileName,append)))){
this.forEach(s ->pw.println(s));
}catch (IOException e){
throw new RuntimeException(e);
}
}
}
public static void main(String[] args) throws IOException {
TextFile textFile = new TextFile("e:/test/a.txt","\\d");
System.out.println(textFile);
textFile.addAll(Arrays.asList("4","e"));
textFile.write("e:/test/a.txt",false);
}
读取二进制文件
// 读取二进制文件
public class BinaryFile {
public static byte[] read(String fileName) {
try (
BufferedInputStream in = new BufferedInputStream(new FileInputStream(fileName))
) {
// 创建指定大小的字节数组
// 问题:数组可能会巨大
byte[] bytes = new byte[in.available()];
in.read(bytes);
return bytes;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
18.8 标准I/O
从标准输入中读取
按照标准模型,Java提供了System.in、System.out、Systeim.err。
out和err被包装成了printStream,但是in没有,需要我们包装。
// 打印我们输入的信息
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String s;
while ((s = br.readLine()) != null && s.length() > 0){
System.out.println(s);
}
}
public static void main(String[] args) throws IOException {
BufferedInputStream in = new BufferedInputStream(System.in);
byte[] bytes = new byte[128];
int len;
while ((len = in.read(bytes)) != -1){
String s = new String(bytes,0,len);
// 至少有一个回车符
if (s.length() == 1)
System.exit(0);
System.out.println(s);
}
}
将System.out转换成PrintWriter
public static void main(String[] args) throws IOException {
// 开启自动清空缓存
PrintWriter pw = new PrintWriter(System.out,true);
pw.println("呵呵");
}
标准I/O重定向
// 重定向输出
public static void out() throws FileNotFoundException {
OutputStream out = new FileOutputStream("e:/test/a.txt");
System.setOut(new PrintStream(out));
System.out.println("你好");
}
// 重定向输入
public static void in() throws IOException {
InputStream in = new FileInputStream("e:/test/a.txt");
System.setIn(in);
byte[] bytes = new byte[128];
System.in.read(bytes);
System.out.println(new String(bytes));
}
18.9 进程控制
在Java内部执行操作系统的其他程序,并控制这些程序的输入和输出。
public static void main(String[] args) throws IOException {
// 进程构建
ProcessBuilder processBuilder = new ProcessBuilder(/*运行命令*/"java Test".split(" "));
// 获得运行时环境
Map<String, String> environment = processBuilder.environment();
environment.put("CLASSPATH","E:\\test");
// 运行进程
Process process = processBuilder.start();
// 获得进程的输出(对于该程序是输入)
BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
String s;
while ((s = br.readLine()) != null){
System.out.println(s);
}
}
18.10 新I/O
JDK1.4中引入新I/O,其目的是提高速度。
相关概念:
通道 包含数据的地方
缓冲器 与通道交互
旧I/O中的FileInputStream/FileOutputStream/RandomAccessFile可以直接获取通道;
字符流不能产生通道,Channels类提供了在通道中产生Reader和Writer的方法。
public class Test {
public static int SIZE = 1024;
public static void main(String[] args) throws IOException {
// 获得通道
FileChannel fc = new FileOutputStream("e:\\test\\a.txt").getChannel();
// wrap(byte[])将字节数组包装为ByteBuffer
fc.write(ByteBuffer.wrap("hello nio,".getBytes()));
fc.close();
File file;
fc = new RandomAccessFile("e:\\test\\a.txt", "rw").getChannel();
// 移动到文件最后
fc.position(fc.size());
fc.write(ByteBuffer.wrap("append".getBytes()));
fc.close();
fc = new FileInputStream("e:\\test\\a.txt").getChannel();
// allocateDirect()返回与操作系统更高耦合的直接缓冲器,速度可能更快
ByteBuffer byteBuffer = ByteBuffer.allocate(SIZE);
// 读取到缓冲器中
fc.read(byteBuffer);
// 做好让别人读取的准备
byteBuffer.flip();
while (/*判断是否还有字节*/byteBuffer.hasRemaining()){
System.out.print((char) byteBuffer.get());
}
}
}
复制文件
public class Test {
public static int SIZE = 8;
public static void main(String[] args) throws IOException {
// 读
FileChannel in = new FileInputStream("e:\\test\\a.txt").getChannel();
// 写
FileChannel out = new FileOutputStream("e:\\test\\b.txt").getChannel();
ByteBuffer buffer = ByteBuffer.allocate(SIZE);
while (in.read(buffer) != -1){
// 准备被读取
buffer.flip();
// 写
// 下次循环会追加
out.write(buffer);
// 清空缓冲区,为下次读作准备
buffer.clear();
}
}
}
public class Test {
public static int SIZE = 8;
public static void main(String[] args) throws IOException {
// 读
FileChannel in = new FileInputStream("e:\\test\\a.txt").getChannel();
// 写
FileChannel out = new FileOutputStream("e:\\test\\b.txt").getChannel();
// 专门的复制文件
// in从0开始,size()长度,复制到out文件
in.transferTo(0,in.size(),out);
// out文件来自于in,从0开始,size()长度
out.transferFrom(in,0,in.size());
}
}
18.10.1 转换数据
前边读取文件时存在的一个问题:每次只读一个字节的数据。
ByteBuffer可以通过asCharBuffer()转为CharBuffer,CharBuffertoString()为字符串。
public class Test {
public static int SIZE = 1024;
// 读取字符
public static void main(String[] args) throws IOException {
FileChannel channel = new FileInputStream("e:\\test\\a.txt").getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(SIZE);
channel.read(byteBuffer);
byteBuffer.flip();
System.out.println(byteBuffer.asCharBuffer()); // 乱码
byteBuffer.rewind(); // 重置位置
String encode = System.getProperty("file.encoding"); // 获得程序的编码
System.out.println(encode); // UTF-8
// 使用该文件的编码对缓冲器解码
System.out.println(Charset.forName("GB2312").decode(byteBuffer));
channel.close();
// 存入时编码
channel = new FileOutputStream("e:/test/a.txt").getChannel();
// 这里使用是UTF-16,UTF-16BE,utf-8不可以;另一种utf-16LE也不可以
channel.write(ByteBuffer.wrap("福".getBytes("UTF-16BE")));
channel.close();
channel = new FileInputStream("e:\\test\\a.txt").getChannel();
byteBuffer.clear();
channel.read(byteBuffer);
byteBuffer.flip();
System.out.println(byteBuffer.asCharBuffer());
byteBuffer.clear();
channel.close();
// 存入CharBuffer
channel = new FileOutputStream("e:/test/a.txt").getChannel();
byteBuffer.asCharBuffer().put("景");
channel.write(byteBuffer);
byteBuffer.clear();
channel.close();
channel = new FileInputStream("e:\\test\\a.txt").getChannel();
channel.read(byteBuffer);
byteBuffer.flip();
System.out.println(byteBuffer.asCharBuffer());
}
}
编码
public static void main(String[] args) throws IOException {
Charset defaultCharset = Charset.defaultCharset(); // 默认编码
SortedMap<String, Charset> charsets = Charset.availableCharsets(); // 支持的所有编码
Charset charset = Charset.forName("UTF-8"); // 获得指定编码
ByteBuffer byteBuffer = charset.encode("嗨"); // 编码
CharBuffer charBuffer = charset.decode(byteBuffer); // 解码
System.out.println(charBuffer); // 嗨
}
18.10.2 获取基本类型
public class Test {
public static int SIZE = 1024;
public static void main(String[] args) throws IOException {
ByteBuffer byteBuffer = ByteBuffer.allocate(SIZE);
int i = 0;
// 检查缓冲器内容是否都置为0了
while (i++ < byteBuffer.limit()) {
if (byteBuffer.get() != 0) {
System.out.println("缓冲器内容没有置为0");
}
}
System.out.println("i:" + i);
byteBuffer.rewind();
byteBuffer.asCharBuffer().put("abc");
char c;
while ((c = byteBuffer.getChar()) != 0) {
System.out.print(c + " ");
}
byteBuffer.rewind();
byteBuffer.asIntBuffer().put(123);
System.out.println("整型:" + byteBuffer.getInt());
byteBuffer.rewind();
byteBuffer.asDoubleBuffer().put(12.3);
System.out.println("浮点:" + byteBuffer.getDouble());
byteBuffer.rewind();
byteBuffer.asShortBuffer().put((short) 12);
System.out.println("shot:" + byteBuffer.getShort());
byteBuffer.rewind();
byteBuffer.asLongBuffer().put(145L);
System.out.println("long:" + byteBuffer.getLong());
byteBuffer.clear();
}
}
/*
i:1025
a b c 整型:123
浮点:12.3
shot:12
long:145
*/
18.10.3 视图缓冲器
视图缓冲器 让我们通过某个特定的基本类型的视窗查看底层的ByteBuffer,但是底层仍然是Bytebuffer,对视窗的修改都会映射到底层中。
视图基本示例
public class Test {
public static int SIZE = 1024;
public static void main(String[] args) throws IOException {
ByteBuffer byteBuffer = ByteBuffer.allocate(SIZE);
// 获得整型视图
IntBuffer intBuffer = byteBuffer.asIntBuffer();
// put数组
intBuffer.put(new int[]{1,2,3,4,5});
// 获得指定位置的值
System.out.println(intBuffer.get(2)); // 3
// 修改
intBuffer.put(1,10);
intBuffer.flip();
while (intBuffer.hasRemaining()){
System.out.print(intBuffer.get()+" "); // 1 10 3 4 5
}
}
}
各种视图中转换
public static void main(String[] args) throws IOException {
ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[]{0,0,0,0,0,0,0,'a'});
while (byteBuffer.hasRemaining())
// 0->0 1->0 2->0 3->0 4->0 5->0 6->0 7->97
System.out.print(byteBuffer.position()/*地址*/+"->"+byteBuffer.get()+" ");
System.out.println();
byteBuffer.rewind();
// int
IntBuffer intBuffer = byteBuffer.asIntBuffer();
while (intBuffer.hasRemaining()){
// 0->0 1->97
System.out.print(intBuffer.position()/*地址*/+"->"+intBuffer.get()+" ");
}
System.out.println();
intBuffer.rewind();
// double
DoubleBuffer doubleBuffer = byteBuffer.asDoubleBuffer();
while (doubleBuffer.hasRemaining()){
// 0->4.8E-322
System.out.print(doubleBuffer.position()/*地址*/+"->"+doubleBuffer.get()+" ");
}
System.out.println();
doubleBuffer.rewind();
// char
CharBuffer charBuffer = byteBuffer.asCharBuffer();
while (charBuffer.hasRemaining()){
// 0-> 1-> 2-> 3->a
System.out.print(charBuffer.position()/*地址*/+"->"+charBuffer.get()+" ");
}
System.out.println();
charBuffer.clear();
}
各基本类型对应结果
字节存放次序
高位字节 其值变化后使整个字的值变化最大的那个字节。
高位优先 将高位字节放在地址最低的储存单元上。
低位优先 将高位字节放在地址最高的储存单元上。
public static void main(String[] args) throws IOException {
ByteBuffer byteBuffer = ByteBuffer.allocate(12);
byteBuffer.asCharBuffer().put("abcdef");
System.out.println(Arrays.toString(byteBuffer.array()));
byteBuffer.rewind();
// 设置高位优先
byteBuffer.order(ByteOrder.BIG_ENDIAN);
byteBuffer.asCharBuffer().put("abcdef");
System.out.println(Arrays.toString(byteBuffer.array()));
byteBuffer.rewind();
// 设置低位优先
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
byteBuffer.asCharBuffer().put("abcdef");
System.out.println(Arrays.toString(byteBuffer.array()));
}
/*
[0, 97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102]
[0, 97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102]
[97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102, 0]
*/
// char是两个字节,可以看到,高位优先时a是0,97;低位优先时是97,0
18.10.4 用缓冲器操纵数据
ByteBuffer是数据进出通道的唯一方式,基本类型的缓冲器不能转为ByteBuffer,但是基本类型的缓冲器可以操作底层的ByteBuffer,所以可以认为没有任何限制。
nio之间的关系
18.10.5 缓冲器的细节
Buffer由数据和四个索引组成。
索引
- mark:标记;
- position:位置;
- limit:界限;可以读取到的最大位置
- capacity:容量;缓冲器最大可以装的数据
相关方法
public class Test {
public static void main(String[] args) throws IOException {
char[] chars = "UsingBuffers".toCharArray();
ByteBuffer byteBuffer = ByteBuffer.allocate(chars.length * 2);
CharBuffer charBuffer = byteBuffer.asCharBuffer();
/*可以通过CharBuffer的wrap()方法产生一个CharBuffer,但是这里的
底层仍然使用了ByteBuffer,因为是ByteBuffer唯一可以与通道交互的
*/
// charBuffer = CharBuffer.wrap(chars);
// 此时位置移动到了末尾
charBuffer.put(chars);
// 要将位置移动到开始,才能输出所有值
System.out.println(charBuffer.rewind()); // UsingBuffers
f(charBuffer); // 交换
System.out.println(charBuffer.rewind()); // sUniBgfuefsr
f(charBuffer);
System.out.println(charBuffer.rewind()); // UsingBuffers
}
// 交换相邻的两个字符
public static void f(CharBuffer charBuffer) {
while (charBuffer.hasRemaining()) {
charBuffer.mark();
char c1 = charBuffer.get();
char c2 = charBuffer.get();
// 重置到标记的位置
charBuffer.reset();
charBuffer.put(c2).put(c1);
}
}
}
细节解析:
- 开始时position和mark指向0,limit和capacity指向最大值;
- 输出的元素是[position,limit];
- 调用get()和put()会将position向后移动,但是带索引的重载不会移动;
- mark()方法将mark移到position位置;
- reset()方法将position移到mark位置;
- rewind()将position移动到初始位置。
18.10.6 内存映射文件
内存映射文件允许我们创建和修改那些因为太大而不能放入内存的文件,这可以让我们假定整个文件都在内存中。
public class Test {
public static int SIZE = 0x8FFFFFF; // 128M
public static void main(String[] args) throws IOException {
MappedByteBuffer mappedByteBuffer = new RandomAccessFile("e:/test/a.txt", "rw").getChannel().map(FileChannel.MapMode.READ_WRITE, 0, SIZE);
for (int i = 0; i < SIZE; i++) {
mappedByteBuffer.put((byte)'a');
}
System.out.println("输出完毕");
for (int i = SIZE/2; i < SIZE / 2 + 6; i++) {
System.out.println(mappedByteBuffer.get(i));
}
}
}
MappedByteBuffer 特殊类型的直接缓冲器,必须指定初始位置和长度,这使我们可以映射某个大文件的较小部分,它继承在ByteBuffer,我们可以使用很多方法,如asCharBuffer.
性能
public class Test {
private static int numInts = 200000;
private static int readAndWrite = 10000;
// 测试类
private static abstract class Tester {
private String name;
public Tester(String name) {
this.name = name;
}
// 执行的方法
public void runTest() throws IOException {
System.out.print(name + ": ");
long start = System.nanoTime();
test();
System.out.println(System.nanoTime() - start);
}
public abstract void test() throws IOException;
}
// 6种比较对象
private static Tester[] testers = {
new Tester("io 写") {
@Override
public void test() throws IOException {
DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("e:/test/a.txt")));
for (int i = 0; i < numInts; i++) {
out.writeInt(i);
}
out.close();
}
},
new Tester("nio 写") {
@Override
public void test() throws IOException {
FileChannel channel = new RandomAccessFile("e:/test/a.txt", "rw").getChannel();
IntBuffer intBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, channel.size()).asIntBuffer();
for (int i = 0; i < numInts; i++) {
intBuffer.put(i);
}
channel.close();
}
},
new Tester("io 读") {
@Override
public void test() throws IOException {
DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream("e:/test/a.txt")));
for (int i = 0; i < numInts; i++) {
in.readInt();
}
in.close();
}
},
new Tester("nio 读") {
@Override
public void test() throws IOException {
FileChannel channel = new RandomAccessFile("e:/test/a.txt", "r").getChannel();
IntBuffer intBuffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size()).asIntBuffer();
while (intBuffer.hasRemaining()) {
intBuffer.get();
}
channel.close();
}
},
new Tester("io 读写") {
@Override
public void test() throws IOException {
RandomAccessFile accessFile = new RandomAccessFile("e:/test/a.txt", "rw");
accessFile.writeInt(1);
for (int i = 0; i < readAndWrite; i++) {
// 一个整数是4字节,索引这里要-4
accessFile.seek(accessFile.length() - 4);
accessFile.writeInt(accessFile.readInt());
}
accessFile.close();
}
},
new Tester("nio 读写") {
@Override
public void test() throws IOException {
FileChannel channel = new RandomAccessFile("e:/test/a.txt", "rw").getChannel();
IntBuffer intBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, channel.size()).asIntBuffer();
intBuffer.put(0);
for (int i = 1; i < readAndWrite; i++) {
intBuffer.put(intBuffer.get(i - 1));
}
channel.close();
}
}
};
public static void main(String[] args) throws IOException {
for (Tester tester : testers) {
tester.runTest();
}
}
}
/*
io 写: 20793800
nio 写: 7365400
io 读: 16946400
nio 读: 4548600
io 读写: 238458600
nio 读写: 1009200
*/
// 可以看到,尽管io已用nio重写,在同样的情况下nio明显比io快
18.10.7 文件加锁
加锁机制:允许我们同步的访问某个作为共享资源的文件。该锁对操作系统的其他进程也起作用。
public static void main(String[] args) throws IOException, InterruptedException {
FileOutputStream out = new FileOutputStream("e:/test/a.txt",true);
// tryLock()非阻塞式,不能获得就返回
// lock()阻塞式,会等到获得锁
FileLock fileLock = out.getChannel().tryLock();
// 判断该锁是共享还是独占
fileLock.isShared();
if (fileLock != null){
System.out.println("已上锁");
TimeUnit.MILLISECONDS.sleep(1000);
System.out.println("锁已释放");
}
}
// 对文件部分上锁
// 起始位置,长度,是否是共享锁
// tryLock(long position, long size, boolean shared)
由操作系统提供对独占锁和共享锁的支持,如果不支持共享锁,则都是独占锁。
对映射文件部分加锁
public class Test {
private static FileChannel channel;
private static int SIZE = 0x8FFFFFF; // 128M
public static void main(String[] args) throws IOException, InterruptedException {
channel = new RandomAccessFile("e:/test/a.txt","rw").getChannel();
MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, SIZE);
for (int i = 0; i < SIZE; i++) {
mappedByteBuffer.put((byte) 'a');
}
new LockAndModify(mappedByteBuffer,0,SIZE/3);
new LockAndModify(mappedByteBuffer,SIZE/2,SIZE/2+SIZE/4);
System.in.read();
}
private static class LockAndModify extends Thread{
private ByteBuffer buffer;
private int start,end;
public LockAndModify(ByteBuffer buffer,int start,int end){
this.start = start;
this.end = end;
buffer.limit(end);
buffer.position(start);
// 返回该缓冲器的一个分片,从position到limit
this.buffer = buffer.slice();
// 开启线程
start();
}
@SneakyThrows
@Override
public void run() {
// 锁只能通过通道获得
FileLock lock = channel.lock(start, end, false);
System.out.println("获得锁 "+start+" "+end);
while (buffer.position() < buffer.limit()-1){
buffer.put((byte) (buffer.get()+1));
}
lock.release();
System.out.println("释放锁 "+start+" "+end);
}
}
}
18.11 压缩
压缩类是InputStream和OutputStream继承层次的一部分,压缩类库是按照字节而不是字符处理的。
用GZIP进行简单压缩
注意:GZIP即是GZ
public static void main(String[] args) throws IOException {
BufferedReader reader = new BufferedReader(new FileReader("e:/test/a.txt"));
BufferedOutputStream out = new BufferedOutputStream(new GZIPOutputStream(new FileOutputStream("e:/test/a.gz")));
String s;
System.out.println("输出压缩文件==");
while ((s = reader.readLine()) != null) {
out.write(s.getBytes());
}
reader.close();
out.close();
reader = new BufferedReader(new InputStreamReader(new GZIPInputStream(new FileInputStream("e:/test/a.gz"))));
System.out.println("读取压缩文件==");
while ((s = reader.readLine()) != null){
System.out.println(s);
}
reader.close();
}
用Zip进行多文件保存
public class Test {
private static String[] paths={"e:/test/a.txt","e:/test/b.txt"};
public static void main(String[] args) throws IOException {
// 输出zip文件
// 字节输出流
FileOutputStream out = new FileOutputStream("e:/test/test.zip");
// 检验输出流
CheckedOutputStream cops = new CheckedOutputStream(out, new Adler32());
// 压缩输出流
ZipOutputStream zops = new ZipOutputStream(cops);
// 缓冲输出流
BufferedOutputStream bout = new BufferedOutputStream(zops);
zops.setComment("设置注释");
// 遍历多个文件
for (String path : paths) {
// 读取文件
BufferedReader reader = new BufferedReader(new FileReader(path));
// 加入要压缩的文件
// 解决多级目录问题
path = path.substring(path.length()-5,path.length());
// ZipEntry包含很多功能,代表zip中的文件
zops.putNextEntry(new ZipEntry(path));
// 这里最好使用读取每一行,否则中文可能乱码
String c;
while ((c= reader.readLine()) != null){
bout.write(c.getBytes());
}
reader.close();
// 清空缓冲区
bout.flush();
}
// 关闭输出流
bout.close();
// 读取zip文件
// 类似的输入
FileInputStream in = new FileInputStream("e:/test/test.zip");
CheckedInputStream cips = new CheckedInputStream(in,new Adler32());
ZipInputStream zips = new ZipInputStream(cips);
BufferedInputStream bips = new BufferedInputStream(zips);
// 接收zip中的文件
ZipEntry ze;
// 接收zip中文件的内容
byte[] bytes = new byte[128];
// 判断zip中是否由文件
while ((ze = zips.getNextEntry()) != null){
int len;
// 读取文件
while ((len = bips.read(bytes) )!= -1){
System.out.println(new String(bytes,0,len));
}
}
bips.close();
}
}
// Checksum类型:Adler32快,CRC32慢但准确
// 压缩并不局限于文件,可以压缩任何格式的文件
Java档案文件
# 将当前目录下的类文件都加到my.jar中
jar cf my.jar *.class
# 展示my.jar中的列表
jar tf my.jar
# 查看my.jar更详细信息
jar tvf my.jar
# 将com cn文件夹及其子内容添加到my.jar,并展示详细信息
jar cvf my.jar com cn
jar不支持添加和更新,也不支持移动文件到jar中,但是跨平台。
18.12 对象序列化
对象序列化 将那些实现了Serializable接口的对象转换成一个字节序列,并能够以后将这个字节序列完全恢复为原来的对象。
其是轻量级的,因为其必须在程序中显式的序列化和反序列化,而不是通过关键字定义。
// 示例
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Worm worm = new Worm(2, 'a');
// 使用文件序列化
System.out.println("输出对象:"+worm);
ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream("e:/test/a.out"));
oout.writeObject("测试String");
oout.writeObject(worm);
oout.close();
ObjectInputStream oin = new ObjectInputStream(new FileInputStream("e:/test/a.out"));
String s = (String) oin.readObject();
System.out.println("文件读入:"+s);
worm = (Worm) oin.readObject();
System.out.println("文件读入:"+worm);
oin.close();
// 使用缓存序列化
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream oout2 = new ObjectOutputStream(bout);
oout2.writeObject("测试String");
oout2.writeObject(worm);
// 关了数据也会被写进ByteArrayOutputStream,不知道为什么不关
oout2.flush();
ObjectInputStream oin2 = new ObjectInputStream(new ByteArrayInputStream(bout.toByteArray()));
s = (String) oin2.readObject();
System.out.println("文件读入:"+s);
worm = (Worm) oin2.readObject();
System.out.println("文件读入:"+worm);
oout2.close();
oin2.close();
}
}
// 数据类
class Data implements Serializable {
private int i;
public Data(int i) {
this.i = i;
}
@Override
public String toString() {
return Integer.toString(i);
}
}
//蠕虫类
class Worm implements Serializable {
private Random random = new Random();
private Data[] datas = {
new Data(random.nextInt(10)),
new Data(random.nextInt(10)),
new Data(random.nextInt(10))};
private int i;
private char c;
private Worm next;
public Worm(int i,char c){
this.i = i;
this.c = c;
// 如果i-1>0则有下一个链接
if (--i > 0){
next = new Worm(i, (char) (c+1));
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(c).append("(");
for (Data data : datas) {
sb.append(data);
}
sb.append(")");
if (next != null){
sb.append("\n").append(next);
}
return sb.toString();
}
}
18.12.1 寻找类
将一个类从序列化状态恢复时,虚拟机必须可以找到序列化的Class对应的类。
18.12.2 序列化的控制
Externalizable接口
作为一个代替Serializable的接口,可以对序列化/反序列化做出控制;在反序列化时,会调用默认构造器。
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Blip blip = new Blip("s",1);
// 使用文件序列化
ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream("e:/test/a.out"));
oout.writeObject(blip);
oout.close();
ObjectInputStream oin = new ObjectInputStream(new FileInputStream("e:/test/a.out"));
blip = (Blip) oin.readObject();
System.out.println(blip); // Blip{s='s', i=1}
oin.close();
}
}
// Externalizable的两个方法如果没有实现,则s为nulli为0
// 如果被序列化的类有继承关系,也必须调用基类的实现方法
class Blip implements Externalizable{
private String s;
private int i;
// 默认构造器必须是public的,Externalizable会调用默认构造器,调不到时会报错
public Blip(){
}
public Blip(String s, int i) {
this.s = s;
this.i = i;
}
// 实现对输出的控制
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(s);
out.writeObject(i);
}
// 实现对输入的控制
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
s = (String) in.readObject();
i = (int) in.readObject();
}
@Override
public String toString() {
return "Blip{" +
"s='" + s + '\'' +
", i=" + i +
'}';
}
}
transient(关键字)
标识字段,表示不需要序列化
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException{
Login login = new Login("ding","d123");
System.out.println(login);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("e:/test/a.txt"));
out.writeObject(login);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("e:/test/a.txt"));
login = (Login) in.readObject();
System.out.println(login);
}
}
class Login implements Serializable{
private Date date = new Date();
private String name;
private transient String password;
public Login(String name, String password) {
this.name = name;
this.password = password;
}
@Override
public String toString() {
return "Blip{" +
"date=" + date +
", name='" + name + '\'' +
", password='" + password + '\'' +
'}';
}
}
Externalizable替代方法
只实现Serializable并且添加如下两个方法,则可以达到和Externalizable一样的效果
private void writeObject(ObjectOutputStream out) throws IOException;
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException;
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException{
SerialCtl sCtl = new SerialCtl("test1","test2");
System.out.println(sCtl);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("e:/test/a.txt"));
// 在序列化时会检查被序列化的对象是否实现了自己的writeObject(),如果有,则调用。
out.writeObject(sCtl);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("e:/test/a.txt"));
// 可以看到,这里成功了,但是没有调用默认的构造器
sCtl = (SerialCtl) in.readObject();
System.out.println(sCtl);
}
}
class SerialCtl implements Serializable{
private String s1;
private transient String s2;
public SerialCtl(String s1, String s2) {
this.s1 = s1;
this.s2 = s2;
}
private void writeObject(ObjectOutputStream out) throws IOException{
// 如果先执行默认的序列化,可以调用该方法
out.defaultWriteObject();
out.writeObject(s2);
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{
// 必须先调用默认方法
in.defaultReadObject();
s2 = (String) in.readObject();
}
@Override
public String toString() {
return "SerialCtl{" +
"s1='" + s1 + '\'' +
", s2='" + s2 + '\'' +
'}';
}
}
18.12.3 使用持久性
class House implements Serializable{}
class Animal implements Serializable{
private String name;
private House house;
public Animal(String name, House house) {
this.name = name;
this.house = house;
}
@Override
public String toString() {
return name+":"+super.toString()+" house:"+house;
}
}
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException{
House house = new House();
Animal a1 = new Animal("狗",house);
Animal a2 = new Animal("猫",house);
List<Animal> list = Arrays.asList(a1,a2);
System.out.println(list);
ObjectOutputStream out1 = new ObjectOutputStream(new FileOutputStream("e:/test/a.txt"));
out1.writeObject(list);
out1.writeObject(list);
out1.close();
ObjectOutputStream out2 = new ObjectOutputStream(new FileOutputStream("e:/test/b.txt"));
out2.writeObject(list);
out2.close();
ObjectInputStream in1 = new ObjectInputStream(new FileInputStream("e:/test/a.txt"));
list = (List<Animal>) in1.readObject();
System.out.println(list);
System.out.println(list);
list = (List<Animal>) in1.readObject();
ObjectInputStream in2 = new ObjectInputStream(new FileInputStream("e:/test/b.txt"));
list = (List<Animal>) in2.readObject();
System.out.println(list);
}
}
/*
[狗:com.io.Animal@27c170f0 house:com.io.House@5451c3a8, 猫:com.io.Animal@2626b418 house:com.io.House@5451c3a8]
[狗:com.io.Animal@49097b5d house:com.io.House@6e2c634b, 猫:com.io.Animal@37a71e93 house:com.io.House@6e2c634b]
[狗:com.io.Animal@49097b5d house:com.io.House@6e2c634b, 猫:com.io.Animal@37a71e93 house:com.io.House@6e2c634b]
[狗:com.io.Animal@7e6cbb7a house:com.io.House@7c3df479, 猫:com.io.Animal@7106e68e house:com.io.House@7c3df479]
*/
// 可以看到在同一个流中,同一指向的对象反序列化后也会指向同一个地方
// 放入不同流后的恢复,则不会指向同一地址
// 注意:准备序列化的对象最好不要在序列化过程中乱改,会导致不确定性
静态问题
注意:静态域不能被序列化。
搭建一个画图的系统
abstract class Shape implements Serializable {
// 定义颜色
public static final int RED = 1, BULE = 2, GREEN = 3;
private int x;
private int y;
// 维度
private int dimension;
private static Random random = new Random();
private static int couter;
public Shape(int x, int y, int dimension) {
this.x = x;
this.y = y;
this.dimension = dimension;
}
public abstract void setColor(int color);
public abstract int getColor();
@Override
public String toString() {
return getClass().getSimpleName() + " " + getColor() + " " + x + " " + y + " " + dimension;
}
// 工厂类
public static Shape shapeFactory() {
int x = random.nextInt(100);
int y = random.nextInt(100);
int dimension = random.nextInt(100);
switch (couter++ % 3) {
case 0:
return new Circle(x, y, dimension);
case 1:
return new Square(x, y, dimension);
default:
return new Line(x, y, dimension);
}
}
}
class Circle extends Shape {
// 静态域不能继承
private static int color = RED;
public Circle(int x, int y, int dimension) {
super(x, y, dimension);
}
@Override
public void setColor(int color) {
Circle.color = color;
}
@Override
public int getColor() {
return color;
}
}
class Square extends Shape {
private static int color = RED;
public Square(int x, int y, int dimension) {
super(x, y, dimension);
}
@Override
public void setColor(int color) {
Square.color = color;
}
@Override
public int getColor() {
return color;
}
}
class Line extends Shape {
private static int color = RED;
public Line(int x, int y, int dimension) {
super(x, y, dimension);
}
@Override
public void setColor(int color) {
Line.color = color;
}
@Override
public int getColor() {
return color;
}
// 如果希望序列化静态域,我们必须手动去作
public static void serializeStaticState(ObjectOutputStream out) throws IOException {
out.writeInt(color);
}
public static void derializeStaticState(ObjectInputStream in) throws IOException {
color = in.readInt();
}
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
// Class
List<Class<? extends Shape>> shapeTypes = Arrays.asList(Circle.class,Square.class,Line.class);
List<Shape> shapes = new ArrayList<>();
// 对象
for (int i = 0; i < 5; i++) {
shapes.add(Shape.shapeFactory());
}
System.out.println(shapes);
// [Circle 1 9 61 4, Square 1 56 80 70, Line 1 63 62 7, Circle 1 37 90 66, Square 1 96 97 68]
// 设置颜色
for (int i = 0; i < 5; i++) {
shapes.get(i).setColor(Shape.GREEN);
}
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("e:/test/a.txt"));
out.writeObject(shapeTypes);
Line.serializeStaticState(out);
out.writeObject(shapes);
System.out.println(shapes);
// [Circle 3 9 61 4, Square 3 56 80 70, Line 3 63 62 7, Circle 3 37 90 66, Square 3 96 97 68]
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("e:/test/a.txt"));
shapeTypes = (List<Class<? extends Shape>>) in.readObject();
Line.derializeStaticState(in);
shapes = (List<Shape>) in.readObject();
System.out.println(shapes);
// [Circle 3 9 61 4, Square 3 56 80 70, Line 3 63 62 7, Circle 3 37 90 66, Square 3 96 97 68]
}
/* 理论上,静态域不应该被序列化,为什么反序列化后,颜色是我们需要的?因为序列化和反序列化在一个JVM中,在反序列化时静态域被加载了,所以,此时,被序列化的对象可以获得我们想要的值。
*/
public static void main(String[] args) throws IOException, ClassNotFoundException {
List<Shape> shapes = new ArrayList<>();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("e:/test/a.txt"));
List<Class<? extends Shape>> shapeTypes = (List<Class<? extends Shape>>) in.readObject();
Line.derializeStaticState(in);
shapes = (List<Shape>) in.readObject();
System.out.println(shapes);
// [Circle 1 9 61 4, Square 1 56 80 70, Line 3 63 62 7, Circle 1 37 90 66, Square 1 96 97 68]
}
/*此时可以看到,只有Line类的静态域是我们需要的值,而其他类的静态域都是他们的初始值。
18.13 XML
主要介绍了XOM类库的使用,但是目前几乎没有在用的了。
18.14 Preferences
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 用于个人偏好 systemNodeForPackage()用于通用的安装配置
Preferences pref = Preferences.userNodeForPackage(Test.class);
pref.put("name","ding");
int age = pref.getInt("age",0);
// 每次执行都不同,程序会利用系统资源帮助我们自动存储
System.out.println(age);
age++;
pref.putInt("age",age);
}
}