文章目录

  • 18.1 File类
    • 18.1.1 获取当前目录下文件名并做过滤
    • 18.1.2 递归获取指定目录下文件集并过滤
    • 18.1.3 目录的检查及创建
  • 18.2 输入和输出
    • 18.2.1 InputStream类型
      • 18.2.1.1 ByteArrayInputStream
      • 18.2.1.2 FileInputStream
      • 18.2.1.3 PipedInputStream
      • 18.2.1.4 SequenceInputStream
    • 18.2.2 OutputStream类型
      • 18.2.2.1 FileOutputStream
      • 18.2.2.2 ByteArrayOutputStream
    • 18.2.3 实现复制功能
  • 18.3 添加属性和有用的接口
    • 18.3.1 Filter模式
    • 18.3.2 编写FilterInputStream
  • 18.4 Reader和Writer
    • 18.4.1 Reader
      • FileReader
      • CharArrayReader
      • StringReader
      • InputStreamReader
    • 18.4.2 Writer
      • FileWriter
      • PrintWriter
      • CharArrayWriter
      • StringWriter
      • OutputStreamWriter
  • 18.7 PrintStream和PrintWriter
    • 18.7.1 PrintStream
    • 18.7.2 PrintWriter
  • 18.8 标准I/O
    • 18.8.1 标准输入、输出
    • 18.8.2 标准I/O重定向
  • 18.9 进程控制
  • 18.10 新I/O
  • 18.11 压缩
    • 18.11.1 读取zip包
    • 18.11.2 写入zip包
    • 18.11.3 Java文档文件
    • 18.11.4 读取classpath资源
  • 18.12 对象序列化
    • 18.12.1 序列化
    • 18.12.2 反序列化
    • 18.12.3 注意
  • 18.13 XML与JSON
    • 18.3.1 XML数据格式
    • 18.3.2 使用DOM解析XML
    • 18.3.3 使用SAX解析XML
    • 18.3.4 使用Jackson解析XML
    • 18.3.4 JSON数据格式
  • 18.14 使用Files

自从Java 1.0版本以来,Java的I/O类库发生了明显改变,在原来 面向字节的类中添加了 面向字符和基于Unicode的类。在JDK 1.4中,添加了 nio类改进性能及功能。

18.1 File类

File类既能代表一个特定文件的名称,又能代表一个目录下的一组文件的名称。如果它指的是一个文件集,我们就可以对此集合调用list()方法,这个方法会返回一个字符数组。

public class ReferenceTest {public static void main(String[] args) {File file1 = new File("E:\\CS-Book\\Git");  // windowsFile file2 = new File("/usr/bin/javac"); // linuxSystem.out.println(file1);String[] list1 = file1.list();System.out.println(Arrays.toString(list1));File file2 = new File("E:\\CS-Book\\Git参考手册.zip");System.out.println(file2);}
}

相对路径:
用.表示当前目录,…表示上级目录。

// 假设当前目录是C:\Docs
File f1 = new File("sub\\javac"); // 绝对路径是C:\Docs\sub\javac
File f3 = new File(".\\sub\\javac"); // 绝对路径是C:\Docs\sub\javac
File f3 = new File("..\\sub\\javac"); // 绝对路径是C:\sub\javac

18.1.1 获取当前目录下文件名并做过滤

list()可以获得目录下的所有文件,list(FilenameFilter filter)可以对目录过滤。

public class ReferenceTest {public static FilenameFilter filter(final String regex) {return new FilenameFilter() {private Pattern pattern = Pattern.compile(regex);@Overridepublic boolean accept(File dir, String name) {return pattern.matcher(name).matches();}};}public static void main(String[] args) {File file1 = new File("E:\\Java\\JDK");String[] list1 = file1.list(filter(".*\\.txt"));  // .* 任意多个字符,\\. 转义System.out.println(Arrays.toString(list1));Arrays.sort(list1, String.CASE_INSENSITIVE_ORDER);System.out.println(Arrays.toString(list1));}
}
public class ReferenceTest {public static void main(String[] args) {File file1 = new File("E:\\Java\\JDK");String[] list1 = file1.list(new FilenameFilter() {@Overridepublic boolean accept(File dir, String name) {return name.endsWith(".exe");}});System.out.println(Arrays.toString(list1));}
}

18.1.2 递归获取指定目录下文件集并过滤

public class C {public static File[] local(File filePath, String regex) {File[] files = filePath.listFiles(new FilenameFilter() {Pattern compile = Pattern.compile(regex);@Overridepublic boolean accept(File dir, String name) {return compile.matcher(name).matches();}});return files;}public static List<File> walk(File filePath, String regex) {List<File> allFiles = new ArrayList<File>();File[] subFiles = filePath.listFiles();for (File file : subFiles) {if (file.isDirectory()) {List<File> walk = walk(file, regex);allFiles.addAll(walk);} else {Pattern compile = Pattern.compile(regex);if (compile.matcher(file.getName()).matches()) {allFiles.add(file);}}}return allFiles;}public static void main(String[] args) {File[] local = local(new File("E:\\Java\\JDK"), ".*\\.txt");System.out.println(Arrays.toString(local));List<File> walk = walk(new File("E:\\Java\\JDK"), ".*\\.txt");System.out.println(walk);}
}

18.1.3 目录的检查及创建

File类不仅代表存在的文件或目录。也可以用File对象来创建新的目录或尚不存在的整个目录路径。还可以查看文件的特性(大小,最后修改日期,读/写),检查是文件还是目录,并可以删除文件。

public class A {public static void main(String[] args) {File file = new File("E:\\Java\\JDK\\javafx-src.zip");System.out.println(File.separator); // 根据当前平台打印"\"或"/"System.out.println(file.isFile() + " " +  file.isDirectory());System.out.println(file.getAbsoluteFile()); // 绝对路径,C:\Win\Sys\..\note.exeSystem.out.println(file.getCanonicalPath());  // 规范路径,C:\Win\note.exeSystem.out.println(file.canRead() + " " + file.canWrite());System.out.println(file.getName());System.out.println(file.getParent());System.out.println(file.length() / (1024.0 * 1024));System.out.println(new java.sql.Date(file.lastModified()));File newFile = new File("E:\\Java\\JDK\\a\\a.txt");File parentFile = newFile.getParentFile();System.out.println(parentFile.mkdirs());   // 创建文件夹System.out.println(newFile.createNewFile());if (newFile.exists()) {System.out.println(newFile.delete());System.out.println(parentFile.delete());}File file = new File("E:\\Java\\code\\Thinking in Java\\src\\H3");File tempFile = File.createTempFile("tmp-", ".txt", file);System.out.println(tempFile.getName());System.out.println(tempFile.getPath());tempFile.delete();tempFile.deleteOnExit();}
}

重命名或移动

File file = new File("E:\\Java\\JDK\\a\\b.txt");File newFile = new File("E:\\Java\\JDK\\a.txt");newFile.renameTo(file);

18.2 输入和输出

流:代表任何有能力产出数据的数据源对象或者是有能力接收数据的接收端对象。“流”屏蔽了实际的I/O设备中处理数据的细节。

Java类库中的I/O类分为输入和输出两部分:
(1)任何自抽象类InputStream或Reader派生而来的类都含有read()方法。InputStream是字节输入流的所有类的超类,Reader用于读取字符流。
(2)任何自抽象类OutputStream或Writer派生而来的类都含有write()方法。

系统学习 Java IO (十六)----这么多类,应该用哪个?
https://www.jianshu.com/p/937af17e2506

18.2.1 InputStream类型

在调用InputStream的read()方法读取数据时,我们说read()方法是阻塞(Blocking)的。它的意思是,对于下面的代码:

int n;
n = input.read(); // 必须等待read()方法返回才能执行下一行代码
int m = n;

执行到第二行代码时,必须等read()方法返回后才能继续。因为读取IO流相比执行普通代码,速度会慢很多,因此,无法确定read()方法调用到底要花费多长时间

18.2.1.1 ByteArrayInputStream

将字节数组数据读取到内存缓冲区中。
read()每次读取一个字节作为返回值。
read(byte[size])每次读取size大小的数据到字节数组中。

测试的时候,用来在内存中模拟一个字节流输入。

public class A {public static void main(String[] args) throws Exception {byte[] bytes = "talk is cheap show me the code.".getBytes();byte[] getBytes = new byte[10];int c;ByteArrayInputStream byteArrayInputStream1 = new ByteArrayInputStream(bytes);System.out.println(byteArrayInputStream1.available());while ((c = byteArrayInputStream1.read(getBytes)) != -1) {System.out.print(new String(getBytes,0, c));}System.out.println("");ByteArrayInputStream byteArrayInputStream2 = new ByteArrayInputStream(bytes);System.out.println(byteArrayInputStream2.available());while ((c = byteArrayInputStream2.read()) != -1) {System.out.print((char) c);}}
}

18.2.1.2 FileInputStream

从文件中读取数据到内存中

public class A {public static void main(String[] args) {FileInputStream fileInputStream = null;try {fileInputStream = new FileInputStream("E:\\Java\\code\\Thinking in Java\\src\\H3\\a.txt");System.out.println(fileInputStream.available());byte[] data = new byte[20];int len;while ((len = fileInputStream.read(data)) != -1) {System.out.print(new String(data, 0, len));}} catch (Exception e) {System.out.println("文件输入流异常" + e);} finally {if (fileInputStream != null)try {fileInputStream.close();} catch (IOException e) {System.out.println("文件输入流无法关闭");}}}
}

18.2.1.3 PipedInputStream

PipedOutputStream和PipedInputStream主要用于线程之间的通信 。二者必须配合使用,也就是一端写入,另一端接收。本质上也是一个中间缓存区,讲数据缓存在PipedInputStream的数组当中,等待PipedOutputStream的读取。

Java IO源码分析(三)——PipedOutputStream 和 PipedInputStream
https://blog.csdn.net/qq_36263268/article/details/111028488

18.2.1.4 SequenceInputStream

SequenceInputStream 可以将两个或多个其他 InputStream 合并为一个。

public class A {public static void main(String[] args) throws Exception {FileInputStream a = new FileInputStream("E:\\Java\\code\\Thinking in Java\\src\\H3\\a.txt");FileInputStream b = new FileInputStream("E:\\Java\\code\\Thinking in Java\\src\\H3\\b.txt");FileInputStream c = new FileInputStream("E:\\Java\\code\\Thinking in Java\\src\\H3\\c.txt");Vector<FileInputStream> vector = new Vector<>();vector.add(a);vector.add(b);vector.add(c);SequenceInputStream stream = new SequenceInputStream(vector.elements());byte[] data = new byte[20];int len;while ((len = stream.read(data)) != -1) {System.out.print(new String(data, 0, len));}stream.close();}
}

18.2.2 OutputStream类型

18.2.2.1 FileOutputStream

public class A {public static void main(String[] args) {FileOutputStream outputStream = null;try {outputStream = new FileOutputStream("E:\\Java\\code\\Thinking in Java\\src\\H3\\a.txt");outputStream.write("Hello".getBytes("UTF-8")); // Hello} catch (Exception e) {System.out.println("文件输出流异常");} finally {try {outputStream.close();} catch (Exception e) {System.out.println("文件输出流无法关闭");}}}
}

18.2.2.2 ByteArrayOutputStream

public class A {public static void main(String[] args) {ByteArrayOutputStream outputStream = null;try {outputStream = new ByteArrayOutputStream();outputStream.write("Hello".getBytes());byte[] bytes = outputStream.toByteArray();} catch (Exception e) {System.out.println("字节数组输出流异常");} finally {try {outputStream.close();} catch (Exception e){System.out.println("字节数组输出流无法关闭");}}}
}

18.2.3 实现复制功能

public class A {public static void copy(String oldf, String newf) throws Exception {FileInputStream fileInputStream = new FileInputStream(oldf);StringBuilder stringBuilder = new StringBuilder();byte[] bytes = new byte[10];int len;while ((len = fileInputStream.read(bytes)) != -1) {String s = new String(bytes, 0, len);stringBuilder.append(s);}fileInputStream.close();System.out.println(stringBuilder);File file = new File(newf);file.createNewFile();FileOutputStream fileOutputStream = new FileOutputStream(file);fileOutputStream.write(stringBuilder.toString().getBytes());fileOutputStream.flush();fileOutputStream.close();}public static void main(String[] args) throws Exception {copy("E:\\Java\\code\\Thinking in Java\\src\\H3\\a.txt", "E:\\Java\\code\\Thinking in Java\\src\\H3\\b.txt");}
}

18.3 添加属性和有用的接口

18.3.1 Filter模式

JDK将InputStream分为两大类:
一类是直接提供数据的基础InputStream,例如:

  • FileInputStream
  • ByteArrayInputStream
  • ServletInputStream

一类是提供额外附加功能的InputStream,例如:

  • BufferedInputStream
  • DigestInputStream
  • CipherInputStream

基础InputStream作为数据源提供数据,附加InputStream在此基础上提供额外的功能。

InputStream file = new FileInputStream("test.gz");
InputStream buffered = new BufferedInputStream(file);
InputStream gzip = new GZIPInputStream(buffered);


上述这种通过一个“基础”组件再叠加各种“附加”功能组件的模式,称之为Filter模式(或者装饰器模式:Decorator)。它可以让我们通过少量的类来实现各种功能的组合:

18.3.2 编写FilterInputStream

public class CountInputStream extends FilterInputStream {int count;public CountInputStream(InputStream in) {super(in);}public int getCount() {return count;}@Overridepublic int read() throws IOException {int n = in.read();if (n != -1) {this.count ++;}return n;}@Overridepublic int read(byte b[], int off, int len) throws IOException {int n = in.read(b, off, len);if (n != -1) {this.count += n;}return n;}public static void main(String[] args) throws Exception {CountInputStream countInputStream = new CountInputStream(new ByteArrayInputStream("Hello World!".getBytes()));int len;while ((len = countInputStream.read()) != -1) {System.out.print((char) len);}countInputStream.close();System.out.println("\n" + countInputStream.getCount());}
}

18.4 Reader和Writer

18.4.1 Reader

Reader是所有字符输入流的超类。

FileReader

public class A {public static void main(String[] args) throws Exception {FileReader fileReader = new FileReader("E:\\Java\\code\\Thinking in Java\\src\\H3\\a.txt");int len2;char[] chars2 = new char[10];while ((len2 = fileReader.read(chars2)) != -1) {System.out.print(new String(chars2, 0, len2));}fileReader.close();}
}

CharArrayReader

public class A {public static void main(String[] args) throws Exception {CharArrayReader charArrayReader = new CharArrayReader("Hello World!".toCharArray());int len2;char[] chars2 = new char[10];while ((len2 = charArrayReader.read(chars2)) != -1) {System.out.print(new String(chars2, 0, len2));}charArrayReader.close();}
}

StringReader

public class A {public static void main(String[] args) throws Exception {StringReader charArrayReader = new StringReader("Hello World!");int len2;char[] chars2 = new char[10];while ((len2 = charArrayReader.read(chars2)) != -1) {System.out.print(new String(chars2, 0, len2));}charArrayReader.close();}
}

InputStreamReader

除了特殊的CharArrayReader和StringReader,普通的Reader实际上是基于InputStream构造的,因为Reader需要从InputStream中读入字节流(byte),然后,根据编码设置,再转换为char就可以实现字符流。既然Reader本质上是一个基于InputStream的byte到char的转换器,那么,如果我们已经有一个InputStream,想把它转换为Reader,是完全可行的。InputStreamReader就是这样一个转换器,它可以把任何InputStream转换为Reader。示例代码如下:

public class A {public static void main(String[] args) throws Exception {FileInputStream fileInputStream = new FileInputStream("E:\\Java\\code\\Thinking in Java\\src\\H3\\a.txt");InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, "UTF-8");int len;char[] chars = new char[10];while ((len = inputStreamReader.read(chars)) != -1) {System.out.print(new String(chars, 0, len));}inputStreamReader.close();}
}

18.4.2 Writer

FileWriter

创建文件并将字符串写到文件中,可以追加文件

public class A {public static void main(String[] args) throws Exception {FileWriter fileInputStream = new FileWriter("E:\\Java\\code\\Thinking in Java\\src\\H3\\a.txt", true); // 可追加fileInputStream.write('a');fileInputStream.write("AAAA");fileInputStream.write("BBBB".toCharArray());fileInputStream.close();}
}

PrintWriter

创建文件并将字符串写到文件中,只能覆盖文件

public class A {public static void main(String[] args) throws Exception {PrintWriter printWriter = new PrintWriter("E:\\Java\\code\\Thinking in Java\\src\\H3\\a.txt");printWriter.print("aaaaaaaaaaaaaaaaa");printWriter.close();}
}

CharArrayWriter

public class A {public static void main(String[] args) throws Exception {CharArrayWriter charArrayWriter = new CharArrayWriter();charArrayWriter.write("Hello".toCharArray());char[] chars = charArrayWriter.toCharArray();System.out.println(chars);}
}

StringWriter

public class A {public static void main(String[] args) throws Exception {StringWriter stringWriter = new StringWriter();stringWriter.write("Hello");String s = stringWriter.toString();System.out.println(s);}
}

OutputStreamWriter

public class A {public static void main(String[] args) throws Exception {FileOutputStream fileOutputStream = new FileOutputStream("E:\\Java\\code\\Thinking in Java\\src\\H3\\a.txt");OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream, "UTF-8");outputStreamWriter.write("String");outputStreamWriter.close();}
}

18.7 PrintStream和PrintWriter

除了添加了一组print()/println()方法,可以打印各种数据类型,比较方便外,它还有一个额外的优点,就是不会抛出IOException,这样我们在编写代码的时候,就不必捕获IOException。

18.7.1 PrintStream

PrintStream是一种FilterOutputStream,它在OutputStream的接口上,额外提供了一些写入各种数据类型的方法:print()、println()
System.out是系统默认提供的PrintStream,表示标准输出
System.err是系统默认提供的标准错误输出。

public class A {public static void main(String[] args) throws Exception {FileDescriptor out = FileDescriptor.out;PrintStream printStream = new PrintStream(new FileOutputStream(out));printStream.print("111");printStream.write("Hello".getBytes());System.out.println("");FileDescriptor err = FileDescriptor.err;PrintStream printStream2 = new PrintStream(new FileOutputStream(err));printStream2.print("111");printStream2.write("Hello".getBytes());}
}

18.7.2 PrintWriter

public class A {public static void main(String[] args) throws Exception {PrintWriter printWriter = new PrintWriter("E:\\Java\\code\\Thinking in Java\\src\\H3\\a.txt");printWriter.print("aaaaaaaaaaaaaaaaa");printWriter.close();}
}

18.8 标准I/O

18.8.1 标准输入、输出

public class A {public static void main(String[] args) throws Exception {BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));StringBuilder stringBuilder = new StringBuilder();String s;while((s=bufferedReader.readLine()) !=null) {stringBuilder.append(s);if (s.equals("end")){break;}}System.out.println(stringBuilder);}
}

18.8.2 标准I/O重定向

public class A {public static void main(String[] args) throws Exception {BufferedInputStream in = new BufferedInputStream(new FileInputStream("E:\\Java\\code\\Thinking in Java\\src\\H3\\a.txt"));System.setIn(in);BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("E:\\Java\\code\\Thinking in Java\\src\\H3\\b.txt"));PrintStream out = new PrintStream(bufferedOutputStream);System.setOut(out);System.setErr(out);BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));String s;while((s=bufferedReader.readLine()) !=null) {System.out.println(s);}out.close();in.close();}
}

18.9 进程控制

https://blog.csdn.net/zhoujing_0424/article/details/79917368
https://blog.csdn.net/lenny_wants/article/details/95357011

ProcessBuilder类是J2SE 1.5在java.lang中新添加的一个新类,用于创建操作系统进程,它提供一种启动和管理进程(也就是应用程序)的方法。在J2SE 1.5之前,都是由Process类处来实现进程的控制管理。
每个 ProcessBuilder 实例管理一个进程属性集。它的start() 方法利用这些属性创建一个新的 Process 实例。start() 方法可以从同一实例重复调用,以利用相同的或相关的属性创建新的子进程。

public class A {public static void main(String[] args) throws Exception {ProcessBuilder processBuilder = new ProcessBuilder("CMD", "/C", "ipconfig");processBuilder.directory(new File("D:/"));  // 设置工作路径processBuilder.redirectErrorStream(true);   // 标准输出和错误输出合并File log = new File("E:\\Java\\code\\Thinking in Java\\out\\production\\Thinking in Java\\H3\\a.txt");processBuilder.redirectOutput(ProcessBuilder.Redirect.appendTo(log)); // 重定向Process process = processBuilder.start();ProcessBuilder processBuilder2 = new ProcessBuilder("ipconfig", "/all");processBuilder2.directory(new File("D:/"));Process process2 = processBuilder2.start();BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process2.getInputStream(), "gbk"));String len;while ((len=bufferedReader.readLine()) != null) {System.out.println(len);}}
}

18.10 新I/O

18.11 压缩

ZipInputStream是一种FilterInputStream,它可以直接读取zip包的内容:
另一个JarInputStream是从ZipInputStream派生,它增加的主要功能是直接读取jar文件里面的MANIFEST.MF文件。因为本质上jar包就是zip包,只是额外附加了一些固定的描述文件。

18.11.1 读取zip包

public class A {public static void main(String[] args) throws Exception {FileInputStream fileInputStream = new FileInputStream("E:\\Java\\code\\Thinking in Java\\src\\H3\\H3.zip");ZipInputStream zipInputStream = new ZipInputStream(fileInputStream);ZipEntry nextEntry = null;while ((nextEntry = zipInputStream.getNextEntry()) != null) {System.out.println(nextEntry.getName());if (!nextEntry.isDirectory()) {int n;while ((n = zipInputStream.read()) != -1) {System.out.print((char)n);}}}}
}

18.11.2 写入zip包

如果要实现目录层次结构,new ZipEntry(name)传入的name要用相对路径。

public class A {public static void main(String[] args) throws Exception {ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream("E:\\Java\\code\\Thinking in Java\\src\\H3\\H4.zip"));File[] files = new File[]{new File("E:\\Java\\code\\Thinking in Java\\src\\H3\\a.txt"),new File("E:\\Java\\code\\Thinking in Java\\src\\H3\\b.txt"),};for (File file: files) {zipOutputStream.putNextEntry(new ZipEntry(file.getName()));zipOutputStream.write(Files.readAllBytes(file.toPath()));zipOutputStream.closeEntry();}}
}

18.11.3 Java文档文件

一个JAR文件由一组压缩文件构成,同时还有一张描述了所有这些文件的文件清单。

18.11.4 读取classpath资源

public class B {public static void getResource(String s) throws Exception {InputStream resourceAsStream = B.class.getResourceAsStream(s);if (resourceAsStream != null) {System.out.println("aa");int len;while ((len = resourceAsStream.read()) != -1) {System.out.print((char)len);}}}public static void getFile(String s) throws Exception {InputStream fileInputStream = new FileInputStream(s);int len;while ((len = fileInputStream.read()) != -1) {System.out.print((char)len);}}public static void main(String[] args) throws Exception {getResource("/Resources/a.txt");getFile("./b.txt");}
}

18.12 对象序列化

序列化是指把一个Java对象变成二进制内容,本质上就是一个byte[]数组。

为什么要把Java对象序列化呢?因为序列化后可以把byte[]保存到文件中,或者把byte[]通过网络传输到远程,这样,就相当于把Java对象存储到文件或者通过网络传输出去了。

有序列化,就有反序列化,即把一个二进制内容(也就是byte[]数组)变回Java对象。有了反序列化,保存到文件中的byte[]数组又可以“变回”Java对象,或者从网络上读取byte[]并把它“变回”Java对象。

18.12.1 序列化

一个Java对象要能序列化,必须实现一个特殊的java.io.Serializable接口,它的定义如下:

public interface Serializable {}

Serializable接口没有定义任何方法,它是一个空接口。我们把这样的空接口称为“标记接口”(Marker Interface),实现了标记接口的类仅仅是给自身贴了个“标记”,并没有增加任何方法。

把一个Java对象变为byte[]数组,需要使用ObjectOutputStream。它负责把一个Java对象写入一个字节流:

public class B {public static void main(String[] args) throws Exception {FileOutputStream fileOutputStream = new FileOutputStream("E:\\Java\\code\\Thinking in Java\\src\\Resources\\a.txt");ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);objectOutputStream.writeInt(1);objectOutputStream.writeUTF("Hello");objectOutputStream.writeObject(new Seen(1));fileOutputStream.close();}
}

ObjectOutputStream既可以写入基本类型,如int,boolean,也可以写入String(以UTF-8编码),还可以写入实现了Serializable接口的Object。

因为写入Object时需要大量的类型信息,所以写入的内容很大。

18.12.2 反序列化

和ObjectOutputStream相反,ObjectInputStream负责从一个字节流读取Java对象:

public class B {public static void main(String[] args) throws Exception {FileInputStream fileInputStream = new FileInputStream("E:\\Java\\code\\Thinking in Java\\src\\Resources\\a.txt");ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);System.out.println(objectInputStream.readInt());System.out.println(objectInputStream.readUTF());Seen seen = (Seen) objectInputStream.readObject();System.out.println(seen);}
}

除了能读取基本类型和String类型外,调用readObject()可以直接返回一个Object对象。要把它变成一个特定类型,必须强制转型。

Java的序列化允许class定义一个特殊的serialVersionUID静态变量,用于标识Java类的序列化“版本”,通常可以由IDE自动生成。如果增加或修改了字段,可以改变serialVersionUID的值,这样就能自动阻止不匹配的class版本:

public class Person implements Serializable {private static final long serialVersionUID = 2709425275741743919L;
}

18.12.3 注意

(1)反序列化时,由JVM直接构造出Java对象,不调用构造方法,构造方法内部的代码,在反序列化时根本不可能执行。
(2)Java的序列化机制可以导致一个实例能直接从byte[]数组创建,而不经过构造方法,因此,它存在一定的安全隐患。一个精心构造的byte[]数组被反序列化后可以执行特定的Java代码,从而导致严重的安全漏洞。
(3)Java本身提供的基于对象的序列化和反序列化机制既存在安全性问题,也存在兼容性问题。更好的序列化方法是通过JSON这样的通用数据结构来实现,只输出基本类型(包括String)的内容,而不存储任何与代码相关的信息。

18.13 XML与JSON

18.3.1 XML数据格式

XML是可扩展标记语言(eXtensible Markup Language)的缩写,它是是一种数据表示格式,可以描述非常复杂的数据结构,常用于传输和存储数据。

<?xml version="1.0" encoding="UTF-8" ?>  // 固定结构,可以加上可选的编码
<!DOCTYPE mapper SYSTEM "book.dtd">  // 声明DTD,https://zhuanlan.zhihu.com/p/372027351
<mapper id="1"><name>Java核心技术</name>   // 特殊符号需要转义,如>转为&gt;<author>Cay S. Horstmann</author><isbn lang="CN">1234567</isbn><tags><tag>Java</tag><tag>Network</tag></tags><pubDate/>
</mapper>

格式正确的XML(Well Formed)是指XML的格式是正确的,可以被解析器正常读取(可通过浏览器验证)。而合法的XML是指,不但XML格式正确,而且它的数据结构可以被DTD或者XSD验证。

18.3.2 使用DOM解析XML

因为XML是一种树形结构的文档,它有两种标准的解析API:
(1)DOM:一次性读取XML,并在内存中表示为树形结构;内存占用大;
(2)SAX:以流的形式读取XML,使用事件回调。

DOM是Document Object Model的缩写,DOM模型就是把XML结构作为一个树形结构处理,从根节点开始,每个节点都可以包含任意个子节点。


Java提供了DOM API来解析XML,它使用下面的对象来表示XML的内容:

Document:代表整个XML文档;
Element:代表一个XML元素;
Attribute:代表一个元素的某个属性。

public class B {public static void printNode(Node parse1, int indent) {for (int i = 0; i < indent; i++) {System.out.print(" ");}switch (parse1.getNodeType()) {case Node.DOCUMENT_NODE:System.out.println("Document: " + parse1.getNodeName());break;case Node.ELEMENT_NODE:System.out.println("Element: " + parse1.getNodeName());break;case Node.TEXT_NODE:System.out.println("Text: " + parse1.getNodeName() + " = " + parse1.getNodeValue());break;case Node.ATTRIBUTE_NODE:System.out.println("Attr: " + parse1.getNodeName() + " = " + parse1.getNodeValue());break;case Node.CDATA_SECTION_NODE:System.out.println("CDATA: " + parse1.getNodeName() + " = " + parse1.getNodeValue());break;case Node.COMMENT_NODE:System.out.println("Comment: " + parse1.getNodeName() + " = " + parse1.getNodeValue());break;default:System.out.println("NodeType: " + parse1.getNodeType() + ", NodeName: " + parse1.getNodeName());}for (Node child = parse1.getFirstChild(); child!=null; child = child.getNextSibling()) {printNode(child, indent + 1);}}public static void main(String[] args) throws Exception {InputStream inputStream = B.class.getResourceAsStream("/Resources/a1.xml");System.out.println(inputStream);DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();Document parse1 = documentBuilder.parse(inputStream);Document parse2 = documentBuilder.parse(new File("E:\\Java\\code\\Thinking in Java\\src\\Resources\\a1.xml"));Document parse3 = documentBuilder.parse("E:\\Java\\code\\Thinking in Java\\src\\Resources\\a1.xml");printNode(parse3, 0);}
}

18.3.3 使用SAX解析XML

SAX是Simple API for XML的缩写,它是一种基于流的解析方式,边读取XML边解析,并以事件回调的方式让调用者获取数据。因为是一边读一边解析,所以无论XML有多大,占用的内存都很小。

SAX解析会触发一系列事件:

startDocument:开始读取XML文档;
startElement:读取到了一个元素,例如;
characters:读取到了字符;
endElement:读取到了一个结束的元素,例如;
endDocument:读取XML文档结束。

关键代码SAXParser.parse()除了需要传入一个InputStream外,还需要传入一个回调对象,这个对象要继承自DefaultHandler:

public class B {public static void main(String[] args) throws Exception {InputStream input = B.class.getResourceAsStream("/Resources/a1.xml");SAXParserFactory spf = SAXParserFactory.newInstance();SAXParser saxParser = spf.newSAXParser();saxParser.parse(input, new MyHandler());}
}
class MyHandler extends DefaultHandler {public void startDocument() throws SAXException {print("start document");}public void endDocument() throws SAXException {print("end document");}public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {print("start element:", localName, qName);}public void endElement(String uri, String localName, String qName) throws SAXException {print("end element:", localName, qName);}public void characters(char[] ch, int start, int length) throws SAXException {print("characters:", new String(ch, start, length));}public void error(SAXParseException e) throws SAXException {print("error:", e);}void print(Object... objs) {for (Object obj : objs) {System.out.print(obj);System.out.print(" ");}System.out.println();}
}

18.3.4 使用Jackson解析XML

名叫Jackson的开源第三方库可以轻松做到XML到JavaBean的转换。
要使用Jackson,先添加两个Maven的依赖:
com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.10.1
org.codehaus.woodstox:woodstox-core-asl:4.4.1

InputStream input = Main.class.getResourceAsStream("/book.xml");
JacksonXmlModule module = new JacksonXmlModule();
XmlMapper mapper = new XmlMapper(module);
Book book = mapper.readValue(input, Book.class);
System.out.println(book.id);
System.out.println(book.name);
System.out.println(book.author);
System.out.println(book.isbn);
System.out.println(book.tags);
System.out.println(book.pubDate);

18.3.4 JSON数据格式

XML的特点是功能全面,但标签繁琐,格式复杂。在Web上使用XML现在越来越少,取而代之的是JSON这种数据结构。

JSON是JavaScript Object Notation的缩写,它去除了所有JavaScript执行代码,只保留JavaScript的对象格式。

JSON作为数据传输的格式,有几个显著的优点:

  • JSON只允许使用UTF-8编码,不存在编码问题;
  • JSON只允许使用双引号作为key,特殊字符用\转义,格式简单;
  • 浏览器内置JSON支持,如果把数据用JSON发送给浏览器,可以用JavaScript直接处理。

因此,JSON适合表示层次结构,因为它格式简单,仅支持以下几种数据类型:

  • 键值对:{“key”: value}
  • 数组:[1, 2, 3]
  • 字符串:“abc”
  • 数值(整数和浮点数):12.34
  • 布尔值:true或false
  • 空值:null

浏览器直接支持使用JavaScript对JSON进行读写:

// JSON string to JavaScript object:
jsObj = JSON.parse(jsonStr);// JavaScript object to JSON string:
jsonStr = JSON.stringify(jsObj);

所以,开发Web应用的时候,使用JSON作为数据传输,在浏览器端非常方便。因为JSON天生适合JavaScript处理,所以,绝大多数REST API都选择JSON作为数据传输格式。

一文彻底弄懂REST API
(https://zhuanlan.zhihu.com/p/536437382)

在Java中,针对JSON也有标准的JSR 353 API,但是我们在前面讲XML的时候发现,如果能直接在XML和JavaBean之间互相转换是最好的。类似的,如果能直接在JSON和JavaBean之间转换,那么用起来就简单多了。

常用的用于解析JSON的第三方库有:
Jackson
Gson
Fastjson

注意到上一节提到的那个可以解析XML的浓眉大眼的Jackson也可以解析JSON!因此我们只需要引入以下Maven依赖:

com.fasterxml.jackson.core:jackson-databind:2.12.0

就可以使用下面的代码解析一个JSON文件:

InputStream input = Main.class.getResourceAsStream("/book.json");
ObjectMapper mapper = new ObjectMapper();
// 反序列化时忽略不存在的JavaBean属性:
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Book book = mapper.readValue(input, Book.class);

核心代码是创建一个ObjectMapper对象。关闭DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES功能使得解析时如果JavaBean不存在该属性时解析不会报错。

把JSON解析为JavaBean的过程称为反序列化。如果把JavaBean变为JSON,那就是序列化。要实现JavaBean到JSON的序列化,只需要一行代码:

String json = mapper.writeValueAsString(book);

要把JSON的某些值解析为特定的Java对象,例如LocalDate,也是完全可以的。例如:

{"name": "Java核心技术","pubDate": "2016-09-01"
}

要解析为:

public class Book {public String name;public LocalDate pubDate;
}

只需要引入标准的JSR 310关于JavaTime的数据格式定义至Maven:

com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.12.0

然后,在创建ObjectMapper时,注册一个新的JavaTimeModule:

ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule());

有些时候,内置的解析规则和扩展的解析规则如果都不满足我们的需求,还可以自定义解析。

举个例子,假设Book类的isbn是一个BigInteger:

public class Book {public String name;public BigInteger isbn;
}

但JSON数据并不是标准的整形格式:

{"name": "Java核心技术","isbn": "978-7-111-54742-6"
}

直接解析,肯定报错。这时,我们需要自定义一个IsbnDeserializer,用于解析含有非数字的字符串:

public class IsbnDeserializer extends JsonDeserializer<BigInteger> {public BigInteger deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {// 读取原始的JSON字符串内容:String s = p.getValueAsString();if (s != null) {try {return new BigInteger(s.replace("-", ""));} catch (NumberFormatException e) {throw new JsonParseException(p, s, e);}}return null;}
}

然后,在Book类中使用注解标注:

public class Book {public String name;// 表示反序列化isbn时使用自定义的IsbnDeserializer:@JsonDeserialize(using = IsbnDeserializer.class)public BigInteger isbn;
}

类似的,自定义序列化时我们需要自定义一个IsbnSerializer,然后在Book类中标注@JsonSerialize(using = …)即可。

反序列化
在反序列化时,Jackson要求Java类需要一个默认的无参数构造方法,否则,无法直接实例化此类。存在带参数构造方法的类,如果要反序列化,注意再提供一个无参数构造方法。

对于enum字段,Jackson按String类型处理,即:

class Book {public DayOfWeek start = MONDAY;
}

序列化为:

{"start": "MONDAY"
}

对于record类型,Jackson会自动找出它的带参数构造方法,并根据JSON的key进行匹配,可直接反序列化。对record类型的支持需要版本2.12.0以上。

18.14 使用Files

从Java 7开始,提供了Files这个工具类,能极大地方便我们读写文件。

虽然Files是java.nio包里面的类,但他俩封装了很多读写文件的简单方法,例如,我们要把一个文件的全部内容读取为一个byte[],可以这么写:

byte[] data = Files.readAllBytes(Path.of("/path/to/file.txt"));

如果是文本文件,可以把一个文件的全部内容读取为String:

// 默认使用UTF-8编码读取:
String content1 = Files.readString(Path.of("/path/to/file.txt"));
// 可指定编码:
String content2 = Files.readString(Path.of("/path", "to", "file.txt"), StandardCharsets.ISO_8859_1);
// 按行读取并返回每行内容:
List<String> lines = Files.readAllLines(Path.of("/path/to/file.txt"));

写入文件也非常方便:

// 写入二进制文件:
byte[] data = ...
Files.write(Path.of("/path/to/file.txt"), data);
// 写入文本并指定编码:
Files.writeString(Path.of("/path/to/file.txt"), "文本内容...", StandardCharsets.ISO_8859_1);
// 按行写入文本:
List<String> lines = ...
Files.write(Path.of("/path/to/file.txt"), lines);

此外,Files工具类还有copy()、delete()、exists()、move()等快捷方法操作文件和目录。

最后需要特别注意的是,Files提供的读写方法,受内存限制,只能读写小文件,例如配置文件等不可一次读入几个G的大文件。读写大型文件仍然要使用文件流,每次只读写一部分文件内容。

Java编程思想 第十八章 Java I/O系统相关推荐

  1. java编程思想怎么样_读完java编程思想后的思考?

    谢邀,这本书真的给我带来很多思考. 我的java入门不是java编程思想,学校的教材是一本紫色的书,已经忘了叫什么名字了,里面内容倒挺新还讲了些javafx.但那本书实在是太浅并且结构混乱,以至于我和 ...

  2. Java编程思想 第十五章:泛型

    1. 泛型 "泛型"意思就是适用于许多类型. 使用泛型的目的之一: 指定容器持有什么类型,让编译器确保正确性,而不是在运行期发现错误. 这个容器可以看成是有其他类型对象作为成员的类 ...

  3. Java编程思想学习(十二) 数组和容器

    一.数组 1).数组的多种初始化方式  下面总结了初始化数组的多种方式,以及如何对指向数组的引用赋值,使其指向另一个数组对象.值得注意的是:对象数组和普通数组的各种操作基本上都是一样的:要说有什么不同 ...

  4. Java编程思想—第十二十三章

    1.在堆中new出异常对象 2.通常用System.err来输出异常,而System.out可能会被重定向,不建议用. 3.在catch中抛出异常throw e.则之后的catch子句将被忽略. 4. ...

  5. java编程思想 初始化_《java编程思想》_第五章_初始化与清理

    初始化和清理是涉及安全的两个问题,java中采用了构造器,并额外提供了"垃圾回收器",对于不再使用的内存资源,垃圾回收器能自动将其释放. 一.用构造器确保初始化 java中,通过提 ...

  6. 《JAVA编程思想》学习笔记:第8章(多态)

    目录 Java编程思想(一)第1~4章:概述 Java编程思想(二)第5章:初始化和清理 Java编程思想(三)第6章:访问权限 Java编程思想(四)第7章:复用类 Java编程思想(五)第8章:多 ...

  7. 《JAVA编程思想》学习笔记:第16章(数组)

    目录 Java编程思想(一)第1~4章:概述 Java编程思想(二)第5章:初始化和清理 Java编程思想(三)第6章:访问权限 Java编程思想(四)第7章:复用类 Java编程思想(五)第8章:多 ...

  8. 《JAVA编程思想》学习笔记:第1-4章(Java概述)

    全书目录 Java编程思想(一)第1~4章:概述 Java编程思想(二)第5章:初始化和清理 Java编程思想(三)第6章:访问权限 Java编程思想(四)第7章:复用类 Java编程思想(五)第8章 ...

  9. 《JAVA编程思想》学习笔记:第21章(并发)

    目录 Java编程思想(一)第1~4章:概述 Java编程思想(二)第5章:初始化和清理 Java编程思想(三)第6章:访问权限 Java编程思想(四)第7章:复用类 Java编程思想(五)第8章:多 ...

最新文章

  1. CH Round #30 摆花[矩阵乘法]
  2. 标准SQL注入入侵语句
  3. 讲讲你理解的服务治理
  4. IPSR(中断程序状态寄存器),IPSR包含了当前正在执行的中断服务程序编号,用于识别当前中断。
  5. JavaMail入门第五篇 解析邮件
  6. 《你的灯亮着吗》第一遍读后感
  7. linux服务器发异常包,如何排查Linux服务器上的恶意发包行为
  8. 腾讯电脑管家修复代理服务器,腾讯QQ电脑管家LSP修复在哪
  9. 记录一下java的常用单词
  10. 【云原生监控系列第一篇】一文详解Prometheus普罗米修斯监控系统(山前前后各有风景,有风无风都很自由)
  11. 飞秋教程(飞秋应用管理器)
  12. matlab 飞机大战小游戏
  13. 噜噜噜啦啦啦啦啦啾啾啾~
  14. python爬虫豆瓣电影按电影类型_Python爬虫入门 | 7 分类爬取豆瓣电影,解决动态加载问题...
  15. 阿里安全研究员路全:如何运用AI对抗“数据污染”?
  16. 漫步微积分二十九——微积分基本定理
  17. html中表格边框好看的样式,table完美css样式 table表格边框样式
  18. 草草们的忧伤:环信IM昵称和头像
  19. Half a million dollars is or are a lot of money?
  20. c#移动鼠标到指定坐标并点击

热门文章

  1. 推荐系统产品与算法深度解析
  2. linuxubuntu谷歌浏览器无法安装扩展程序
  3. 200道网络安全常见面试题合集(附答案解析+配套资料)
  4. 面对即将到来的30岁--计划与感悟
  5. 朱氏奇门秘占远行避难
  6. RTKLIB专题学习(十)—电离层改正
  7. ElasticSearch学习笔记(01)
  8. SpringBoot启动报错: Error creating bean with name ‘“XXXX‘ defined in class path resource
  9. 倚澜科技与京东科技达成战略合作
  10. python 进入虚拟环境 source activate 时候报错 Badly placed ()'s