JavaSE-09(Java IO精华总结)

Java IO

在这里插入图片描述

简单做个总结:

  • 1 .InputStream/OutputStream 字节流的抽象类。
  • 2 .Reader/Writer 字符流的抽象类。
  • 3 .FileInputStream/FileOutputStream 节点流:以字节为单位直接操作“文件”。
  • 4 .ByteArrayInputStream/ByteArrayOutputStream 节点流:以字节为单位直接操作“字节数组对象”。
  • 5 .ObjectInputStream/ObjectOutputStream 处理流:以字节为单位直接操作“对象”。
  • 6 .DataInputStream/DataOutputStream 处理流:以字节为单位直接操作“基本数据类型与字符串类型”。
  • 7 .FileReader/FileWriter 节点流:以字符为单位直接操作“文本文件”(注意:只能读写文本文件)。
  • 8 .BufferedReader/BufferedWriter 处理流:将Reader/Writer对象进行包装,增加缓存功能,提高读写效率。
  • 9 .BufferedInputStream/BufferedOutputStream 处理流:将InputStream/OutputStream对象进行包装,增加缓存功能,提高读写效率。
  • 10 .InputStreamReader/OutputStreamWriter 处理流:将字节流对象转化成字符流对象。
  • 11 .PrintStream 处理流:将OutputStream进行包装,可以方便地输出字符,更加灵活。

IO流分类

流按处理的数据单元分类:

  • 字节流:以字节为单位获取数据,命名上以Stream结尾的流一般是字节流,如FileInputStream、FileOutputStream。
  • 字符流:以字符为单位获取数据,命名上以Reader/Writer结尾的流一般是字符流,如FileReader、FileWriter。

流按处理对象不同分类:

  • 节点流:可以直接从数据源或目的地读写数据,如FileInputStream、FileReader、DataInputStream等。
  • 处理流:不直接连接到数据源或目的地,是”处理流的流”。通过对其他流的处理提高程序的性能,如BufferedInputStream、BufferedReader等。处理流也叫包装流。

节点流处于IO操作的第一线,所有操作必须通过它们进行;处理流可以对节点流进行包装,提高性能或提高程序的灵活性。

在这里插入图片描述

IO 流简介

IO 即 Input/Output,输入和输出。

数据输入到计算机内存的过程即输入,反之输出到外部存储(比如数据库,文件,远程主机)的过程即输出。

菜鸟雷区 输入/输出流的划分是相对程序而言的,并不是相对数据源。

在这里插入图片描述

在这里插入图片描述

数据传输过程类似于水流,因此称为 IO 流。我们把数据源和目的地可以理解为IO流的两端。当然,通常情况下,这两端可能是文件或者网络连接。

Java IO 流的 40 多个类都是从如下 4 个抽象类基类中派生出来的。

  • InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。

    任何从InputStream或Reader派生而来的类都有read()基本方法,读取单个字节或字节数组;

    适配器类InputStreamReader 可以将InputStream转成为Reader

在这里插入图片描述

  • OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。

    任何从OutputStream或Writer派生的类都含有write()的基本方法,用于写单个字节或字节数组。

    适配器类OutputStreamWriter可以将OutputStream转成为Writer

    在这里插入图片描述


刚开始写IO代码,总被各种IO流类搞得晕头转向。这么多IO相关的类,各种方法,啥时候能记住。

其实只要我们掌握了IO类库的总体设计思路,理解了它的层次脉络之后,就很清晰。

知道啥时候用哪些流对象去组合想要的功能就好了(装饰器模式),API的话,可以查手册的。


一般在使用IO流的时候会有下面类似代码:

在这里插入图片描述

这里其实是一种装饰器模式的使用,IO流体系中使用了装饰器模式包装了各种功能流类。

在Java IO流体系中FilterInputStream/FilterOutStreamFilterReader/FilterWriter就是装饰器模式的接口类,从该类向下包装了一些功能流类。有DataInputStream、BufferedInputStream、LineNumberInputStream、PushbackInputStream等,当然还有面向字节的输出的功能流类;面向字符的功能流类等。

ReaderWriter的基础功能类,可以对比InputStreamOutputStream来学习

面向字节面向字符
InputStreamReader
OutputStreamWriter
FileInputStreamFileReader
FileOutputStreamFileWriter
ByteArrayInputStreamCharArrayReader
ByteArrayOutputStreamCharArrayWriter
PipedInputStreamPipedReader
PipedOutputStreamPipedWriter
StringBufferInputStream(已弃用)StringReader
无对应类StringWriter

File类

File类其实不止是代表一个文件,它也能代表一个目录下的一组文件(代表一个文件路径)。

常用方法

盘点一下File类中最常用到的一些方法:

在这里插入图片描述

需要注意的是,不同系统对文件路径的分割符表是不一样的,比如Windows中是“\”,Linux是“/”。

而File类给提供了抽象的表示File.separator,屏蔽了系统层差异

因此平时在代码中不要使用诸如“\”这种代表路径,可能造成Linux平台下代码执行错误。

File类在IO中的作用

当以文件作为数据源或目标时,除了可以使用文件路径字符串作为文件以及位置的指定以外,我们也可以使用File类指定。

如下示例:

package com.yoostar.coms;import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;public class FileInIODemo {public static void main(String[] args) {BufferedReader br = null;BufferedWriter bw = null;try {br = new BufferedReader(new FileReader(new File("d:/sxt.txt")));bw = new BufferedWriter(new FileWriter(new File("d:/sxt8.txt")));String temp = "";int i = 1;while ((temp = br.readLine()) != null) {bw.write(i + "," + temp);bw.newLine();i++;}bw.flush();} catch (Exception e) {e.printStackTrace();} finally {try {if (br != null) {br.close();}if (bw != null) {bw.close();}} catch (Exception e) {e.printStackTrace();}}}
}

字节流

InputStream(字节输入流)

InputStream是输入流,前面已经说到,它是从数据源对象将数据读入程序内容时,使用的流对象。

通过看InputStream的源码知道,它是一个抽象类:

在这里插入图片描述

在这里插入图片描述

InputStream 常用方法:

//从数据中读入一个字节,并返回该字节,遇到流的结尾时返回-1
int read() //读入一个字节数组,并返回实际读入的字节数,最多读入b.length个字节,遇到流结尾时返回-1
//如果有可用字节读取,则最多读取的字节数最多等于 `b.length` , 返回读取的字节数。这个方法等价于read(b, 0, b.length)。
int read(byte[] b)// 读入一个字节数组,返回实际读入的字节数或者在碰到结尾时返回-1.
//b:代表数据读入的数组, off:代表第一个读入的字节应该被放置的位置在b中的偏移量,len:读入字节的最大数量
int read(byte[],int off,int len)// 返回当前可以读入的字节数量,如果是从网络连接中读入,这个方法要慎用,
int available() //在输入流中跳过n个字节,返回实际跳过的字节数
long skip(long n)//标记输入流中当前的位置
void mark(int readlimit) //判断流是否支持打标记,支持返回true
boolean markSupported() // 返回最后一个标记,随后对read的调用将重新读入这些字节。
void reset() //关闭输入流,这个很重要,流使用完一定要关闭
void close()

从 Java 9 开始,InputStream 新增加了多个实用的方法:

  • readAllBytes():读取输入流中的所有字节,返回字节数组。
  • readNBytes(byte[] b, int off, int len):阻塞直到读取 len 个字节。
  • transferTo(OutputStream out):将所有字节从一个输入流传递到一个输出流。

直接从InputStream抽象类继承的流,可以发现基本上对应了每种数据源类型:

功能
ByteArrayInputStream将字节数组作为InputStream. 即ByteArrayInputStream是把内存中的”字节数组对象”当做数据源。
StringBufferInputStream将String转成InputStream
FileInputStream从文件中读取内容
PipedInputStream产生用于写入相关PipedOutputStream的数据。实现管道化
SequenceInputStream将两个或多个InputStream对象转换成单一的InputStream
FilterInputStream抽象类,主要是作为“装饰器”的接口类,实现其他的功能流

DataInputStream 用于读取指定类型数据,不能单独使用,必须结合 FileInputStream

DataInputStream和DataOutputStream提供了可以存取与机器无关的所有Java基础类型数据(如:int、double、String等)的方法。

FileInputStream fileInputStream = new FileInputStream("input.txt");
//必须将fileInputStream作为构造参数才能使用
DataInputStream dataInputStream = new DataInputStream(fileInputStream);
//可以读取任意具体的类型数据
dataInputStream.readBoolean();
dataInputStream.readInt();
dataInputStream.readUTF();//writeUTF()和readUTF()来写入和读取字符串

OutputStream(字节输出流)

OutputStream用于将数据(字节信息)写入到目的地(通常是文件)

OutputStream是输出流的抽象基类,它是将程序内存中的数据写入到目的地(也就是接收数据的一端)。

在这里插入图片描述

在这里插入图片描述

OutputStream 常用方法:

在这里插入图片描述

同样地,OutputStream也提供了一些基础流的实现,这些实现也可以和特定的目的地(接收端)对应起来,比如输出到字节数组或者是输出到文件/管道等:

功能
ByteArrayOutputStream在内存中创建一个缓冲区,所有送往“流”的数据都要放在此缓冲区
FileOutputStream将数据写入文件
PipedOutputStream和PipedInputStream配合使用。实现管道化
FilterOutputStream抽象类,主要是作为“装饰器”的接口类,实现其他的功能流

DataOutputStream 用于写入指定类型数据,不能单独使用,必须结合 FileOutputStream

DataInputStream和DataOutputStream提供了可以存取与机器无关的所有Java基础类型数据(如:int、double、String等)的方法。

// 输出流
FileOutputStream fileOutputStream = new FileOutputStream("out.txt");
DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream);
// 输出任意数据类型
dataOutputStream.writeBoolean(true);
dataOutputStream.writeByte(1);

使用装饰器包装有用的流

Java IO 流体系使用了装饰器模式来给基础的输入/输出流添加额外的功能。

额外的功能可能是:可以将流缓冲起来提高性能、使流能够读写基本数据类型等。

通过装饰器模式添加功能的流类型都是从FilterInputStreamFilterOutputStream抽象类扩展而来的。

FilterInputStream类型

功能
DataInputStream和DataOutputStream搭配使用,使得流可以读取int char long等基本数据类型
BufferedInputStream使用缓冲区,主要是提高性能. IO 操作是很消耗性能的,缓冲流将数据加载至缓冲区,一次性读取/写入多个字节,从而避免频繁的 IO 操作,提高流的传输效率。
LineNumberInputStream跟踪输入流中的行号,可以使用getLineNumber、setLineNumber(int)
PushbackInputStream使得流能弹出“一个字节的缓冲区”,可以将读到的最后一个字符回退

FilterOutStream类型

功能
DataOutputStream和DataInputStream搭配使用,使得流可以写入int char long等基本数据类型
PrintStream用于产生格式化的输出
BufferedOutputStream使用缓冲区,可以调用flush()清空缓冲区

因此要理解流的使用就是搭配起来或者使用功能流组合起来去转移或者存储数据。

通过缓冲区提高读写效率

方式一

通过创建一个指定长度的字节数组作为缓冲区,以此来提高IO流的读写效率。

该方式适用于读取较大图片时的缓冲区定义。

**注意:**缓冲区的长度一定是 2 的整数幂。一般情况下1024 长度较为合适。

package com.coms.util.excel;import java.io.FileInputStream;
import java.io.FileOutputStream;public class FileStreamBuffedDemo {public static void main(String[] args) {FileInputStream fis = null;FileOutputStream fos = null;try {//创建文件字节输入流对象fis = new FileInputStream("d:/1.png");//创建文件字节输出流对象fos = new FileOutputStream("d:/3.png");//创建一个缓冲区,提高读写效率byte[] buff = new byte[1024];int temp = 0;while ((temp = fis.read(buff)) != -1) {fos.write(buff, 0, temp);}} catch (Exception e) {e.printStackTrace();} finally {try {if (fis != null) {fis.close();}if (fos != null) {fos.close();}} catch (Exception e) {e.printStackTrace();}}}
}
方式二

通过创建一个字节数组作为缓冲区,数组长度是通过输入流对象的available()返回当前文件的预估长度来定义的。在读写文件时,是在一次读写操作中完成文件读写操作的。注意:如果文件过大,那么对内存的占用也是比较大的。所以大文件不建议使用该方法。

package com.yoostar.coms;import java.io.FileInputStream;
import java.io.FileOutputStream;public class FileStreamBuffer2Demo {public static void main(String[] args) {FileInputStream fis = null;FileOutputStream fos = null;try {//创建文件字节输入流对象 fis = new FileInputStream("d:/itbz.jpg");//创建文件字节输出流对象 fos = new FileOutputStream("d:/cc.jpg");//创建一个缓冲区,提高读写效率 byte[] buff = new byte[fis.available()];fis.read(buff);//将数据从内存中写入到磁盘中。 fos.write(buff);} catch (Exception e) {e.printStackTrace();} finally {try {if (fis != null) {fis.close();}if (fos != null) {fos.close();}} catch (Exception e) {e.printStackTrace();}}}
}

通过缓冲流提高读写效率

Java缓冲流本身并不具有IO流的读取与写入功能,只是在别的流(节点流或其他处理流)上加上缓冲功能提高效率,就像是把别的流包装起来一样,因此缓冲流是一种处理流(包装流)。 当对文件或者其他数据源进行频繁的读写操作时,效率比较低,这时如果使用缓冲流就能够更高效的读写信息。因为缓冲流是先将数据缓存起来,然后当缓存区存满后或者手动刷新时再一次性的读取到程序或写入目的地。 因此,缓冲流还是很重要的,我们在IO操作时记得加上缓冲流来提升性能。BufferedInputStreamBufferedOutputStream这两个流是缓冲字节流,通过内部缓存数组来提高操作流的效率。

package com.yoostar.coms;import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;public class FileStreamBuffed3Demo {public static void main(String[] args) {FileInputStream fis = null;FileOutputStream fos = null;BufferedInputStream bis = null;BufferedOutputStream bos = null;try {fis = new FileInputStream("d:/itbz.jpg");bis = new BufferedInputStream(fis);fos = new FileOutputStream("d:/ff.jpg");bos = new BufferedOutputStream(fos);//缓冲流中的 byte 数组长度默认是 8192int temp = 0;while ((temp = bis.read()) != -1) {bos.write(temp);}} catch (Exception e) {e.printStackTrace();} finally {try {//注意:关闭流顺序:"后开的先关闭"if (bis != null) {bis.close();}if (fis != null) {fis.close();}if (bos != null) {bos.close();}if (fos != null) {fos.close();}} catch (Exception e) {e.printStackTrace();}}}
}

字符流

不管是文件读写还是网络发送接收,信息的最小存储单元都是字节。

那为什么 I/O 流操作要分为字节流操作和字符流操作呢?

个人认为主要有两点原因

  • 字符流是由 Java 虚拟机将字节转换得到的,这个过程还算是比较耗时。
  • 如果字节流且我们不知道编码类型就很容易出现中文乱码问题。

因此,I/O 流就干脆提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。

字符流默认采用的是 Unicode 编码,我们可以通过构造方法自定义编码。

utf8 :英文占 1 字节,中文占 3 字节,unicode:任何字符都占 2 个字节,gbk:英文占 1 字节,中文占 2 字节。

如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好

Reader(字符输入流)

Reader用于从源头(通常是文件)读取数据(字符信息)到内存中,java.io.Reader抽象类是所有字符输入流的父类。

ReaderInputStream类似,不同点在于,Reader基于字符而非基于字节。

在这里插入图片描述

Reader 常用方法:

  • read() : 从输入流读取一个字符。
  • read(char[] cbuf) : 从输入流中读取一些字符,并将它们存储到字符数组 cbuf中,等价于 read(cbuf, 0, cbuf.length)
  • read(char[] cbuf, int off, int len):在read(char[] cbuf) 方法的基础上增加了 off 参数(偏移量)和 len 参数(要读取的最大字符数)。
  • skip(long n):忽略输入流中的 n 个字符 ,返回实际忽略的字符数。
  • close() : 关闭输入流并释放相关的系统资源。

InputStreamReader 是字节流转换为字符流的桥梁,其子类 FileReader 是基于该基础上的封装,可以直接操作字符文件。

// 字节流转换为字符流的桥梁
public class InputStreamReader extends Reader {
}
// 用于读取字符文件
public class FileReader extends InputStreamReader {
}

FileReader 代码示例:

try (FileReader fileReader = new FileReader("input.txt");) {int content;long skip = fileReader.skip(3);System.out.println("The actual number of bytes skipped:" + skip);System.out.print("The content read from file:");while ((content = fileReader.read()) != -1) {System.out.print((char) content);}
} catch (IOException e) {e.printStackTrace();
}

input.txt 文件内容:

img

输出:

The actual number of bytes skipped:3
The content read from file:我是Guide。

Writer(字符输出流)

Writer用于将数据(字符信息)写入到目的地(通常是文件),java.io.Writer抽象类是所有字符输出流的父类。

在这里插入图片描述

Writer 常用方法:

  • write(int c) : 写入单个字符。
  • write(char[] cbuf):写入字符数组 cbuf,等价于write(cbuf, 0, cbuf.length)
  • write(char[] cbuf, int off, int len):在write(char[] cbuf) 方法的基础上增加了 off 参数(偏移量)和 len 参数(要读取的最大字符数)。
  • write(String str):写入字符串,等价于 write(str, 0, str.length())
  • write(String str, int off, int len):在write(String str) 方法的基础上增加了 off 参数(偏移量)和 len 参数(要读取的最大字符数)。
  • append(CharSequence csq):将指定的字符序列附加到指定的 Writer 对象并返回该 Writer 对象。
  • append(char c):将指定的字符附加到指定的 Writer 对象并返回该 Writer 对象。
  • flush():刷新此输出流并强制写出所有缓冲的输出字符。
  • close():关闭输出流释放相关的系统资源。

OutputStreamWriter 是字节流转换为字符流的桥梁,其子类 FileWriter 是基于该基础上的封装,可以直接将字符写入到文件。

// 字符流转换为字节流的桥梁
public class OutputStreamWriter extends Writer {
}
// 用于写入字符到文件
public class FileWriter extends OutputStreamWriter {
}

FileWriter 代码示例:

try (Writer output = new FileWriter("output.txt")) {output.write("你好,我是Guide。");
} catch (IOException e) {e.printStackTrace();
}

输出结果:

img

使用装饰器包装有用的流

当然也有类似字节流的装饰器实现方式,给字符流添加额外的功能或这说是行为。

这些功能字符流类主要有:

  • BufferedReader

    BufferedReader是针对字符输入流的缓冲流对象,提供了更方便的按行读取的方法:readLine();

    在使用字符流读取文本文件时,我们可以使用该方法以行为单位进行读取.

  • BufferedWriter

    BufferedWriter是针对字符输出流的缓冲流对象,在字符输出缓冲流中可以使用newLine();方法实现换行处理。

  • PrintWriter

    在Java的IO流中专门提供了用于字符输出的流对象PrintWriter。

    该对象具有自动行刷新缓冲字符输出流,特点是可以按行写出字符串,并且可通过println();方法实现自动换行。

  • LineNumberReader

  • PushbackReader

转换流

InputStreamReader/OutputStreamWriter用来实现将字节流转化成字符流。比如,如下场景:

System.in是字节流对象,代表键盘的输入,如果我们想按行接收用户的输入时,就必须用到缓冲字符流BufferedReader特有的方法readLine(),但是经过观察会发现在创建BufferedReader的构造方法的参数必须是一个Reader对象,这时候我们的转换流InputStreamReader就派上用场了。而System.out也是字节流对象,代表输出到显示器,按行读取用户的输入后,并且要将读取的一行字符串直接显示到控制台,就需要用到字符流的write(Stringstr)方法,所以我们要使用OutputStreamWriter将字节流转化为字符流。(还可以解决中文乱码的问题)

对象序列化与反序列化

学习链接:https://mp.weixin.qq.com/s/0EfIUB9E-0Oh_Clwuxswuw

序列化就是将对象转成字节序列的过程,反序列化就是将字节序列重组成对象的过程。

在这里插入图片描述

为什么要有对象序列化机制

程序中的对象,其实是存在有内存中,当我们JVM关闭时,无论如何它都不会继续存在了。那有没有一种机制能让对象具有“持久性”呢?序列化机制提供了一种方法,你可以将对象序列化的字节流输入到文件保存在磁盘上。

序列化机制的另外一种意义便是我们可以通过网络传输对象了,Java中的 远程方法调用(RMI),底层就需要序列化机制的保证。

对象序列化的作用有如下两种:

  • 持久化: 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中。
  • 网络通信: 在网络上传送对象的字节序列。比如:服务器之间的数据通信、对象传递。

在Java中怎么实现序列化和反序列化

首先要序列化的对象必须实现一个Serializable接口(这是一个标识接口,起标识作用,不包括任何方法)

在这里插入图片描述

其次需要是用两个对象流类:ObjectInputStream 和ObjectOutputStream

主要使用ObjectInputStream对象的readObject方法从流中读入对象、ObjectOutputStream的writeObject方法将对象写到流中

ObjectOutputStream代表对象输出流,它的writeObject(Objectobj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。只有实现了Serializable接口的类的对象才能被序列化

Serializable接口是一个空接口,只起到标记作用。


下面我们通过序列化机制将一个简单的pojo对象写入到文件,并再次读入到程序内存。

public class Student implements Serializable {private String name;private Integer age;private Integer score;@Overridepublic String toString() {return "Student:" + '\n' +"name = " + this.name + '\n' +"age = " + this.age + '\n' +"score = " + this.score + '\n';}// 序列化public static void serialize(  ) throws IOException {Student student = new Student();student.setName("CodeSheep");student.setAge( 18 );student.setScore( 1000 );ObjectOutputStream objectOutputStream = new ObjectOutputStream( new FileOutputStream( new File("student.txt") ) );objectOutputStream.writeObject( student );objectOutputStream.close();System.out.println("序列化成功!已经生成student.txt文件");System.out.println("==============================================");}//反序列化public static void deserialize(  ) throws IOException, ClassNotFoundException {ObjectInputStream objectInputStream = new ObjectInputStream( new FileInputStream( new File("student.txt") ) );//读出来做一个强转Student student = (Student) objectInputStream.readObject();objectInputStream.close();System.out.println("反序列化结果为:");System.out.println( student );}
}

不想序列化的数据使用transient(瞬时)关键字屏蔽

如果我们上面的user对象有一个password字段,属于敏感信息,这种是不能走序列化的方式的,但是实现了Serializable 接口的对象会自动序列化所有的数据域,怎么办呢?在password字段上加上关键字transient就好了。

在这里插入图片描述

系统IO流:System类中的IO流

在标准IO模型中,Java提供了System.in、System.out和System.error。

System.in

在这里插入图片描述

是一个静态域,未被包装过的InputStream。通常我们会使用BufferedReader进行包装然后一行一行地读取输入,这里就要用到前面说的适配器流InputStreamReaderinputStream转为Reader

System.out

System.out是一个PrintStream流。

System.out一般会把你写到其中的数据输出到控制台上。

System.out通常仅用在类似命令行工具的控制台程序上。

System.out也经常用于打印程序的调试信息(尽管它可能并不是获取程序调试信息的最佳方式)。

System.err

System.err是一个PrintStream流。

System.err与System.out的运行方式类似,但它更多的是用于打印错误文本。

系统流重定向

尽管System.in, System.out, System.err这3个流是java.lang.System类中的静态成员,并且已经预先在JVM启动的时候初始化完成,你依然可以更改它们.

可以使用setIn(InputStream)、setOut(PrintStream)、setErr(PrintStream)进行重定向。

比如可以将控制台的输出重定向到文件中。

在这里插入图片描述

解压缩数据流处理

Java IO类库是支持读写压缩格式的数据流的。

这些压缩相关的流类是按字节处理的。

看下设计压缩解压缩的相关流类:

压缩类功能
CheckedInputStreamgetCheckSum()可以为任何InputStream产生校验和(不仅是解压缩)
CheckedOutputStreamgetCheckSum()可以为任何OutputStream产生校验和(不仅是压缩)
DeflaterOutputStream压缩类的基类
ZipOutputStream继承自DeflaterOutputStream,将数据压缩成Zip文件格式
GZIPOutputStream继承自DeflaterOutputStream,将数据压缩成GZIP文件格式
InflaterInputStream解压缩类的基类
ZipInputStream继承自InflaterInputStream,解压缩Zip文件格式的数据
GZIPInputStream继承自InflaterInputStream,解压缩GZIP文件格式的数据

表格中CheckedInputStreamCheckedOutputStream 一般会和Zip压缩解压过程配合使用,主要是为了保证我们压缩和解压过程数据包的正确性,得到的是中间没有被篡改过的数据。

CheckedInputStream 为例,它的构造器需要传入一个Checksum类型:

在这里插入图片描述

而Checksum 是一个接口,因为是接口,所以可以看到这里又用到了策略模式,具体的校验算法是可以选择的。Java类库给我提供了两种校验和算法:Adler32 和 CRC32,性能方面可能Adler32 会更好一些,不过CRC32可能更准确。

压缩(ZIP)

我们可以把一个或一批文件压缩成一个zip文档。

将多个文件压缩成zip包

 1 public class ZipFileUtils {2    public static void compressFiles(File[] files, String zipPath) throws IOException {34        // 定义文件输出流,表明是要压缩成zip文件的5        FileOutputStream f = new FileOutputStream(zipPath);67        // 给输出流增加校验功能8        CheckedOutputStream checkedOs = new CheckedOutputStream(f,new Adler32());9
10        // 定义zip格式的输出流,这里要明白一直在使用装饰器模式在给流添加功能
11        // ZipOutputStream 也是从FilterOutputStream 继承下来的
12        ZipOutputStream zipOut = new ZipOutputStream(checkedOs);
13
14        // 增加缓冲功能,提高性能
15        BufferedOutputStream buffOut = new BufferedOutputStream(zipOut);
16
17        //对于压缩输出流我们可以设置个注释
18        zipOut.setComment("zip test");
19
20        // 下面就是从Files[] 数组中读入一批文件,然后写入zip包的过程
21        for (File file : files){
22
23            // 建立读取文件的缓冲流,同样是装饰器模式使用BufferedReader
24            // 包装了FileReader
25            BufferedReader bfReadr = new BufferedReader(new FileReader(file));
26
27            // 一个文件对象在zip流中用一个ZipEntry表示,使用putNextEntry添加到zip流中
28            zipOut.putNextEntry(new ZipEntry(file.getName()));
29
30            int c;
31            while ((c = bfReadr.read()) != -1){
32                buffOut.write(c);
33            }
34
35            // 注意这里要关闭
36            bfReadr.close();
37            buffOut.flush();
38        }
39        buffOut.close();
40    }
41
42    public static void main(String[] args) throws IOException {
43        String dir = "d:";
44        String zipPath = "d:/test.zip";
45        File[] files = Directory.getLocalFiles(dir,".*\\.txt");
46        ZipFileUtils.compressFiles(files, zipPath);
47    }
48}

解压缩(ZIP)

解压缩zip包到目标文件夹

1    public static void unConpressZip(String zipPath, String destPath) throws IOException {2        if(!destPath.endsWith(File.separator)){3            destPath = destPath + File.separator;4            File file = new File(destPath);5            if(!file.exists()){6                file.mkdirs();7            }8        }9        // 新建文件输入流类,
10        FileInputStream fis = new FileInputStream(zipPath);
11
12        // 给输入流增加检验功能
13        CheckedInputStream checkedIns = new CheckedInputStream(fis,new Adler32());
14
15        // 新建zip输出流,因为读取的zip格式的文件嘛
16        ZipInputStream zipIn = new ZipInputStream(checkedIns);
17
18        // 增加缓冲流功能,提高性能
19        BufferedInputStream buffIn = new BufferedInputStream(zipIn);
20
21        // 从zip输入流中读入每个ZipEntry对象
22        ZipEntry zipEntry;
23        while ((zipEntry = zipIn.getNextEntry()) != null){
24            System.out.println("解压中" + zipEntry);
25
26            // 将解压的文件写入到目标文件夹下
27            int size;
28            byte[] buffer = new byte[1024];
29            FileOutputStream fos = new FileOutputStream(destPath + zipEntry.getName());
30            BufferedOutputStream bos = new BufferedOutputStream(fos, buffer.length);
31            while ((size = buffIn.read(buffer, 0, buffer.length)) != -1) {
32                bos.write(buffer, 0, size);
33            }
34            bos.flush();
35            bos.close();
36        }
37        buffIn.close();
38
39        // 输出校验和
40        System.out.println("校验和:" + checkedIns.getChecksum().getValue());
41    }
42
43    // 在main函数中直接调用
44    public static void main(String[] args) throws IOException {
45        String dir = "d:";
46        String zipPath = "d:/test.zip";
47//        File[] files = Directory.getLocalFiles(dir,".*\\.txt");
48//        ZipFileUtils.compressFiles(files, zipPath);
49
50        ZipFileUtils.unConpressZip(zipPath,"F:/ziptest");
51    }

IO流的典型使用方式

IO流种类繁多,可以通过不同的方式组合I/O流类,但平时我们常用的也就几种组合。

缓冲输入文件

 1 public class BufferedInutFile {2    public static String readFile(String fileName) throws IOException {3        BufferedReader bf = new BufferedReader(new FileReader(fileName));4        String s;56        // 这里读取的内容存在了StringBuilder,当然也可以做其他处理7        StringBuilder sb = new StringBuilder();8        while ((s = bf.readLine()) != null){9            sb.append(s + "\n");
10        }
11        bf.close();
12        return sb.toString();
13    }
14
15    public static void main(String[] args) throws IOException {
16        System.out.println(BufferedInutFile.readFile("d:/1.txt"));
17    }
18}

格式化内存输入

读取格式化的数据,可以使用DataInputStream

 	1 public class FormattedMemoryInput {2    public static void main(String[] args) throws IOException {3        try {4            DataInputStream dataIns = new DataInputStream(5                    new ByteArrayInputStream(BufferedInutFile.readFile("f:/FormattedMemoryInput.java").getBytes()));6            while (true){7                System.out.print((char) dataIns.readByte());8            }9        } catch (EOFException e) {
10            System.err.println("End of stream");
11        }
12    }
13}

基本的文件输出

FileWriter对象可以向文件写入数据。

首先创建一个FileWriter和指定的文件关联,然后使用BufferedWriter将其包装提供缓冲功能,为了提供格式化机制,它又被装饰成为PrintWriter

 1public class BasicFileOutput {2    static String file = "BasicFileOutput.out";34    public static void main(String[] args) throws IOException {5        BufferedReader in = new BufferedReader(new StringReader(BufferedInutFile.readFile("f:/BasicFileOutput.java")));6        PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file)));78        int lineCount = 1;9        String s;
10        while ((s = in.readLine()) != null){
11            out.println(lineCount ++ + ": " + s);
12        }
13        out.close();
14        in.close();
15    }
16}	

下面是我们写出的BasicFileOutput.out文件,可以看到我们通过代码字节加上了行号

在这里插入图片描述

数据的存储和恢复

为了输出可供另一个“流”恢复的数据,我们需要使用DataOutputStream写入数据,然后使用DataInputStream恢复数据。

当然这些流可以是任何形式(这里的形式其实就是我们前面说过的流的两端的类型),比如文件。

 1public class StoringAndRecoveringData {2    public static void main(String[] args) throws IOException {3        DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("data.txt")));4        out.writeDouble(3.1415926);5        out.writeUTF("三连走起");6        out.writeInt(125);7        out.writeUTF("点赞加关注");8        out.close();9
10        DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream("data.txt")));
11        System.out.println(in.readDouble());
12        System.out.println(in.readUTF());
13        System.out.println(in.readInt());
14        System.out.println(in.readUTF());
15        in.close();
16    }
17}

在这里插入图片描述

随机访问流

这里要介绍的随机访问流指的是支持随意跳转到文件的任意位置进行读写的 RandomAccessFile

RandomAccessFile可以实现两个作用:

  • 1 .实现对一个文件做读和写的操作。
  • 2 .可以访问文件的任意位置。不像其他流只能按照先后顺序读取。

RandomAccessFile 的构造方法如下,我们可以指定 mode(读写模式)。

// openAndDelete 参数默认为 false 表示打开文件并且这个文件不会被删除
public RandomAccessFile(File file, String mode)throws FileNotFoundException {this(file, mode, false);
}
// 私有方法
private RandomAccessFile(File file, String mode, boolean openAndDelete)  throws FileNotFoundException{// 省略大部分代码
}

读写模式主要有下面四种:

  • r : 只读模式。
  • rw: 读写模式
  • rws: 相对于 rwrws 同步更新对“文件的内容”或“元数据”的修改到外部存储设备。
  • rwd : 相对于 rwrwd 同步更新对“文件的内容”的修改到外部存储设备。

文件内容指的是文件中实际保存的数据,元数据则是用来描述文件属性比如文件的大小信息、创建和修改时间。

RandomAccessFile 中有一个文件指针用来表示下一个将要被写入或者读取的字节所处的位置。

我们可以通过 RandomAccessFileseek(long pos) 方法来设置文件指针的偏移量(距文件开头 pos 个字节处)。

如果想要获取文件指针当前的位置的话,可以使用 getFilePointer() 方法。


学习这个流我们需掌握三个核心方法:

  • 1 .RandomAccessFile(Stringname, String mode)name用来确定文件; mode取r(读)或rw(可读写),通过mode可以确定流对文件的访问权限。
  • 2 .seek(long a) 用来定位流对象读写文件的位置,a确定读写位置距离文件开头的字节个数。
  • 3 .getFilePointer() 获得流的当前读写位置。

RandomAccessFile 代码示例:

RandomAccessFile randomAccessFile = new RandomAccessFile(new File("input.txt"), "rw");
System.out.println("读取之前的偏移量:" + randomAccessFile.getFilePointer() + ",当前读取到的字符" + (char) randomAccessFile.read() + ",读取之后的偏移量:" + randomAccessFile.getFilePointer());
// 指针当前偏移量为 6
randomAccessFile.seek(6);
System.out.println("读取之前的偏移量:" + randomAccessFile.getFilePointer() + ",当前读取到的字符" + (char) randomAccessFile.read() + ",读取之后的偏移量:" + randomAccessFile.getFilePointer());
// 从偏移量 7 的位置开始往后写入字节数据
randomAccessFile.write(new byte[]{'H', 'I', 'J', 'K'});
// 指针当前偏移量为 0,回到起始位置
randomAccessFile.seek(0);
System.out.println("读取之前的偏移量:" + randomAccessFile.getFilePointer() + ",当前读取到的字符" + (char) randomAccessFile.read() + ",读取之后的偏移量:" + randomAccessFile.getFilePointer());

input.txt 文件内容:

img

输出:

读取之前的偏移量:0,当前读取到的字符A,读取之后的偏移量:1
读取之前的偏移量:6,当前读取到的字符G,读取之后的偏移量:7
读取之前的偏移量:0,当前读取到的字符A,读取之后的偏移量:1

input.txt 文件内容变为 ABCDEFGHIJK

RandomAccessFilewrite 方法在写入对象的时候如果对应的位置已经有数据的话,会将其覆盖掉。

RandomAccessFile randomAccessFile = new RandomAccessFile(new File("input.txt"), "rw");
randomAccessFile.write(new byte[]{'H', 'I', 'J', 'K'});

假设运行上面这段程序之前 input.txt 文件内容为 ABCD ,运行之后则变为 HIJK

RandomAccessFile的应用

RandomAccessFile 比较常见的一个应用就是实现大文件的 断点续传

何谓断点续传?简单来说就是上传文件中途暂停或失败(比如遇到网络问题)之后,不需要全部重新上传,只需要上传那些未成功上传的文件分片即可。分片(先将文件切分成多个文件分片)上传是断点续传的基础。

RandomAccessFile 可以帮助我们合并文件分片,示例代码如下:

img

我在《Java 面试指北》open in new window中详细介绍了大文件的上传问题。

img

RandomAccessFile 的实现依赖于 FileDescriptor (文件描述符) 和 FileChannel (内存映射文件)。


Apache IO包

JDK中提供的文件操作相关的类,但是功能都非常基础,进行复杂操作时需要做大量编程工作。实际开发中,往往需要你自己动手编写相关的代码,尤其在遍历目录文件时,经常用到递归,非常繁琐。

Apache-commons-io工具包中提供了IOUtils/FileUtils,可以让我们非常方便的对文件和目录进行操作。

Apache IOUtils和FileUtils类库为我们提供了更加简单、功能更加强大的文件操作和IO流操作功能。


FileUtils的使用

FileUtils类中常用方法:

  • cleanDirectory:清空目录,但不删除目录。
  • contentEquals:比较两个文件的内容是否相同。
  • copyDirectory:将一个目录内容拷贝到另一个目录。可以通过FileFilter过滤需要拷贝的文件。
  • copyFile:将一个文件拷贝到一个新的地址。
  • copyFileToDirectory:将一个文件拷贝到某个目录下。
  • copyInputStreamToFile:将一个输入流中的内容拷贝到某个文件。
  • deleteDirectory:删除目录。
  • deleteQuietly:删除文件。
  • listFiles:列出指定目录下的所有文件。
  • openInputSteam:打开指定文件的输入流。
  • readFileToString:将文件内容作为字符串返回。
  • readLines:将文件内容按行返回到一个字符串数组中。
  • size:返回文件或目录的大小。
  • write:将字符串内容直接写到文件中。
  • writeByteArrayToFile:将字节数组内容写到文件中。
  • writeLines:将容器中的元素的toString方法返回的内容依次写入文件中。
  • writeStringToFile:将字符串内容写到文件中。

demo1:

package com.yoostar.coms;import org.apache.commons.io.FileUtils;public class FileUtilsDemo1 {public static void main(String[] args) throws Exception {String content = FileUtils.readFileToString(new File("d:/sxt.txt"), "utf-8");System.out.println(content);}
}

demo2:

package com.yoostar.coms;import org.apache.commons.io.FileUtils;public class FileUtilsDemo2 {public static void main(String[] args) throws Exception {FileUtils.copyDirectory(new File("d:/a"), new File("c:/a"), new FileFilter() {//在文件拷贝时的过滤条件@Overridepublic boolean accept(File pathname) {if (pathname.isDirectory() || pathname.getName().endsWith("html")) {return true;}return false;}});}
}

IOUtils的使用

  • buffer方法:将传入的流进行包装,变成缓冲流。并可以通过参数指定缓冲大小。
  • closeQueitly方法:关闭流。
  • contentEquals方法:比较两个流中的内容是否一致。
  • copy方法:将输入流中的内容拷贝到输出流中,并可以指定字符编码。
  • copyLarge方法:将输入流中的内容拷贝到输出流中,适合大于 2 G内容的拷贝。
  • lineIterator方法:返回可以迭代每一行内容的迭代器。
  • read方法:将输入流中的部分内容读入到字节数组中。
  • readFully方法:将输入流中的所有内容读入到字节数组中。
  • readLine方法:读入输入流内容中的一行。
  • toBufferedInputStream,toBufferedReader:将输入转为带缓存的输入流。
  • toByteArray,toCharArray:将输入流的内容转为字节数组、字符数组。
  • toString:将输入流或数组中的内容转化为字符串。
  • write方法:向流里面写入内容。
  • writeLine方法:向流里面写入一行内容

demo

package com.yoostar.coms;import java.io.FileInputStream;public class IOUtilsDemo {public static void main(String[] args) throws Exception {String content = IOUtils.toString(new FileInputStream("d:/sxt.txt"), "utf-8");System.out.println(content);}
}

IO流总结

按流的方向分类:

  • 输入流:数据源到程序(InputStream、Reader读进来)。
  • 输出流:程序到目的地(OutPutStream、Writer写出去)。

按流的处理数据单元分类:

  • 字节流:按照字节读取数据(InputStream、OutputStream)。
  • 字符流:按照字符读取数据(Reader、Writer)。

按流的功能分类:

  • 节点流:可以直接从数据源或目的地读写数据。
  • 处理流:不直接连接到数据源或目的地,是处理流的流。通过对其他流的处理提高程序的性能。

IO的四个基本抽象类:

InputStream、OutputStream、Reader、Writer

InputStream的实现类:

  • FileInputStream
  • ByteArrayInutStream
  • BufferedInputStream
  • DataInputStream
  • ObjectInputStream

OutputStream的实现类:

  • FileOutputStream
  • ByteArrayOutputStream
  • BufferedOutputStream
  • DataOutputStream
  • ObjectOutputStream
  • PrintStream

Reader的实现类

  • FileReader
  • BufferedReader
  • InputStreamReader

Writer的实现类

  • FileWriter
  • BufferedWriter
  • OutputStreamWriter

Java IO设计模式

参考学习链接:Java IO 设计模式总结 | JavaGuide(Java面试 + 学习指南)

  • 装饰器模式
  • 适配器模式
  • 工厂模式
  • 观察者模式

JAVA IO模型详解

I/O

何为 I/O?

I/O(Input/Outpu) 即输入/输出

我们先从计算机结构的角度来解读一下 I/O。

根据冯.诺依曼结构,计算机结构分为 5 大部分:运算器、控制器、存储器、输入设备、输出设备。

冯诺依曼体系结构冯诺依曼体系结构

输入设备(比如键盘)和输出设备(比如显示器)都属于外部设备。网卡、硬盘这种既可以属于输入设备,也可以属于输出设备。

输入设备向计算机输入数据,输出设备接收计算机输出的数据。

从计算机结构的视角来看的话, I/O 描述了计算机系统与外部设备之间通信的过程。

我们再先从应用程序的角度来解读一下 I/O。

根据大学里学到的操作系统相关的知识:为了保证操作系统的稳定性和安全性,一个进程的地址空间划分为 用户空间(User space)内核空间(Kernel space )

像我们平常运行的应用程序都是运行在用户空间,只有内核空间才能进行系统态级别的资源有关的操作,比如文件管理、进程通信、内存管理等等。也就是说,我们想要进行 IO 操作,一定是要依赖内核空间的能力。

并且,用户空间的程序不能直接访问内核空间。

当想要执行 IO 操作时,由于没有执行这些操作的权限,只能发起系统调用请求操作系统帮忙完成。

因此,用户进程想要执行 IO 操作的话,必须通过 系统调用 来间接访问内核空间

我们在平常开发过程中接触最多的就是 磁盘 IO(读写文件)网络 IO(网络请求和响应)

从应用程序的视角来看的话,我们的应用程序对操作系统的内核发起 IO 调用(系统调用),操作系统负责的内核执行具体的 IO 操作。也就是说,我们的应用程序实际上只是发起了 IO 操作的调用而已,具体 IO 的执行是由操作系统的内核来完成的。

当应用程序发起 I/O 调用后,会经历两个步骤:

  1. 内核等待 I/O 设备准备好数据
  2. 内核将数据从内核空间拷贝到用户空间。

有哪些常见的 IO 模型?

UNIX 系统下, IO 模型一共有 5 种:同步阻塞 I/O同步非阻塞 I/OI/O 多路复用信号驱动 I/O异步 I/O

这也是我们经常提到的 5 种 IO 模型。

Java 中 3 种常见 IO 模型

BIO (Blocking I/O)

BIO 属于同步阻塞 IO 模型

同步阻塞 IO 模型中,应用程序发起 read 调用后,会一直阻塞,直到内核把数据拷贝到用户空间。

图源:《深入拆解Tomcat & Jetty》图源:《深入拆解Tomcat & Jetty》

在客户端连接数量不高的情况下,是没问题的。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。

NIO (Non-blocking/New I/O)

Java 中的 NIO 于 Java 1.4 中引入,对应 java.nio 包,提供了 Channel , SelectorBuffer 等抽象。NIO 中的 N 可以理解为 Non-blocking,不单纯是 New。它是支持面向缓冲的,基于通道的 I/O 操作方法。 对于高负载、高并发的(网络)应用,应使用 NIO 。

Java 中的 NIO 可以看作是 I/O 多路复用模型。也有很多人认为,Java 中的 NIO 属于同步非阻塞 IO 模型。

跟着我的思路往下看看,相信你会得到答案!

我们先来看看 同步非阻塞 IO 模型

图源:《深入拆解Tomcat & Jetty》图源:《深入拆解Tomcat & Jetty》

同步非阻塞 IO 模型中,应用程序会一直发起 read 调用,等待数据从内核空间拷贝到用户空间的这段时间里,线程依然是阻塞的,直到在内核把数据拷贝到用户空间。

相比于同步阻塞 IO 模型,同步非阻塞 IO 模型确实有了很大改进。通过轮询操作,避免了一直阻塞。

但是,这种 IO 模型同样存在问题:应用程序不断进行 I/O 系统调用轮询数据是否已经准备好的过程是十分消耗 CPU 资源的。

这个时候,I/O 多路复用模型 就上场了。

img

IO 多路复用模型中,线程首先发起 select 调用,询问内核数据是否准备就绪,等内核把数据准备好了,用户线程再发起 read 调用。read 调用的过程(数据从内核空间 -> 用户空间)还是阻塞的。

目前支持 IO 多路复用的系统调用,有 select,epoll 等等。select 系统调用,目前几乎在所有的操作系统上都有支持。

  • select 调用:内核提供的系统调用,它支持一次查询多个系统调用的可用状态。几乎所有的操作系统都支持。
  • epoll 调用:linux 2.6 内核,属于 select 调用的增强版本,优化了 IO 的执行效率。

IO 多路复用模型,通过减少无效的系统调用,减少了对 CPU 资源的消耗。

Java 中的 NIO ,有一个非常重要的选择器 ( Selector ) 的概念,也可以被称为 多路复用器。通过它,只需要一个线程便可以管理多个客户端连接。当客户端数据到了之后,才会为其服务。

img

AIO (Asynchronous I/O)

AIO 也就是 NIO 2。Java 7 中引入了 NIO 的改进版 NIO 2,它是异步 IO 模型。

异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。

img

目前来说 AIO 的应用还不是很广泛。Netty 之前也尝试使用过 AIO,不过又放弃了。这是因为,Netty 使用了 AIO 之后,在 Linux 系统上的性能并没有多少提升。

最后,来一张图,简单总结一下 Java 中的 BIO、NIO、AIO。

img


The End!!创作不易,欢迎点赞/评论!!欢迎关注个人GZH

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/718602.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Running job: job_1709516801756_0003

** yarn运行卡在Running job: job_1709516801756_0003问题解决: ** 在运行wordcount时出现错误,一直卡住 运行命令:hadoop jar share/hadoop/mapreduce/hadoop-mapreduce-examples-3.1.3.jar wordcount /input /output 出现错误&#xff1a…

经典思路!人参叶际微生物如何发8分文章?

中国中医科学院中药研究所在《Environmental Microbiome》期刊上(IF7.9)发表了关于叶际真菌微生态网络的文章,该研究通过对ITS测序结果和环境因子测定结果以及皂苷含量测定结果进行生信分析,提出了维持微生态网络的稳定性策略和影响皂苷含量的因素。 期刊…

H12-821_113

113.如图所示是路由器现ATE输出的部分信息,以下关于这部分信息的描述,错误的是哪一项? A.display pim rp-info命令用来查看组播组对应的RP信息 B.RP地址是2.2.2.2 C.组地址是225.0.0.0 D.RP的优先级是0 答案:C 注释: …

HCIA-Datacom题库(自己整理分类的)_29_PPP协议判断【6道题】

1.数据链路层采用PPP封装链路两端的IP地址可以不在同一个网段。√ 2.PPP链路两端不在同一网段不能通信。 3.参考以下拓扑及配置,路由器R1与R2通过Serial低速线缆连接,且数据链路层封装使用PPP。当R1和R2的Holdtime不一致时,PPP协商失败&…

爬虫实战——麻省理工学院新闻

文章目录 发现宝藏一、 目标二、 浅析三、获取所有模块四、请求处理模块、版面、文章1. 分析切换页面的参数传递2. 获取共有多少页标签并遍历版面3.解析版面并保存版面信息4. 解析文章列表和文章5. 清洗文章6. 保存文章图片 五、完整代码六、效果展示 发现宝藏 前些天发现了一…

MySQL面试题-日志(答案版)

日志 1、为什么需要 undo log? (1)实现事务回滚,保障事务的原子性。 事务处理过程中,如果出现了错误或者用户执 行了 ROLLBACK 语句,MySQL 可以利用 undo log 中的历史数据将数据恢复到事务开始之前的状态…

ssh无法直接登入Linux超级用户root(23/3/3更新)

说明:不允许ssh用超级用户的身份登入是为了安全性,如果只是学习使用对安全性没啥要求可以按以下操作解除限制 以普通用户登录到服务器后,执行以下命令以编辑 SSH 服务器配置文件 /etc/ssh/sshd_config sudo nano /etc/ssh/sshd_config 此时会…

【C++练级之路】【Lv.10】【STL】priority_queue类和反向迭代器的模拟实现

快乐的流畅:个人主页 个人专栏:《C语言》《数据结构世界》《进击的C》 远方有一堆篝火,在为久候之人燃烧! 文章目录 一、仿函数1.1 仿函数的介绍1.2 仿函数的优势 二、priority_queue2.1 push2.2 pop2.3 top2.4 size2.5 empty 三、…

【3D Slicer】心脏CT图像分割操作保姆级教程 Cardiac CT image segmentation

心脏CT图像分割操作流程指南 1 安装3D Slicer软件2 打开文件2.1 从File->Add Data->Choose File2.2 直接拖入 3 进行分割操作4 切片填充 Fill between slices5 第二个例子6 数据保存7 打开保存后的文件 1 安装3D Slicer软件 方式二选一 1.官网:3D Slicer 2.百…

无字母数字rce总结(自增、取反、异或、或、临时文件上传)

目录 自增 取反 异或 或 临时文件上传 自增 自 PHP 8.3.0 起,此功能已软弃用 在 PHP 中,可以递增非数字字符串。该字符串必须是字母数字 ASCII 字符串。当到达字母 Z 且递增到下个字母时,将进位到左侧值。例如,$a Z; $a;将…

Java中的Object类详解

Java中的Object类详解 1. equals(Object obj)2. hashCode()3. toString()4.getClass()5.notify() 和 notifyAll()6. wait() 和 wait(long timeout)7. clone()8.finalize() Java中的 Object 类是所有类的父类,可以被所有Java类继承并使用。下面先看下源码&#xff1a…

google最新大语言模型gemma本地化部署

Gemma是google推出的新一代大语言模型,构建目标是本地化、开源、高性能。 与同类大语言模型对比,它不仅对硬件的依赖更小,性能却更高。关键是完全开源,使得对模型在具有行业特性的场景中,有了高度定制的能力。 Gemma模…

面试数据库篇(mysql)- 12分库分表

拆分策略 垂直分库 垂直分库:以表为依据,根据业务将不同表拆分到不同库中。 特点: 按业务对数据分级管理、维护、监控、扩展在高并发下,提高磁盘IO和数据量连接数垂直分表:以字段为依据,根据字段属性将不同字段拆分到不同表中。 特点: 1,冷热数据分离 2,减少IO过渡争…

【Micropython基础】TCP客户端与服务器

文章目录 前言一、连接Wifi1.1 创建STA接口1.2 激活wifi接口1.3 连接WIFI1.4 判断WIFI是否连接1.5 连接WIFI总体代码 二、创建TCP 客户端2.1 创建套接字2.2 设置TCP服务器的ip地址和端口2.3 连接TCP服务器2.3 发送数据2.4 接收数据2.5 断开连接2.6 示例代码 三、TCP服务器的创建…

批量二维码的教程和优势:拓宽应用领域,提升效率与创新

随着二维码技术的不断发展,批量二维码在多个领域展现出了显著的优势,为商业和行业带来了更多便捷和创新。以下是批量二维码的一些显著优势: 1. 高效快速生成: 批量二维码一次性生成多个二维码,相较于逐个生成的方式&…

Linux之进程信号

目录 一、概念引入 1、生活中的信号 2、Linux中的信号 二、信号处理常见方式 三、信号的产生 1、键盘产生信号 2、系统调用接口产生信号 3、软件条件产生信号 4、硬件异常产生信号 四、信号的保存 相关概念 信号保存——三个数据结构 信号集——sigset_t 信号集操…

超简单的chatgpt-next-web部署教程!

随着AI的应用变广,各类AI程序已逐渐普及,尤其是在一些日常办公、学习等与撰写/翻译文稿密切相关的场景,大家都希望找到一个适合自己的稳定可靠的ChatGPT软件来使用。 ChatGPT-Next-Web就是一个很好的选择。它是一个Github上超人气的免费开源…

Docker基础教程 - 1 Docker简介

更好的阅读体验:点这里 ( www.doubibiji.com ) 1 Docker简介 Docker是一个强大的容器化平台,让你能够更轻松地构建、部署和运行应用程序。 下面我们来学习 Docker。 1.1 Docker是什么 1 现在遇到的问题 每次部署一台服务器&…

彻底解决华为手机安装谷歌框架后出现未认证的弹窗问题

引言 本人使用华为手机通过B站等平台学习如何安装谷歌框架与商店后,发现安装谷歌框架后出现未认证的弹窗问题少有解决办法,而且容易复发,在借鉴相关视频后找到解决办法,但视频中的华谷框架需要付费才能使用,本文将提出…

spring注解驱动系列--自动装配

Spring利用依赖注入(DI),完成对IOC容器中中各个组件的依赖关系赋值;依赖注入是spring ioc的具体体现,主要是通过各种注解进行属性的自动注入。 一、Autowired:自动注入 一、注解介绍 1、默认优先按照类型去…