JAVA: explicación detallada del flujo IO

1. Descripción general del flujo

1.1 ¿Qué es la OI?

IO:Entrada/Salida significa entrada y salida, también llamado flujo (río, flujo de agua), que se refiere al proceso de movimiento de datos de un lugar a otro; para las computadoras, el proceso de copia de archivos, escritura y guardado de archivos y funciones de visualización son Se utilizan todas las IO y el proceso de transmisión de datos se considera como entrada y salida. Desde una perspectiva práctica, puedes utilizar los oídos como flujo de entrada y la boca como flujo de salida.

  • Medios de entrada y salida:
    • documento
    • red
    • entrada de teclado)
    • Pantalla (salida)

1.2 Clasificación IO

Java proporciona las API correspondientes para operaciones IO. Casi todas las operaciones IO en Java requieren el uso de java.iopaquetes; la clasificación de flujos en Java incluye varios métodos:

  • Dividido por la dirección del flujo (los procesos de entrada y salida generalmente se consideran desde la perspectiva del programa )
    • Flujo de entrada (entrada)
    • Producción
  • Según el tipo de procesamiento de flujo.
    • Flujo de bytes (byte)
    • Flujo de caracteres (char)
  • Según la función del flujo.
    • Flujo de nodos (interactuar directamente con fuentes de entrada y salida)
    • Procesamiento de flujos (flujos que envuelven otros flujos: flujos envueltos)

Insertar descripción de la imagen aquí

1.3 Clase para padres de nivel superior

Aunque los streams tienen una gran variedad de contenidos, son muy regulares, casi todos los streams heredan de cuatro streams básicos:

flujo de entrada flujo de salida
flujo de bytes java.io.InputStream java.io.OutputStream
flujo de personajes java.io.Reader java.io.Escritor

Las cuatro secuencias anteriores son las clases principales de nivel superior de todas las secuencias de Java y todas son clases abstractas ;

La identificación del tipo de flujo es muy regular: generalmente, el Streamflujo que termina en es un flujo de bytes; generalmente Reader/Writerel flujo que termina en es un flujo de caracteres.

1.4 Escenarios de uso

  • Copia de archivo (Archivo)
  • Carga y descarga de archivos
  • Importación y exportación de Excel.
  • Transmisión de datos en programas de red (herramientas de chat)

2. Flujo de bytes

2.1 Descripción general de bytes

​ En un sistema informático, todo son bytes: varios archivos (documentos de texto, imágenes, videos, audios) almacenados en el sistema se almacenan en forma de bytes en el nivel inferior de la computadora, por lo que para cualquier operación con archivos, todos pueden ser operado byte a byte (lectura, escritura); la clase principal de nivel superior del flujo de bytes en java.io es: InputStream/OutputStream.

2.2 Flujo de entrada de bytes

Todos los flujos de entrada de bytes en Java se java.io.InputStreamheredan. Dado que esta clase es una clase abstracta, no se pueden crear instancias de ella, por lo que jdk proporciona algunas subclases para la entrada de bytes que se pueden usar directamente:

  • FileInputStream
  • ByteArrayInputStream
  • BufferedInputStream
  • ObjectInputStream

Métodos comunes de InputStream

  • int available(): obtiene el número de bytes legibles en la secuencia
  • int read():Lee un byte de la secuencia y devuelve los datos del byte leído actualmente.
  • int read(byte[] b): almacena los datos del byte leído en el búfer de bytes y devuelve el número total real de bytes leídos.
  • skip(int b):Omitir los bytes especificados para la siguiente lectura

2.2.1. flujo de entrada de archivos

FileInputStreamEs un flujo de implementación para el flujo de entrada de bytes, utilizado principalmente para leer archivos, y los métodos internos son principalmente para la implementación de la clase principal.

  • Constructores comunes

    • FileInputStream(File file):Un objeto creado a partir del archivo proporcionado
    • FileInputStream(String filePath): construye un objeto basado en la ruta del archivo proporcionada
  • Usar FileInputStreampara leer archivos

    1. Lectura básica (leer un byte a la vez)

      //创建File对象
      File file = new File("C:\\Users\\Administrator\\Desktop\\新建文本文档.txt");
      //基于File创建字节输入流
      InputStream in = new FileInputStream(file);
      
      //读取一个字节
      int i = in.read();
      System.out.println((char)i);
      

      IO de Java solo puede leer y escribir archivos estándar y no le permite crear directamente flujos de entrada o salida para un directorio (causará java.io.IOException(拒绝访问))

    2. Leer usando el búfer de bytes (el tamaño del búfer es el total de bytes legibles)

      Dado que el método de lectura anterior lee un byte a la vez, la eficiencia de lectura es muy baja, por lo que en el desarrollo real, generalmente se usa un búfer de bytes para mejorar la eficiencia de lectura:

      //创建File对象
      File file = new File("C:\\Users\\Administrator\\Desktop\\新建文本文档.txt");
      //基于File创建字节输入流
      InputStream in = new FileInputStream(file);
      
      //创建字节缓冲区(大小为总可读字节数)
      byte[] arr = new byte[i];
      //将流中读取的字节内容存储到数组中
      int total = in.read(arr);
      //将字节数组转换为String字符串
      System.out.println(new String(arr));
      
    3. Leer utilizando un búfer de bytes del tamaño adecuado

      La lectura anterior es leer los datos del archivo en el búfer a la vez. Por lo tanto, la capacidad del búfer puede ser muy grande. Si se utiliza un búfer demasiado grande para leer un archivo grande, puede causar problemas. Esto causa Consume espacio y afecta la ejecución de otros programas, por lo que se requiere un búfer del tamaño adecuado para la lectura repetida.

      //创建文件输入流对象(文件:一缸水)
      InputStream in = new FileInputStream("C:\\Users\\Administrator\\Desktop\\Hero.java");
      
      //创建缓冲区(购买一个大小适中的水桶)
      byte[] b = new byte[1024];
      //声明临时变量表示每次读取的实际字节数
      int len = 0;
      while((len = in.read(b)) != -1){
              
              
          String s = new String(b,0,len);
          System.out.println(s);
      }
      

      El método de lectura anterior es una solución general para leer flujos de bytes.

2.3 Flujo de salida de bytes

Además de las operaciones de lectura (entrada) de acuerdo con la dirección del flujo de datos, las operaciones de escritura (salida) de datos también son muy comunes. Los flujos de salida de bytes en Java se heredan java.io.OutputStreamde

Dado que OutputStreames una clase abstracta y no se pueden crear instancias, jdk también proporciona subclases para esta secuencia:

  • FileOutputStream
  • ByteArrayOutputStream
  • BufferedOutputStream
  • ObjectOutputStream
  • PrintStream

Métodos comunes de la clase OutputStream:

  • write(int b):Escribe un byte a través del flujo de salida en la fuente de salida de destino.
  • write(byte[] b): escriba una matriz de bytes en la fuente de salida de destino a través del flujo de salida
  • write(byte[] b,int offset,int len)Escriba len bytes comenzando desde el desplazamiento de una matriz hasta la fuente de salida de destino

2.3.1. Flujo de salida de archivos

FileOutputStreamEs un flujo de implementación para un flujo de salida de bytes, que se utiliza principalmente para escribir archivos. Los métodos internos se utilizan principalmente para la implementación de la clase principal.

  • Constructores comunes

    • FileOutputStream(String filePath):Crea un flujo de salida para operaciones en un archivo estándar según su ruta
    • FileOutputStream(String filePath,boolean append): cree un flujo de salida para operaciones en un archivo estándar según su ruta, usando el modo de agregar.
    • FileOutputStream(File file):Crea un flujo de salida para operaciones en un archivo estándar según su ruta
    • FileOutputStream(File file,boolean append)Crea un flujo de salida para operaciones en un objeto de archivo estándar, usando el modo agregar
  • Usar FileOutputStreampara escribir archivos

     //try...with语句:JDK1.7
            //针对所有的第三方资源不再需要手动回收(关闭)
            //因为从jdk1.7开始,很多资源类都实现过Closeable接口,但凡实现过该接口类
            //只要将其在try()中创建,不再手动关闭资源,会由JVM自动回收
            try(OutputStream os = new FileOutputStream("readme.txt",true);){
          
          
                //写出一个字节到文件中
    //            os.write(101);
    
                //荀子·劝学篇
                String msg = "不积小流无以成江海,不积跬步无以至千里";
                os.write(msg.getBytes());
    
            }catch (IOException e){
          
          
                e.printStackTrace();
            }
    

2.4 Copia completa del expediente

/**
     * 将一个源文件拷贝到一个目标目录中
     * @param src 源文件
     * @param targetDir 目标目录
     */
public static void copyFile(File src, File targetDir) throws IOException {
    
    
    InputStream is = null;
    OutputStream os = null;
    try {
    
    
        //获取源文件的输入流
        is = new FileInputStream(src);
        //获取目标文件的输出流(目标文件是由:目录+源文件名称构成):文件输出流可以创建文件(前提:父目录必须存在)
        os = new FileOutputStream(new File(targetDir,src.getName()));
        //声明字节缓冲区(缓冲区越大,拷贝效率越高,但是带来空间损耗也越大)
        byte[] b = new byte[1024*1024];
        //临时变量表示实际读取的字节数
        int len = 0;
        System.out.println("开始拷贝...");
        while((len = is.read(b)) != -1){
    
    
            //写出读取的内容到输出流
            os.write(b,0,len);
        }
        System.out.println("拷贝完成!");
    }  finally{
    
    
        if(os != null){
    
    
            os.close();
        }
        if(is != null){
    
    
            is.close();
        }
    }

}

//测试
public static void main(String[] args) throws IOException {
    
    
    //源文件
    File src = new File("D:\\素材\\视频\\短视频\\test.mp4");
    //目标目录
    File targetDir = new File("C:\\Users\\Administrator\\Desktop");
    //文件拷贝
    copyFile(src,targetDir);
}

Implementar copia de directorio basada en copia de archivo:

public class FileCopy {
     
     

/**
   * 将一个源目录拷贝到另一个目标目录中
   * @param srcDir
   * @param targetDir
   */
  public static void copyDir(File srcDir,File targetDir) throws IOException {
     
     
      //获取新目录对象
      targetDir = new File(targetDir,srcDir.getName());
      //如果新目录不存在,则创建
      if(!targetDir.exists()){
     
     
          targetDir.mkdirs();
      }
      File[] files = srcDir.listFiles();
      if(Objects.nonNull(files)){
     
     
          for (File file : files) {
     
     
              if(file.isDirectory()){
     
     
                  //目录递归拷贝
                  copyDir(file,targetDir);
              }else{
     
     
                  //执行文件拷贝
                  copyFile(file,targetDir);
              }
          }
      }
  }

  /**
   * 将一个源文件拷贝到一个目标目录中
   * @param src 源文件
   * @param targetDir 目标目录
   */
  public static void copyFile(File src, File targetDir) throws IOException {
     
     
      InputStream is = null;
      OutputStream os = null;
      try {
     
     
          //获取源文件的输入流
          is = new FileInputStream(src);
          //获取目标文件的输出流(目标文件是由:目录+源文件名称构成)
          os = new FileOutputStream(new File(targetDir,src.getName()));
          //声明字节缓冲区(缓冲区越大,拷贝效率越高,但是带来空间损耗也越大)
          byte[] b = new byte[1024*1024];
          //临时变量表示实际读取的字节数
          int len = 0;
          System.out.println("开始拷贝...");
          while((len = is.read(b)) != -1){
     
     
              //写出读取的内容到输出流
             os.write(b,0,len);
          }
          System.out.println("拷贝完成!");
      }  finally{
     
     
          if(os != null){
     
     
              os.close();
          }
          if(is != null){
     
     
              is.close();
          }
      }

  }

  public static void main(String[] args) throws IOException {
     
     
      //源目录
      File srcDir = new File("D:\\素材\\视频");
      //目标目录
      File targetDir = new File("C:\\Users\\Administrator\\Desktop\\video");
      //目录拷贝
      copyDir(srcDir,targetDir);
  }
}

3. Flujo de personajes

3.1 Descripción general de los personajes

Por lo general 文本文件, el contenido del archivo generalmente existe en forma de caracteres (un carácter chino, una letra en inglés, un símbolo), generalmente en el modo de codificación GBK; los 1个字符=2个字节flujos de caracteres generalmente son adecuados para procesar datos de texto, java Casi todos los flujos de caracteres en Reader/Writer, y todos heredan de dos clases abstractas básicas:

  • java.io.Reader: Flujo de entrada de caracteres
  • java.io.Writer: Flujo de salida de caracteres

3.2 Flujo de entrada de caracteres

Los flujos de entrada de caracteres se utilizan generalmente para leer datos de texto.Subcategorías comunes:

  • InputStreamReader
  • FileReader
  • BufferedReader
  • CharArrayReader

3.2.1. Lector de archivos

FileReader es una secuencia que lee el contenido del archivo en forma de secuencia de caracteres. Se InputStreamReaderhereda de FileReader y no tiene métodos internos redundantes (consistentes con la API de la clase principal). Proporciona los siguientes métodos de construcción comunes:

  • Métodos de construcción comúnmente utilizados.

    • FileReader(File file): Obtiene el flujo de entrada de caracteres del archivo según el objeto de archivo proporcionado
    • FileReader(String path):Obtiene el flujo de entrada de caracteres del archivo según la ruta del archivo proporcionada
  • Uso específico

    //创建文件字符输入流
    FileReader fr = new FileReader("D:\\文档资料\\电子书\\书籍推荐.txt");
    
    //获取当前流使用的默认编码模式(并非目标文件的编码)
    //        String encoding = fr.getEncoding();
    //        System.out.println(encoding);
    
    //读取一个字符(以int类型存在)
    //        int read = fr.read();
    //        System.out.println((char)read);
    
    //        int c = 0;
    //        while((c = fr.read()) != -1)
    //        {
          
          
    //            System.out.print((char)c);
    //        }
    
    //使用字符缓冲区
    char[] ch = new char[100];
    int len = 0;
    while((len = fr.read(ch)) != -1){
          
          
        String s = new String(ch,0,len);
        System.out.print(s);
    }
    

3.3 Flujo de salida de caracteres

Los flujos de salida de caracteres se utilizan generalmente para escribir datos de texto.Subcategorías comunes:

  • Lector`
  • OutputStreamWriter
  • BufferedWriter
  • CharArrayWriter
  • FileWriter
  • PrintWriter

3.3.1 Escritor de archivos

FileWriter es una secuencia que escribe el contenido del archivo en forma de secuencia de caracteres. Se hereda de OutputStreamWriterFileWriter y no tiene métodos internos redundantes (consistentes con la API de la clase principal). Proporciona los siguientes métodos de construcción comunes:

  • Métodos de construcción comúnmente utilizados.

    • FileWriter(File file): Obtiene el flujo de entrada de caracteres del archivo según el objeto de archivo proporcionado
    • FileWriter(String path):Obtiene el flujo de entrada de caracteres del archivo según la ruta del archivo proporcionada
    • FileWriter(File file,boolean append): obtiene el flujo de entrada de caracteres del archivo según el objeto de archivo proporcionado (usando el modo agregar)
    • FileWriter(String path,boolean append): Obtenga el flujo de entrada de caracteres del archivo según la ruta del archivo proporcionada (usando el modo agregar)
  • Uso específico

    //基于指定的文件创建字符输出流
    FileWriter fw = new FileWriter("readme.txt");
    fw.write("路漫漫其修远兮,吾将上下而求索");
    
    //允许在流未关闭之前,强制将字符缓冲区中的数据写出到目标输出源
    fw.flush();
    
    Thread.sleep(10000);
    fw.write("好好xio习,天天up!!!");
    fw.close();
    

    Para el flujo de salida de caracteres, se usa uno internamente.Al 字符缓冲区(字符数组)escribir datos, los datos que se escribirán generalmente se almacenan en caché en la matriz de caracteres, y luego, cuando el flujo se cierra (o cuando el búfer está lleno), el búfer se almacena en caché en uno. ir. Los datos en el búfer de caracteres se escriben en la fuente de salida de destino. Si necesita forzar que los datos en el búfer de caracteres se escriban antes de que se cierre la secuencia (o cuando el búfer no esté lleno), puede hacerlo manualmente. llamar flush()a salida forzada.

4. Flujo de procesamiento

El tipo de procesamiento (función) de la secuencia se divide en secuencia de nodo y secuencia de procesamiento:

  • flujo de nodo

    También llamado flujo de bajo nivel, un flujo que se comunica directamente con la fuente de entrada y salida (por ejemplo: FileReader, FileWriter, FileInputStream, FileOutputStream)

  • flujo de procesamiento

    El flujo de procesamiento también se denomina flujo avanzado o flujo de empaquetado , que se puede utilizar para envolver otros flujos de nodos para lograr 类型转换una mejora del flujo o la eficiencia. El flujo de procesamiento consiste principalmente en 缓冲流y转换流

El embalaje suele utilizar un patrón de diseño (patrón decorador)

4.1. Flujos almacenados en buffer

La secuencia almacenada en búfer es una secuencia con su propio búfer, que consta principalmente de los cuatro tipos siguientes:

  • BufferedInputStream: flujo de entrada almacenado en búfer de bytes
  • BufferedOutputStream: flujo de salida almacenado en búfer de bytes
  • BufferedReader: Flujo de entrada del búfer de caracteres
  • BufferedWriter: Flujo de salida almacenado en búfer de caracteres
long start = System.currentTimeMillis();
try(
    //获取输入流
    InputStream in = new FileInputStream("D:\\集团资料\\宣讲\\video\\云计算&大数据\\阿里云.mp4");
    //包装节点流
    BufferedInputStream bis = new BufferedInputStream(in);
    //获取输出流
    OutputStream os = new FileOutputStream("C:\\Users\\Administrator\\Desktop\\阿里云.mp4");
    //包装节点流
    BufferedOutputStream bos = new BufferedOutputStream(os);
) {
    
    
    System.out.println("开始拷贝");
    byte[] b = new byte[1024*1024*8];
    int len = 0;
    while((len = bis.read(b)) != -1){
    
    
        bos.write(b,0,len);
    }
    System.out.println("拷贝结束");
}catch (IOException e){
    
    
    e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("耗时:"+(end-start));

El principio de implementación interna de la secuencia almacenada en búfer utiliza un 8kbbúfer de bytes con un tamaño predeterminado de .Cada vez que se llena el espacio del búfer, los datos se leen y escriben a través del objeto de secuencia.

4.2 Flujo de conversión

En algunos requisitos, a menudo es necesario convertir los datos de un flujo de bytes en un flujo de caracteres, o es necesario convertir un flujo de caracteres en un flujo de bytes. En este caso, es necesario utilizar un flujo de conversión para completar la función. El flujo de conversión se usa generalmente en: transcodificación de archivos, cuando los datos leídos de la red son un flujo de bytes, pero el contenido del flujo son caracteres, puede usar el flujo de conversión para lograr la conversión; el flujo de conversión en Java. io consta principalmente de las dos clases siguientes:

  • InputStreamReader: Se utiliza para convertir un flujo de entrada de bytes en un flujo de entrada de caracteres (bytes -> caracteres)
  • OutputStreamWriter: Se utiliza para convertir un flujo de salida de caracteres en un flujo de salida de bytes (Carácter -> Byte)

Ejemplos de uso:

//获取标准输入流
InputStream is = System.in;

//将字节流转换为字符流
Reader isr = new InputStreamReader(is);
//将字符节点流包装为缓冲流
BufferedReader br = new BufferedReader(isr);
//读取一行
String line = br.readLine();
System.out.println("输入的内容--->"+line);

Insertar descripción de la imagen aquí

La conversión de una transmisión solo puede convertir el tipo de transmisión, no la dirección de la transmisión.

4.3 Caso integral: herramienta de transcodificación de archivos

En la copia diaria de archivos, debido a las diferencias en los modos de codificación de múltiples editores (sistemas), es muy probable que ocurra el problema de archivos confusos (más común para archivos de texto). La función de transcodificación de archivos se puede lograr mediante la conversión de secuencias:

public class FileCharacterConvert {
    
    

    /**
     * 将一个目标文件的编码转换为新的编码
     * @param file  原始文件
     * @param targetDir 目标目录
     * @param oldEncoding  原始编码
     * @param newEncoding   新编码
     */
    public static void convert(File file,File targetDir, String oldEncoding, String newEncoding) throws IOException {
    
    

        //使用特定的编码获取文件的输入流
        FileInputStream fis = new FileInputStream(file);
        Reader reader = new InputStreamReader(fis,oldEncoding);
        BufferedReader br = new BufferedReader(reader);

        //使用特定的编码获取文件的输出流
        FileOutputStream fow = new FileOutputStream(new File(targetDir,file.getName()));
        Writer writer = new OutputStreamWriter(fow,newEncoding);
        BufferedWriter bw = new BufferedWriter(writer);

        //开始读写
        String line = "";
        //循环读取每一行文本以换行符作为一行的结束标记(但是换行符不会被作为内容读取)
        while((line = br.readLine()) != null){
    
    
            //写入读取的一行文本()
            bw.write(line);
            //手动加入一个换行标记到文件,否则所有内容会在同一行显示
            bw.newLine();
            //将缓冲区的数据强制输出到目标输出源
            bw.flush();
        }
        bw.close();
        br.close();
    }

    public static void main(String[] args) throws IOException {
    
    
        //准备需要转换的文件
        File f = new File("C:\\Users\\Administrator\\Desktop\\GamePanel.java");
        convert(f,new File("C:\\Users\\Administrator\\Desktop\\temp"),"gbk","utf-8");
    }
}

5. Imprimir secuencia

java.io proporciona dos flujos especiales para la salida de datos: flujo de impresión. El flujo de impresión solo tiene salida pero no entrada. Se puede crear directamente para la fuente de salida o se pueden empaquetar otros flujos de salida. El flujo de impresión incluye principalmente los dos flujos siguientes .:

  • PrintStream: flujo de impresión de bytes
  • PrintWriter: Flujo de impresión de caracteres

5.1. Flujo de impresión

PrintStreamSe hereda de java.io.OutputStream, es una secuencia utilizada para la salida de datos de bytes, contiene una gran cantidad de print/prinlnmétodos sobrecargados y, System.outen realidad, es unPrintStream

  • Constructores comunes

    • PrintStream(File file): crea una secuencia de impresión basada en el archivo especificado
    • PrintStream(OutputStream os): Envuelve otros flujos de salida de bytes
    • PrintStream(OutputStream os,boolean autoFlush): Envuelve otros flujos de salida de bytes para actualizar automáticamente los datos en el flujo
  • Métodos comunes

    • print(...)
    • println(...)
  • Uso básico

    //        PrintStream ps = new PrintStream(new File("readme.txt"),true);
    
    OutputStream os = new FileOutputStream("readme.txt", true);
    PrintStream ps = new PrintStream(os,true);
    ps.println("你好中国");
    

5.2. Imprenta

PrintWriterLa diferencia PrintStreames que PrintWriteres un flujo de impresión basado en caracteres (incluido el búfer de caracteres) y su API es PrintStreammuy similar; recuerde ejecutar el método cuando utilice PrintWriterdatos de impresión .flush()

FileWriter fw = new FileWriter("readme.txt",true);

PrintWriter pw = new PrintWriter(fw);

pw.println("哪里有彩虹告诉过我!!!");

pw.flush();

pw.close();

6. Lectura de archivos de recursos (clase de propiedades)

En el proceso de aprendizaje posterior del marco, a menudo está involucrada la escritura de algunos archivos de configuración. Los archivos de propiedades son un tipo de archivo muy común. Java proporciona una clase para leer y escribir archivos de propiedades java.util.Properties:

InputStream in = PropertiesDemo.class.getResourceAsStream("/jdbc.properties");
//创建属性对象
Properties prop = new Properties();
//        Properties prop = System.getProperties();
//加载流到属性对象中
prop.load(in);

String user = prop.getProperty("user");
String url = prop.getProperty("url");
System.out.println(user);
System.out.println(url);

prop.setProperty("maxActive","10");

File f = new File("D:\\带班资料\\2021\\J2106\\课程资料\\code\\part1-javase\\java高级\\lesson_01_IO\\resources\\jdbc.properties");
FileWriter fw = new FileWriter(f);
prop.store(fw,"this is jdbc config file");
fw.close();

7. Resumen del flujo

  • Todos los nombres de flujo de entrada contienen input,read
  • Todos los nombres de flujo de salida contienen output,write
  • Todos los nombres de flujos de bytes streamterminan con
  • Todos los nombres de secuencias de caracteres terminan con readerowriter
  • Generalmente, los archivos de tipo binario (imágenes, videos, audios, archivos comprimidos, etc. que no se pueden abrir directamente usando documentos de texto) utilizan principalmente operaciones de flujo de bytes.
  • Los archivos de tipo de texto generales (txt, md, java y otros archivos que se pueden abrir directamente usando documentos de texto) utilizan principalmente operaciones de flujo de caracteres.

Supongo que te gusta

Origin blog.csdn.net/weixin_48006490/article/details/126649763
Recomendado
Clasificación