Java IO 知识体系(整理)

Java IO 知识体系

IO 知识背景

  • 在程序与外部设备(文件、网络、管道)之间数据的传输需要以 “流” 作为媒介进行传输

    • 特性:

      • 先进先出:最先写入输出流的数据最先被输入流读取到;
      • 顺序存取:不能随机访问中间的数据;
      • 只读或者只写:同一个流不能具备两个功能;
    • 传输方式划分:

      • 字节流:字节流可以处理一切文件,一般用来处理图像、视频、音频、PPT、Word 等类型的文件;

        • InputStream 类:
          • int read():读取数据
          • int read(byte b[], int off, int len):从第 off 位置开始读,读取 len 长度的字节,然后放入数组 b 中
          • long skip(long n):跳过指定个数的字节
          • int available():返回可读的字节数
          • void close():关闭流,释放资源
        • OutputStream 类:
          • void write(int b): 写入一个字节,虽然参数是一个 int 类型,但只有低 8 位才会写入,高 24 位会舍弃
          • void write(byte b[], int off, int len): 将数组 b 中的从 off 位置开始,长度为 len 的字节写入
          • void flush(): 强制刷新,将缓冲区的数据写入
          • void close():关闭流
      • 字符流:字符流只能处理纯文本文件(自动处理字符编码)——————>字符流为字节流的子类;

        • Reader 类;
          • int read():读取单个字符
          • int read(char cbuf[], int off, int len):从第 off 位置开始读,读取 len 长度的字符,然后放入数组 b 中
          • long skip(long n):跳过指定个数的字符
          • int ready():是否可以读了
          • void close():关闭流,释放资源
        • Writer 类:
          • void write(int c): 写入一个字符
          • void write( char cbuf[], int off, int len): 将数组 cbuf 中的从 off 位置开始,长度为 len 的字符写入
          • void flush(): 强制刷新,将缓冲区的数据写入
          • void close():关闭流

      PS: >为什么只有字节流和字符流,其他的流,如音频流呢?
      答:字节流和字符流为处理数据提供了基本框架,而特定类型的流(如音频流、视频流等)通常是在这个框架之上,通过其他库或接口来实现的;
      为什么我字节流可以对所有的数据类型进行调用对应的库或接口,还需要这个字符流呢?
      答:尽管字节流可以处理所有类型的数据,但字符流对于文本数据的处理更加高效和方便。根据具体的应用场景选择合适的流类型,可以提高代码的可读性和性能。

      • 处理文本数据:字符流专门用于处理字符(文本)数据,能够正确处理不同的字符编码(如 UTF-8、GBK 等)。
      • 简化编码:字符流自动处理字符的编码和解码,减少了手动转换的复杂性。
      • 可读性:字符流在处理文本时,通常更易于理解和使用,尤其是在读取和写入文本文件

      字节流和字符流缓存区别:
      答:字节流本身没有缓冲区,缓冲字节流相对于字节流,效率提升非常高。而字符流本身就带有缓冲区,缓冲字符流相对于字符流效率提升就不是那么大了

  • 操作对象划分(所有的程序,在执行的时候,都是在内存上进行的,一旦关机,中的数据就没了,那如果想要持久化,就需要把内存中的数据输出到外部):

    • Input:将外部的数据读入内存,比如说把文件从硬盘读取到内存,从网络数据到内存等等

    • Output:将内存中的数据写入到外部,比如说把数据从内存写入到文件,据从内存输出到网络等等。

    • 由上面的不同操作对象可以设置不同类型的流进行变换,如:

      流类型描述
      文件流 (FileInputStream)输入输出控制读写文件数据。
      内存流 (ByteArrayInputStream)在内存中读写数据,适用于压缩、加密、序列化等操作。优点是无需创建临时文件,提高效率;缺点是存储数据量有限,可能导致内存溢出。
      管道 (PipedOutputStream)实现不同线程之间的数据传输,适用于线程间通信和数据传递。局限性是只能在同一 JVM 中的线程之间使用。
      数据类型 (DataInputStream)提供基本的读写操作。
      缓冲 (Buffer)在内存中设置缓冲区,优化读写性能;只有当缓冲区存储足够多数据或调用 flush() 方法时,才与内存或硬盘交互。
      打印流 (PrintStream / PrintWriter)PrintStream 输出字节数据,而 PrintWriter 输出字符数据,二者的使用方式几乎相同。
      对象序列化/反序列化ObjectOutputStream 将 Java 对象转成字节数组,便于保存或传输;
      ObjectInputStream 将字节数组转为 Java 对象。
      转换InputStreamReader 是从字节流到字符流的桥连接,它使用指定的字符集读取字节并将它们解码为字符.
      OutputStreamWriter 将一个字符流的输出对象变为字节流的输出对象,是字符流通向字节流的桥梁

一、文件流

Java IO (Input/Output) 中的文件流是用于读写文件的核心组件。它们允许程序与文件系统进行交互,实现数据的持久化存储和读取。

文件类、输入/输出流与 Java 文件操作的关系

1. 输入流和输出流与 File 类的关系

  • File 类是文件操作的入口: File 类本身不是用来直接读写文件内容的。它的主要作用是代表文件或目录的路径,并提供文件和目录的元数据操作,例如:

    • 判断文件是否存在 (exists())
    • 判断是文件还是目录 (isFile(), isDirectory())
    • 创建、删除文件或目录 (createNewFile(), delete(), mkdir(), mkdirs())
    • 获取文件路径、名称、大小、最后修改时间等信息 (getPath(), getName(), length(), lastModified())
  • 输入/输出流依赖 File 对象: Java 中的输入流 (InputStream, Reader)输出流 (OutputStream, Writer) 才是真正用于读写文件内容的。 但是,文件流的创建通常需要 File 对象作为参数,或者需要文件路径字符串,而文件路径字符串最终也会被用来创建 File 对象。

    • 例如:
      • FileInputStreamFileOutputStream 的构造方法可以接受 File 对象或文件路径字符串。
      • FileReaderFileWriter 的构造方法同样可以接受 File 对象或文件路径字符串。
      • RandomAccessFile 的构造方法也接受 File 对象或文件路径字符串。
  • 流操作作用于 File 代表的文件: 当您创建一个文件输入流或输出流,并将其与 File 对象关联起来后,后续的 read()write() 等流操作,实际上是在操作 File 对象所代表的那个文件 的内容。

总结: File 类是文件操作的基础入口,它提供了文件和目录的抽象路径表示以及元数据操作。而输入/输出流才是真正负责数据传输的组件,它们需要 File 对象来指定操作的目标文件。 File 类负责 “定位” 文件,而流负责 “操作” 文件内容。

2. File 类是 Java 底层的文件操作类吗?

  • File 类是 Java IO 的基石: 可以认为 File 类是 Java IO 文件操作体系的基石。 虽然 File 类本身不直接进行底层的文件 I/O 操作,但它为所有文件相关的操作提供了统一的抽象表示。

  • 底层依赖操作系统: Java 的文件 IO 操作最终都依赖于底层操作系统的文件系统 API。 无论是 File 类的方法,还是文件输入/输出流的操作,最终都会通过 JNI (Java Native Interface) 调用操作系统的本地方法,来完成实际的文件读写、创建、删除等操作。

  • File 类提供抽象层: File 类在 Java 层面提供了一个抽象层,使得 Java 程序可以以平台无关的方式操作文件系统。 Java 开发者无需直接关注底层操作系统文件系统 API 的细节,只需使用 File 类和各种流 API 即可完成文件操作。

3. 工具类 (FileUtils, FileUtil) 以 File 对象展开文件操作吗?

  • 工具类基于 File 类: 是的,像 Apache Commons FileUtils 和 Hutool FileUtil 这样的文件工具类,它们都是构建在 File 类和 Java IO 流 API 之上的。 它们的核心功能仍然是使用 File 对象来代表文件,并使用各种输入/输出流来读写文件内容。

  • 工具类封装和简化操作: 工具类的主要目的是封装和简化常见的、重复性的文件操作,并提供更便捷、更高级的功能。 它们通常会:

    • 封装流操作: 工具类内部会使用 FileInputStream, FileOutputStream, FileReader, FileWriter 等流来完成实际的文件读写操作,但会将流的创建、关闭、异常处理等细节隐藏起来,提供更简洁的方法给用户调用。
    • 提供组合操作: 工具类会提供一些组合操作,例如文件复制、目录复制、递归删除目录、读取整个文件内容到字符串等,这些操作通常需要手动组合 File 类和流 API 才能完成,工具类将其封装成一个方法调用,提高了开发效率。
    • 增强功能: 工具类可能会提供一些额外的功能,例如文件类型判断、MIME 类型获取、文件大小格式化等,这些功能在 File 类和标准流 API 中可能没有直接提供。
  • 工具类方法通常接受 File 或路径: 工具类的方法通常会接受 File 对象或文件路径字符串作为参数,这进一步印证了 File 类在文件操作中的核心地位。 即使传入的是路径字符串,工具类内部通常也会先创建 File 对象,再进行后续操作。

总结:

Java IO 文件操作组件详解 (底层到高层)

本表格总结了 Java IO 中用于文件操作的关键组件,从底层到高层进行了对比,包括 File 类、输入/输出流、RandomAccessFile 以及工具类 (FileUtils / FileUtil)。

特性File 类 (java.io.File)输入/输出流 (IO Streams)RandomAccessFile (java.io.RandomAccessFile)工具类 (FileUtils / FileUtil) (第三方库)
类型基础功能类 (文件/目录抽象表示)核心功能组件 (数据传输管道)核心功能类 (随机访问文件)工具类库 (静态方法集合)
目的文件和目录的路径表示和元数据操作实现程序与文件之间的数据输入和输出 (读写文件内容)提供对文件内容的随机读写能力简化和增强常见文件操作,提供便捷方法
操作层面文件系统抽象层面 (路径、属性)底层数据传输层面 (字节/字符序列)中层文件内容操作层面 (字节级别,随机访问)高层文件操作层面 (封装流操作,提供高级功能)
访问模式路径操作,不涉及内容访问顺序访问 (为主),也支持缓冲等优化随机访问顺序访问 (底层基于流)
数据单位路径名字符串,文件/目录对象字节 (字节流),字符 (字符流),对象 (对象流)字节 (字节流)字节,字符,字符串,行,字节数组等 (根据具体方法而定)
输入/输出流类型不适用 (非流)字节流 (InputStream, OutputStream),字符流 (Reader, Writer),缓冲流,对象流等字节流不适用 (工具类封装了流操作)
易用性基础,简单,用于元数据和结构操作相对底层,需要手动管理流的创建、关闭、异常处理等相对复杂,需要理解文件指针和字节操作高度易用,提供简洁的静态方法,封装了底层细节,易于上手和使用
适用场景文件和目录的创建、删除、重命名、属性获取所有需要读写文件内容的场景 (文本文件,二进制文件,各种数据格式)需要随机访问文件内容的特定场景 (数据库文件,断点续传等)通用文件操作场景,日常文件任务,提高开发效率,代码简洁性
File 对象的
关系
File 对象本身就是操作对象输入/输出流通常以 File 对象或路径作为操作目标,作用于 File 代表的文件内容RandomAccessFileFile 对象或路径作为操作目标,在其代表的文件上进行随机读写工具类方法通常接受 File 对象或路径作为输入参数,操作指定的文件或目录

表格总结:

  • File 类: 位于文件操作的最底层,是所有文件相关操作的基础。它主要关注文件和目录的抽象路径元数据,但不直接处理文件内容的数据流。
  • 输入/输出流: 构建在 File 类之上,是 Java IO 的核心,负责实际的数据传输。它们提供了多种类型的流 (字节流、字符流等) 来处理不同类型的数据,并支持顺序访问和缓冲等机制。
  • RandomAccessFile: 与输入/输出流同级,但专注于随机访问文件内容。它提供了一种更灵活的文件访问模式,适用于特定场景。
  • 工具类 (FileUtils / FileUtil): 位于文件操作的最高层,是基于 File 类和输入/输出流构建的工具库。它们封装了底层的复杂性,提供了更简洁、更易用的 API,用于处理各种常见的文件操作任务,提高开发效率。

层层递进,协同工作:

Java IO 的文件操作体系是分层设计的,从 File 类的抽象表示,到输入/输出流的数据传输,再到 RandomAccessFile 的随机访问,以及工具类的便捷封装,它们共同构建了一个完整、灵活的文件操作框架。 在实际开发中,您可以根据具体需求选择合适的组件:

  • 基础操作 (文件/目录管理): File
  • 顺序读写文件内容: 输入/输出流 (FileInputStream, FileOutputStream, FileReader, FileWriter 等)
  • 随机读写文件内容: RandomAccessFile
  • 便捷、高效的通用文件操作: 工具类 (FileUtils, FileUtil)

二、字节流

  • 一切文件(文本、视频、图片)的数据都是以二进制的形式存储的,传输时也是。所以,字节流可以传输任意类型的文件数据。

  • 字节流 (Byte Streams) 是 Java IO (Input/Output) 体系的最基础核心组成部分。它们是所有其他类型的 IO 流的基础,理解字节流对于深入掌握 Java IO 至关重要。

1. 什么是字节流? (What are Byte Streams?)

  • 定义: 字节流是处理以 字节 (byte) 为单位的数据的流。在 Java 中,一个字节是 8 位 (bits) 的数据。
  • 目的: 字节流的主要目的是读取和写入原始的二进制数据。 计算机中所有的数据最终都以二进制形式 (字节序列) 存储和传输,因此字节流是处理各种数据的基础。
  • 抽象类: Java 提供了两个抽象类作为字节流的基类:
    • InputStream (输入字节流): 用于从数据源 (例如文件、网络连接、内存) 读取字节数据 到程序中。
    • OutputStream (输出字节流): 用于将字节数据从程序 写入 到数据目的地 (例如文件、网络连接、内存)。
  • 常用实现类 (文件操作): 在文件 IO 中,最常用的字节流实现类是:
    • FileInputStream: 用于从文件读取字节数据。
    • FileOutputStream: 用于向文件写入字节数据。

2. 为何是 IO 的基石? (Why are they the Cornerstone?)

字节流之所以被称为 Java IO 的基石,是因为:

  • 通用性: 字节是计算机数据存储和传输的最基本单位。 任何类型的数据,包括文本、图像、音频、视频、程序代码等,在底层都以字节序列的形式存在。 因此,字节流可以处理所有类型的数据
  • 其他流的基础: Java IO 中其他类型的流 (例如字符流、缓冲流、对象流) 都是基于字节流构建的。 它们在字节流的基础上添加了额外的功能或抽象层次,例如:
    • 字符流 (Reader, Writer): 在字节流的基础上,提供了字符编码处理能力,更方便地处理文本数据。 但字符流的底层仍然是字节流。
    • 缓冲流 (BufferedInputStream, BufferedOutputStream): 在字节流的基础上,添加了缓冲区,提高了 IO 效率。 但缓冲流仍然是对字节数据的缓冲和操作。
    • 对象流 (ObjectInputStream, ObjectOutputStream): 在字节流的基础上,提供了对象序列化和反序列化能力,用于对象的持久化和网络传输。 但对象流最终也是将对象转换为字节序列进行传输。
  • 最底层的操作: 字节流提供了最直接、最底层的 IO 操作方式。 理解字节流的工作原理,有助于深入理解 Java IO 的本质。

3. 字节流的关键概念 (Key Concepts)

  • 字节 (Byte): 8 位二进制数据,是计算机存储和处理数据的基本单位。 字节流操作的是一个个独立的字节。
  • 数据方向 (Input/Output):
    • Input (输入): 数据从外部来源 (数据源) 流入程序。 InputStream 及其子类用于输入操作。
    • Output (输出): 数据从程序流出到外部目的地 (数据目的地)。 OutputStream 及其子类用于输出操作。
  • 抽象类与实现类 (Abstract Classes and Implementations):
    • InputStreamOutputStream抽象类,定义了字节输入流和输出流的通用接口和方法。
    • FileInputStream, FileOutputStream 等是实现类,提供了针对特定数据源/目的地的具体实现。 例如,FileInputStream 提供了从文件读取字节数据的具体实现。
  • 基本操作方法 (Basic Operations):
    • read() (InputStream): 从输入流中读取一个字节的数据。 返回值为 int 类型 (0-255 代表读取的字节值,-1 代表已到达流的末尾)。
    • write() (OutputStream): 向输出流中写入一个字节的数据。 参数为 int 类型 (实际写入的是参数的低 8 位,即一个字节)。
    • close() (InputStream & OutputStream): 关闭流,释放与流关联的系统资源 (例如文件句柄、网络连接等)。 务必在流使用完毕后关闭流,避免资源泄漏。
  • 二进制数据处理 (Binary Data Handling): 字节流非常适合处理二进制数据,例如:
    • 图像文件: JPEG, PNG, GIF 等图像文件都是二进制格式。
    • 音频/视频文件: MP3, MP4, AVI 等音频/视频文件也是二进制格式。
    • 程序代码 (class 文件, 可执行文件): 编译后的 Java class 文件、可执行文件等都是二进制格式。
    • 任何非文本数据: 例如,压缩文件 (ZIP, GZIP),加密数据等。

三、字符流

01、字符输入流(Reader)

  • java.io.Reader 是字符输入流的超类(父类),它定义了字符输入流的一些共性方法:

    • 1、close():关闭此流并释放与此流相关的系统资源。
    • 2、read():从输入流读取一个字符。(每次可以读取一个字符,返回读取的字符(转为 int 类型),当读取到文件末尾时,返回-1)
    • 3、read(char[] cbuf):从输入流中读取一些字符,并将它们存储到字符数组 cbuf 中;

02、字符输出流(Writer)

  • java.io.Writer 是字符输出流类的超类(父类),可以将指定的字符信息写入到目的地,来看它定义的一些共性方法:

    • 1、write(int c) 写入单个字符。
    • 2、write(char[] cbuf) 写入字符数组。
    • 3、write(char[] cbuf, int off, int len) 写入字符数组的一部分,off 为开始索引,len 为字符个数。
    • 4、write(String str) 写入字符串。
    • 5、write(String str, int off, int len) 写入字符串的某一部分,off 指定要写入的子串在 str 中的起始位置,len 指定要写入的子串的长度。
    • 6、flush() 刷新该流的缓冲。
    • 7、close() 关闭此流,但要先刷新它。
  • java.io.FileWriter 类是 Writer 的子类,用来将字符写入到文件

03、流的注意事项

  • flush 和 close 的用法:

    • flush :刷新缓冲区,流对象可以继续使用。(进行流的操作时,数据先被读到内存中,然后再把数据写到文件中)
    • close :先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了
  • IO 异常处理规范:

    • try…catch…finally
    • try-with-resources (会在 try 块执行完毕后自动关闭流的对象,不需要手动关闭流)

四、缓冲流

  • 传统的 Java IO 是阻塞模式的,它的工作状态就是“读/写,等待,读/写,等待。。。。。。”

  • 字节缓冲流解决的就是这个问题:一次多读点多写点,减少读写的频率,用空间换时间。

    • 减少系统调用次数:在使用字节缓冲流时,数据不是立即写入磁盘或输出流,而是先写入缓冲区,当缓冲区满时再一次性写入磁盘或输出流。这样可以减少系统调用的次数,从而提高 I/O 操作的效率。
    • 减少磁盘读写次数:在使用字节缓冲流时,当需要读取数据时,缓冲流会先从缓冲区中读取数据,如果缓冲区中没有足够的数据,则会一次性从磁盘或输入流中读取一定量的数据。同样地,当需要写入数据时,缓冲流会先将数据写入缓冲区,如果缓冲区满了,则会一次性将缓冲区中的数据写入磁盘或输出流。这样可以减少磁盘读写的次数,从而提高 I/O 操作的效率。
    • 提高数据传输效率:在使用字节缓冲流时,由于数据是以块的形式进行传输,因此可以减少数据传输的次数,从而提高数据传输的效率。

五、转换流

InputStreamReader 和 OutputStreamWriter 是将字节流转换为字符流或者将字符流转换为字节流。通常用于解决字节流和字符流之间的转换问题,可以将字节流以指定的字符集编码方式转换为字符流,或者将字符流以指定的字符集编码方式转换为字节流。

InputStreamReader 类的常用方法包括:

  • read():从输入流中读取一个字符的数据。
  • read(char[] cbuf, int off, int len):从输入流中读取 len 个字符的数据到指定的字符数组 cbuf 中,从 off 位置开始存放。
  • ready():返回此流是否已准备好读取。
  • close():关闭输入流。

OutputStreamWriter 类的常用方法包括:

  • write(int c):向输出流中写入一个字符的数据。
  • write(char[] cbuf, int off, int len):向输出流中写入指定字符数组 cbuf 中的 len 个字符,从 off 位置开始。
  • flush():将缓冲区的数据写入输出流中。
  • close():关闭输出流。
    在使用转换流时,需要指定正确的字符集编码方式,否则可能会导致数据读取或写入出现乱码。

六、序列流

  • 序列化的思想是“冻结”对象状态,然后写到磁盘或者在网络中传输;反序列化的思想是“解冻”对象状态,重新获得可用的 Java 对象。

  • 一个对象要想序列化,必须满足两个条件:

    • 该类必须实现 java.io.Serializable 接口,否则会抛出 NotSerializableException 。
    • 该类的所有字段都必须是可序列化的。如果一个字段不需要序列化,则需要使用 transient 或者 static 关键字进行修饰。
    • static 关键字表示该成员变量是属于类的,不属于对象的,因此不需要序列化和反序列化。

Serializable 接口

  • 定义:Serializable 是一个标记接口(marker interface),意味着它没有任何方法。实现这个接口的类可以被序列化。
  • 内部含义:
    • JVM 在序列化对象时,会自动处理对象的所有字段,包括私有字段。
    • 如果对象的某个字段不希望被序列化,可以使用 transient 关键字修饰该字段。
    • 在序列化过程中,JVM 会生成一个版本号(serialVersionUID),用于确保反序列化时版本兼容。如果类的结构发生变化,可能会导致 InvalidClassException。

Externalizable 接口

  • 定义:Externalizable 接口是 Serializable 的一个扩展,允许开发者控制序列化的过程。
  • 内部含义:
    • 需要实现 writeExternal(ObjectOutput out) 和 readExternal(ObjectInput in) 方法,以自定义序列化和反序列化逻辑。
    • 由于需要手动处理字段的读写,Externalizable 提供了更高的灵活性和控制力。
    • 不需要 serialVersionUID,但开发者需要确保序列化和反序列化时的字段一致性

transient

transient 关键字在 Java 中用于指示某个字段不应被序列化。具体来说,transient 修饰的字段在对象序列化时将被忽略,也就是说,当对象被转换为字节流时,这些字段的值不会被写入字节流;在反序列化时,这些字段会被赋予默认值(如 null、0、false 等)。

  • 使用的场景:
    • 敏感数据:
      • 当对象包含敏感信息(如密码、信用卡信息等)时,使用 transient 可以防止这些信息被序列化,从而提高安全性。
    • 临时字段:
      • 如果某个字段是临时计算的结果,不需要在对象的持久化状态中保存(比如缓存值),可以使用 transient。
    • 不必要的字段:
      • 对于一些在反序列化时不需要恢复的字段,比如某些统计信息或状态标志,也可以使用 transient。
    • 避免序列化开销:
      • 某些字段可能会占用大量内存或序列化开销,如果这些字段在反序列化后不需要使用,可以将其标记为 transient 来减少序列化的负担。

Kryo 序列化的优势

在实际开发中,开发者通常更倾向于使用 Kryo 序列化而非 JDK 自带的序列化,原因如下:

问题描述
可移植性差JDK 的序列化机制是 Java 特有的,无法跨语言进行序列化和反序列化。这限制了与其他语言或平台的交互。
性能差JDK 的序列化生成的字节流体积较大,增加了数据传输和存储的成本。Kryo 通过更高效的算法和更小的字节序列来提高性能。
安全问题JDK 序列化存在安全风险,攻击者可以构造恶意数据来实现远程代码执行,从而对系统造成严重的安全威胁。这种安全漏洞在 Java 反序列化中尤为突出。

七、打印流

  • 类别:

    • PrintStream 是 OutputStream 的子类(System.out 返回的正是打印流 PrintStream);
    • PrintWriter 是 Writer 的子类;
  • PrintStream 类的常用方法包括:

    • print():输出一个对象的字符串表示形式。
    • println():输出一个对象的字符串表示形式,并在末尾添加一个换行符。
    • printf():使用指定的格式字符串和参数输出格式化的字符串。
      • 内部结构: public PrintStream printf(String format, Object... args);
      • %s:输出一个字符串。
      • %d 或 %i:输出一个十进制整数。
      • %x 或 %X:输出一个十六进制整数,%x 输出小写字母,%X 输出大写字母。
      • %f 或 %F:输出一个浮点数。
      • %e 或 %E:输出一个科学计数法表示的浮点数,%e 输出小写字母 e,%E 输出大写字母 E。
      • %g 或 %G:输出一个浮点数,自动选择 %f 或 %e/%E 格式输出。
      • %c:输出一个字符。
      • %b:输出一个布尔值。
      • %h:输出一个哈希码(16 进制)。
      • %n:换行符。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值