100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > Java编程思想 第十八章 Java I/O系统

Java编程思想 第十八章 Java I/O系统

时间:2022-03-18 05:19:51

相关推荐

Java编程思想 第十八章 Java I/O系统

文章目录

18.1 File类18.1.1 获取当前目录下文件名并做过滤18.1.2 递归获取指定目录下文件集并过滤18.1.3 目录的检查及创建 18.2 输入和输出18.2.1 InputStream类型18.2.1.1 ByteArrayInputStream18.2.1.2 FileInputStream18.2.1.3 PipedInputStream18.2.1.4 SequenceInputStream 18.2.2 OutputStream类型18.2.2.1 FileOutputStream18.2.2.2 ByteArrayOutputStream 18.2.3 实现复制功能 18.3 添加属性和有用的接口18.3.1 Filter模式18.3.2 编写FilterInputStream 18.4 Reader和Writer18.4.1 ReaderFileReaderCharArrayReaderStringReaderInputStreamReader 18.4.2 WriterFileWriterPrintWriterCharArrayWriterStringWriterOutputStreamWriter 18.7 PrintStream和PrintWriter18.7.1 PrintStream18.7.2 PrintWriter 18.8 标准I/O18.8.1 标准输入、输出18.8.2 标准I/O重定向 18.9 进程控制18.10 新I/O18.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与JSON18.3.1 XML数据格式18.3.2 使用DOM解析XML18.3.3 使用SAX解析XML18.3.4 使用Jackson解析XML18.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:\DocsFile f1 = new File("sub\\javac"); // 绝对路径是C:\Docs\sub\javacFile f3 = new File(".\\sub\\javac"); // 绝对路径是C:\Docs\sub\javacFile 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 = pile(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 = pile(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 = pile(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 (十六)----这么多类,应该用哪个?

/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

/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,例如:

FileInputStreamByteArrayInputStreamServletInputStream…

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

BufferedInputStreamDigestInputStreamCipherInputStream…

基础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 进程控制

/zhoujing_0424/article/details/79917368

/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,/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 MENT_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

(/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": "-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 I/O系统。

JAVA I/O系统。

2019-11-13

Java—I/O系统

Java—I/O系统

2020-10-26

java I/O 系统

java I/O 系统

2019-05-12

Java I/O系统

Java I/O系统

2022-03-01