Java avanzado: la segunda parte de IO Stream "Diverse Streams"

Tabla de contenido

Secuencia almacenada en Java

Clases BufferedReader y BufferedWriter

flujo aleatorio java

flujo de matriz de Java

flujo de matriz de bytes

El método de construcción de la secuencia ByteArrayInputStream:

El método de construcción de la secuencia ByteArrayOutputStream:

flujo de matriz de caracteres

flujo de datos java

flujo de objetos Java

Serialización de Java y clonación de objetos.

Pocos conocimientos extendidos:

Java usa Scanner para analizar archivos

Analizar archivos usando tokens delimitados predeterminados

Analizar un archivo usando expresiones regulares como tokens delimitados

Diálogo de archivo Java

Flujo de entrada de Java con barra de progreso

Bloqueo de archivos Java


Secuencia almacenada en Java

Una secuencia almacenada en búfer es una secuencia en Java que proporciona un búfer para mejorar la eficiencia de la lectura y escritura de datos. Los flujos de búfer pueden almacenar datos temporalmente en un búfer en la memoria y luego escribir o leer datos al mismo tiempo, lo que reduce las operaciones frecuentes de disco o de red y aumenta así la velocidad de lectura y escritura de datos.

Las transmisiones almacenadas en búfer pueden hacer lo siguiente:

  1. Mejore la eficiencia de lectura y escritura: los flujos de búfer pueden reducir la cantidad de operaciones de disco o de red, almacenar datos en el búfer en la memoria primero y luego escribir o leer al mismo tiempo, mejorando así la eficiencia de lectura y escritura de datos.
  2. Proporciona funciones adicionales: las transmisiones almacenadas en búfer también proporcionan algunas funciones adicionales, como compatibilidad con la conversión de codificación de caracteres, compatibilidad con operaciones de línea y más.

En Java, las secuencias almacenadas en búfer comunes son

  1. BufferedInputStream: es un contenedor para InputStream, que proporciona un búfer para mejorar la eficiencia de la lectura de datos. Puede leer varios bytes en el búfer a la vez y luego leer del búfer byte a byte, lo que reduce las operaciones frecuentes de disco o de red. El uso de BufferedInputStream puede mejorar la eficiencia de la lectura de grandes cantidades de datos.
  2. BufferedOutputStream: es un contenedor para OutputStream, que proporciona un búfer para mejorar la eficiencia de la escritura de datos. Puede escribir datos en el búfer primero y luego escribirlos en el flujo de salida de destino a la vez, lo que reduce las operaciones frecuentes de disco o de red. El uso de BufferedOutputStream puede mejorar la eficiencia al escribir grandes cantidades de datos.
  3. BufferedReader: es un contenedor para Reader que proporciona un búfer para mejorar la eficiencia de la lectura de datos de caracteres. Puede leer varios caracteres en el búfer a la vez y luego leer del búfer carácter por carácter, lo que reduce las operaciones frecuentes de disco o de red. El uso de BufferedReader puede mejorar la eficiencia de la lectura de grandes cantidades de datos de caracteres.
  4. BufferedWriter: es un contenedor para Writer, que proporciona un búfer para mejorar la eficiencia de la escritura de datos de caracteres. Puede escribir datos de caracteres en el búfer primero y luego escribirlos en el flujo de salida de destino a la vez, lo que reduce las operaciones frecuentes de disco o de red. El uso de BufferedWriter puede mejorar la eficiencia al escribir grandes cantidades de datos de caracteres.

Estos flujos almacenados en búfer son envoltorios para flujos de bytes o flujos de caracteres. Puede pasar flujos de bytes o flujos de caracteres a los flujos almacenados en búfer a través de métodos de construcción y luego realizar operaciones de lectura y escritura a través de los flujos almacenados en búfer.

Clases BufferedReader y BufferedWriter

En Java, llamamos a los objetos creados por las clases BufferedReader y BufferedWriter flujos de entrada y salida almacenados en búfer, lo que mejora la capacidad de leer y escribir archivos. Por ejemplo, Sudent.txt es una lista de estudiantes, cada nombre ocupa una línea. Si queremos leer el nombre, debemos leer una línea a la vez. Es difícil usar la secuencia FileReader para completar dicha tarea porque no sabemos cuántos caracteres hay en una línea y la clase FileReader sí. No proporciona un método para leer una línea.

Java proporciona flujos más avanzados: flujo BufferedReader y flujo BufferedWriter, el origen y el destino de ambos deben ser un flujo de entrada de caracteres y un flujo de salida de caracteres. Por lo tanto, si la secuencia de entrada de caracteres del archivo se usa como fuente de la secuencia BufferedReader y la secuencia de salida de caracteres del archivo se usa como destino de la secuencia BufferedWriter, entonces las secuencias creadas por las clases BufferedReader y BufferedWriter tendrán lectura y escritura más fuertes. capacidades que el flujo de entrada de caracteres y el flujo de salida de caracteres. Por ejemplo, las transmisiones de BufferedReader pueden leer archivos línea por línea.

Los métodos de construcción de la clase BufferedReader y BufferedWriter son: 

BufferedReader(Reader in);
BufferedWriter(Writer out);

BufferedReader y BufferedWriter se pueden llamar flujo superior, y el flujo de caracteres al que apuntan se llama flujo inferior. Java utiliza tecnología de almacenamiento en caché para conectar el flujo superior y el flujo inferior. El flujo de entrada de caracteres subyacente primero lee datos en el búfer y el flujo BufferedReader lee datos del búfer; el flujo BufferedWriter escribe datos en el búfer y el flujo de salida de caracteres subyacente escribe continuamente los datos en el búfer en el destino. Cuando la secuencia BufferedWriter llama a Flush() para actualizar el caché o llama al método close() para cerrar, incluso si el búfer no se desborda, la secuencia subyacente escribirá inmediatamente el contenido almacenado en caché en el destino.

Nota : Al cerrar el flujo de salida, primero cierre el flujo de salida almacenado en el búfer y luego cierre el flujo al que apunta el flujo de salida almacenado en el búfer, es decir, primero cierre el flujo superior y luego cierre el flujo inferior. Simplemente cierre la secuencia superior al escribir código, luego la secuencia subyacente de la secuencia superior se cerrará automáticamente.

Por ejemplo:

El archivo a.txt compuesto por oraciones en inglés es el siguiente, cada oración ocupa una línea:

hello
I like you
no yes

Es necesario leer un.txt línea por línea, agregar la cantidad de palabras contenidas en la oración en inglés después de la línea y luego escribir la línea en un archivo llamado b.txt, el código es el siguiente:

import java.io.*;
import java.util.*;
public class Main {
    public static void main(String args[]) {
        File fRead = new File("a.txt");
        File fWrite = new File("b.txt");
        try {
            Writer out = new FileWriter(fWrite);
            BufferedWriter bufferWrite = new BufferedWriter(out);
            Reader in = new FileReader(fRead);
            BufferedReader bufferRead = new BufferedReader(in);
            String str;
            while((str = bufferRead.readLine()) !=null) {
                StringTokenizer fenxi = new StringTokenizer(str);
                int count = fenxi.countTokens();
                str = str+"句子中单词个数:"+count;
                bufferWrite.write(str);
                bufferWrite.newLine();
            }
            bufferWrite.close();
            out.close();
            in = new FileReader(fWrite);
            bufferRead = new BufferedReader(in);
            String s = null;
            System.out.println(fWrite.getName()+"内容:");
            while((s=bufferRead.readLine()) !=null) {
                System.out.println(s);
            }
            bufferRead.close();
            in.close();
        }
        catch(IOException e) {
            System.out.println(e.toString());
        }
    }
}

El resultado de la operación es el siguiente:

b.txt内容:
hello句子中单词个数:1
I like you句子中单词个数:3
no yes句子中单词个数:2

flujo aleatorio java

La secuencia creada por la clase RandomAccessFile se llama secuencia aleatoria . A diferencia de las secuencias de entrada y salida anteriores, la clase RandomAccessFile no es una subclase de la clase InputStream ni una subclase de la clase OutputStream. Sin embargo, la secuencia creada por la clase RandomAccessFile se puede utilizar como origen y destino de la secuencia. En otras palabras, cuando se prepara para leer y escribir un archivo, simplemente cree una secuencia aleatoria que apunte al archivo. Los datos del archivo se pueden leer desde esta secuencia y los datos se pueden escribir en el archivo a través de esta secuencia.

Nota: Dado que RandomAccessFile es seguro para subprocesos, puede abrir varios objetos RandomAccessFile al mismo tiempo para leer el mismo archivo. Sin embargo, tenga en cuenta que si lee el archivo simultáneamente en varios subprocesos, puede causar inconsistencia en los datos u otros problemas.

Los siguientes son los dos constructores de la clase RandomAccessFile:

  1. RandomAccessFile (nombre de cadena, modo de cadena): el nombre del parámetro se utiliza para determinar un nombre de archivo, proporcionando el origen de la secuencia creada y también el destino de la secuencia. El modo del parámetro es r (solo lectura) o rw (legible y escribible), que determina los derechos de acceso de la secuencia creada al archivo.
  2. RandomAccessFile (archivo de archivo, modo cadena): el archivo de parámetros es un objeto de archivo, que proporciona el origen y el destino de la secuencia creada. El modo del parámetro es r (solo lectura) o rw (legible y escribible), que determina los derechos de acceso de la secuencia creada al archivo.

Nota : cuando la secuencia RandomAccessFile apunta a un archivo, el archivo no se actualiza.

Hay un método de búsqueda (a larga) en la clase RandomAccessFile para ubicar la posición de lectura y escritura de la secuencia RandomAccessFile, donde el parámetro a determina el número de bytes entre la posición de lectura y escritura y el comienzo del archivo. Además, la secuencia también puede llamar al método getFilePointer() para obtener la posición actual de lectura y escritura de la secuencia. La secuencia RandomAccessFile lee y escribe archivos de forma más flexible que de forma secuencial.

Los métodos comunes para las transmisiones de RandomAccessFile son los siguientes:

método ilustrar
cerca() cerrar el archivo
getFilePointer() Obtener la posición actual de lectura y escritura
longitud() Obtener la longitud del archivo
leer() Leer un byte de datos de un archivo
leer booleano() Lea un valor booleano del archivo, 0 significa falso; otros valores significan verdadero
leerByte()

leer un byte de un archivo

leerCarbón() Leer un carácter (2 bytes) de un archivo
leerDoble() Leer un valor de coma flotante de doble precisión (8 bytes) de un archivo
leerFlotador() Leer un valor de punto flotante de precisión simple (4 bytes) de un archivo
leer completamente (byte b []) Lea b.length bytes en la matriz b, llene completamente la matriz
leerInt() Leer un valor int (4 bytes) del archivo
leerLínea() leer una línea de texto de un archivo
leer largo() Leer un valor largo (8 bytes) de un archivo
leerCorto() Leer un valor corto (2 bytes) de un archivo
leerByte sin firmar() Leer un byte sin firmar (1 byte) de un archivo
leerUnsignedShort() Leer un valor corto sin firmar (2 bytes) de un archivo
leerUTF() Leer una cadena UTF de un archivo
buscar (posición larga) Posición posición de lectura y escritura
setLength (nueva longitud larga) Establecer la longitud del archivo
saltarBytes(int n) Saltar un número determinado de bytes en un archivo
escribir (byte b []) Escribe b.length bytes en el archivo.
escribir booleano (booleano v) escribir un valor booleano en un archivo como un valor de un solo byte
escribirByte(int v) escribe un byte en el archivo
escribirBytes(cadena s) escribir una cadena en un archivo
escribirChar(char c) escribe un carácter en el archivo
escribir caracteres (cadenas) escribe una cadena como datos de caracteres en un archivo
escribirDoble(doble v) escribe un valor de punto flotante de doble precisión en un archivo
escribirFloat(flotar v) escribe un valor de punto flotante de precisión simple en un archivo
escribirInt(int v) escribe un valor int en el archivo
escribirLong(largo v) Escribe un valor int largo en el archivo.
escribirCorto(int v) escribe un valor int corto en el archivo
escribirUTF(cadena s) escribe una cadena UTF

Nota : Cuando el método readLine() de la secuencia RandomAccessFile lee archivos que contienen caracteres que no son ASCII, como archivos que contienen caracteres chinos, aparecerán "caracteres confusos". Por lo tanto, es necesario volver a codificar la cadena leída por readLine() en una matriz de bytes usando la codificación "iso-8859-1" y luego usar la codificación predeterminada de la máquina actual para convertir la matriz en una cadena. el funcionamiento es el siguiente:

  1. leer
    String str = in.readLine();
  2. 用“iso-8859-1”重新编码
    byte b[] = str.getBytes("iso-8859-1");
  3. 使用当前机器的默认编码将字节数组转化为字符串
    String content = new String(b);
    如果机器的默认编码是“GB2312”,那么
    String content = new String(b);
    等同于
    String content = new String(b,"GB2312");

随机流代码示例: 

import java.io.*;
public class Main {
    public static void main(String args[]) {
        RandomAccessFile inAndOut = null;
        int data[] = {1,2,3,4,5,6,7,8,9,10};
        try {
            inAndOut = new RandomAccessFile("a.txt","rw");
            for(int i=0;i<data.length;i++) {
                inAndOut.writeInt(data[i]);
            }
            for(long i = data.length-1;i>=0;i--) {
                inAndOut.seek(i*4);
                System.out.printf("\t%d",inAndOut.readInt());
                /*
                一个int型数据占4个字节,inAndOut从文件的第36个字节读取最后面的一个整数,每隔4个字节往前读取一个整数
                */
            }
            inAndOut.close();
        }
        catch(IOException e) {}
    }
}

Java数组流

我们要知道,流的源和目的地除了可以是文件以外,还可以是计算机内存

字节数组流

字节数组输入流ByteArrayInputStream和字节数组输出流ByteArrayOutputStream分别使用字节数组作为流的源和目的地。

ByteArrayInputStream流的构造方法:

ByteArrayInputStream(byte[] buf);
ByteArrayInputStream(byte[] buf,int offset,int length);

第一个构造方法构造的字节数组流的源是参数buf指定的数组的全部字节单元。

第二个构造方法构造的字节数组流的源是buf指定的数组从offset处按顺序取的length个字节单元。

字节数组输入流调用public int read();方法可以顺序地从源中读出一个字节,该方法返回读出的字节值;调用public int read(byte[] b,int off,int len);方法可以顺序地从源中读出参数len指定的字节数,并将读出的字节存放到参数b指定的数组中,参数off指定数组b存放读出字节的起始位置,该方法返回实际读出的字节个数,如果未读出字节read方法返回-1。

ByteArrayOutputStream流的构造方法:

ByteArrayOutputStream();
ByteArrayOutputStream(int size);

第一个构造方法构造的字节数组输出流指向一个默认大小为32字节的缓冲区,如果输出流向缓冲区写入的字节个数大于缓冲区时,缓冲区的容量会自动增加。

第二个构造方法构造的字节数组输出流指向的缓冲区的初始大小由参数size指定,如果输出流向缓冲区写入的字节个数大于缓冲区时,缓冲区的容量会自动增加。

字节数组输出流调用public void write(int b);方法可以顺序地向缓冲区写入一个字节;调用public void write(byte[ ] b,int off,int len);方法可以将参数b中指定的len个字节顺序地写入缓冲区,参数off指定从b中写出的字节的起始位置;调用public byte[ ] toByteArray();方法可以返回输出流写入到缓冲区的全部字节。

字符数组流

与字节数组流对应的是字符数组流CharArrayReader类和CharArrayWriter类,字符数组流分别使用字符数组作为流的源和目标。

例如,使用数组流向内存(输出流的缓冲区)写入“mid-autumn festival”和“中秋快乐”,然后再从内存读取曾写入的数据: 

import java.io.*;

public class Main {
    public static void main(String args[]) {
        try {
            // 创建一个字节数组输出流
            ByteArrayOutputStream outByte = new ByteArrayOutputStream();
            // 将字节内容 "mid-autumn festival" 写入字节数组输出流
            byte [] byteContent = "mid-autumn festival ".getBytes();
            outByte.write(byteContent);

            // 创建一个字节数组输入流,并将字节数组输出流的内容传递给它
            ByteArrayInputStream inByte = new ByteArrayInputStream(outByte.toByteArray());
            // 创建一个与字节数组输出流长度相同的字节数组
            byte backByte [] = new byte[outByte.toByteArray().length];
            // 从字节数组输入流中读取内容到 backByte 数组中
            inByte.read(backByte);
            // 将 backByte 数组转换为字符串并打印出来
            System.out.println(new String(backByte));

            // 创建一个字符数组输出流
            CharArrayWriter outChar = new CharArrayWriter();
            // 将字符内容 "中秋快乐" 写入字符数组输出流
            char [] charContent = "中秋快乐".toCharArray();
            outChar.write(charContent);

            // 创建一个字符数组输入流,并将字符数组输出流的内容传递给它
            CharArrayReader inChar = new CharArrayReader(outChar.toCharArray());
            // 创建一个与字符数组输出流长度相同的字符数组
            char backChar [] = new char [outChar.toCharArray().length];
            // 从字符数组输入流中读取内容到 backChar 数组中
            inChar.read(backChar);
            // 将 backChar 数组转换为字符串并打印出来
            System.out.println(new String(backChar));
        }
        catch(IOException exp) {
            // 处理可能发生的 IOException 异常
            exp.printStackTrace();
        }
    }
}

Java数据流

DataInputStream和DataOutputStream类创建的对象称为数据输入流数据输出流。这两个流是很有用的两个流,它们允许程序按着机器无关的风格读取Java原始数据。也就是说,当读取一个数值时,不必再关心这个数值应当是多少个字节。

DataInputStream和DataOutputStream的构造方法如下:

  1. DataInputStream(InputStream in):创建的数据输入流指向一个由参数in指定的底层输入流。
  2. DataOutputStream(OutputStream out):创建的数据输出流指向一个由参数out指定的底层输出流。

DataInputStreamDataOutputStream类的常用方法如下:

方法 说明
close() 关闭流
readBoolean() 读取一个布尔值
readByte() 读取一个字节
readChar() 读取一个字符
readDouble() 读取一个双精度浮点值
readFloat() 读取一个单精度浮点值
readInt() 读取一个int值
readLong() 读取一个长型值
readShort() 读取一个短型值
readUnsignedByte() 读取一个无符号字节
readUnsignedShort() 读取一个无符号短型值
readUTF() 读取一个UTF字符串
skipBytes(int n) 跳过给定数量的字节
writeBoolean(boolean v) 写入一个布尔值
writeBytes(String s) 写入一个字符串
writeChars(String s) 写入字符串
writeDouble(double v) 写入一个双精度浮点值
writeFloat(float v) 写入一个单精度浮点值
writeInt(int v) 写入一个int值
writeLong(long v) 写入一个长型int值
writeShort(int v) 写入一个短型int值
writeUTF(String s) 写入一个UTF字符串

例如,写几个Java类型的数据到一个文件,然后再读出来: 

import java.io.*;

public class Main {
    public static void main(String[] args) {
        // 写入文件
        try (DataOutputStream outputStream = new DataOutputStream(new FileOutputStream("a.txt"))) {
            int intValue = 10;
            double doubleValue = 3.14;
            String stringValue = "Hello, World!";

            outputStream.writeInt(intValue);
            outputStream.writeDouble(doubleValue);
            outputStream.writeUTF(stringValue);

            System.out.println("数据写入成功!");
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 读取文件
        try (DataInputStream inputStream = new DataInputStream(new FileInputStream("a.txt"))) {
            int intValue = inputStream.readInt();
            double doubleValue = inputStream.readDouble();
            String stringValue = inputStream.readUTF();

            System.out.println("读取的整数值:" + intValue);
            System.out.println("读取的浮点数值:" + doubleValue);
            System.out.println("读取的字符串值:" + stringValue);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Java对象流

ObjectInputStream和ObjectOutputStream类分别是InputStream和OutputStream类的子类。ObjectInputStream和ObjectOutputStream类创建的对象称为对象输入流和对象输出流。对象输出流使用writeObject(Object obj)方法将一个对象obj写入到一个文件,对象输入流使用readObject()读取一个对象到程序中。

ObjectInputStream和ObjectOutputStream类的构造方法如下:

  1. ObjectInputStream(InputStream in)
  2. ObjectOutputStream(OutputStream out)

ObjectOutputStream的指向应当是一个输出流对象,因此当准备将一个对象写入到文件时,首先用OutputStream的子类创建一个输出流。 

例如,用FileOutputStream创建一个文件输出流,代码如下:

FileOutputStream fileOut = new FileOutputStream("a.txt");
ObjectOutputStream objectout = new ObjectOutputStream(fileOut);

同样ObjectInputStream的指向应当是一个输入流对象,因此当准备从文件中读入一个对象到程序中时,首先用InputStream的子类创建一个输入流。

例如,用FileInputStream创建一个文件输入流,代码如下:

FileInputStream fileIn = new FileInputStream("a.txt");
ObjectInputStream objectIn = new ObjectInputStream(fileIn);

当使用对象流写入或读入对象时,要保证对象是序列化的,这是为了保证能把对象写入到文件,并能再把对象正确读回到程序中。

一个类如果实现了Serializable接口(java.io包中的接口),那么这个类创建的对象就是所谓序列化的对象。Java类库提供的绝大多数对象都是所谓序列化的。需要强调的是,Serializable接口中没有方法,因此实现该接口的类不需要实现额外的方法。另外需要注意的是,使用对象流把一个对象写入到文件时不仅要保证该对象是序列化的,而且该对象的成员对象也必须是序列化的。

Serializable接口中的方法对程序是不可见的,因此实现该接口的类不需要实现额外的方法,当把一个序列化的对象写入到对象输出流时,JVM就会实现Serializable接口中的方法,将一定格式的文本(对象的序列化信息)写入到目的地。当ObjectInputStream对象流从文件读取对象时,就会从文件中读回对象的序列化信息,并根据对象的序列化信息创建一个对象。

下面是一个使用这两个类进行对象序列化和反序列化的Java代码示例:

import java.io.*;

public class Main {
    public static void main(String[] args) {
        // 序列化对象并写入文件
        try (ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("data.obj"))) {
            // 创建一个自定义对象
            Person person = new Person("John Doe", 25);

            // 将对象写入文件
            outputStream.writeObject(person);

            System.out.println("对象序列化成功!");
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 反序列化对象并读取文件
        try (ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("data.obj"))) {
            // 从文件中读取对象
            Person deserializedPerson = (Person) inputStream.readObject();

            System.out.println("读取的对象信息:");
            System.out.println("姓名:" + deserializedPerson.getName());
            System.out.println("年龄:" + deserializedPerson.getAge());
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    // 自定义Person类,需要实现Serializable接口才能被序列化
    static class Person implements Serializable {
        private String name;
        private int age;

        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public int getAge() {
            return age;
        }
    }
}

上述代码创建了一个名为Person的自定义类,并在其中定义了姓名和年龄属性。该类实现了Serializable接口,使得对象可以被序列化。首先,将一个Person对象序列化并写入到名为"data.obj"的文件中。然后,从文件中反序列化出对象,并进行相关信息的打印输出。

请注意,要想使自定义类能够被序列化,需要确保该类实现了Serializable接口。同时,反序列化过程需要进行类型转换,以恢复原始的对象类型。

Java序列化与对象克隆

Java的序列化和对象克隆都是用于处理对象的机制,但它们有不同的作用和实现方式。

  • 序列化(Serialization):

  1. 序列化用于将对象转换为字节流的过程,以便在网络传输或持久化存储时使用。
  2. 通过实现Serializable接口,类可以支持序列化。该接口没有任何方法,只是一个标记接口。
  3. 使用ObjectOutputStream将对象写入输出流,使用ObjectInputStream从输入流中读取对象。
  4. 序列化过程会将对象的状态以及对象包含的数据保存下来,然后可以重新反序列化为原始对象。
  • 对象克隆(Object Cloning):

  1. 对象克隆用于创建一个现有对象的副本,以便通过克隆对象进行操作,而不影响原对象。
  2. 在默认情况下,Java中的对象克隆是浅克隆,即只复制对象本身,而不复制引用类型的成员变量。
  3. 要实现对象克隆,类必须实现Cloneable接口,并重写clone()方法。
  4. 使用clone()方法可以创建一个新的与原始对象相同的副本。

虽然序列化和对象克隆都涉及对象的复制和重建,但它们的应用场景有所不同:

  • 序列化主要用于对象的传输和持久化存储,例如在网络通信中发送对象、将对象保存到文件或数据库中。
  • 对象克隆主要用于创建一个相同属性的对象副本,在某些场景下可以提高性能、降低开销或简化代码逻辑。

需要注意的是,虽然对象克隆可以方便地创建对象的副本,但它也有一些潜在问题,如浅克隆的风险、深克隆的复杂性等。因此,在使用对象克隆时需要谨慎,并根据具体需求选择合适的实现方式。

扩展小知识:

在编程中,克隆(Clone)和拷贝(Copy)是用于复制对象或数据的概念,但它们有一些区别:

  • 克隆(Clone):

  1. 克隆是创建一个与原始对象具有相同状态和数据的全新对象。
  2. 克隆使用的是对象克隆机制,通过调用对象的 clone() 方法实现。
  3. 克隆得到的对象是独立的,对克隆对象的修改不会影响原始对象。
  4. 在默认情况下,Java 的对象克隆是浅克隆,只复制对象本身,而不复制引用类型的成员变量。如果需要深度克隆,需要在 clone() 方法中手动处理引用类型的成员变量。
  • 拷贝(Copy):

  1. 拷贝是指将一个对象或数据的值复制到另一个对象或数据中。
  2. 拷贝可以通过多种方式进行,包括不同层级的拷贝(如深拷贝和浅拷贝)、手动逐个复制属性等。
  3. 拷贝的实现方式依赖于编程语言或框架提供的特定操作或函数。

区别总结:

  • 克隆是创建一个全新的、与原始对象具有相同状态和数据的对象,通过对象自身的克隆机制实现。
  • 拷贝是将一个对象或数据的值复制到另一个对象或数据中,可以使用不同方式实现。
  • 克隆得到的对象是独立的,对克隆对象的修改不会影响原始对象;而拷贝得到的对象可能与原始对象共享引用数据。
  • 在 Java 中,默认的对象克隆是浅克隆,需要手动处理引用类型的成员变量以实现深度克隆;而拷贝可以根据需求选择不同层级的拷贝方式。

下面是一个使用Java序列化和对象克隆进行对象复制的代码示例: 

import java.io.*;

// 实现Serializable接口以支持序列化
class Person implements Serializable, Cloneable {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 省略了Getter和Setter方法

    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + "}";
    }

    // 重写clone()方法,实现对象的深拷贝
    @Override
    public Object clone() throws CloneNotSupportedException {
        // 调用父类的clone()方法进行浅拷贝
        Person clonedPerson = (Person) super.clone();
        // 对非基本类型的字段进行拷贝
        clonedPerson.name = new String(this.name);
        return clonedPerson;
    }
}

public class Main {
    public static void main(String[] args) {
        // 使用序列化进行对象复制
        Person person1 = new Person("Alice", 25);

        Person person2 = null;
        try {
            // 将person1序列化为字节流
            ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
            ObjectOutputStream objectOut = new ObjectOutputStream(byteOut);
            objectOut.writeObject(person1);

            // 将字节流反序列化为person2对象
            ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
            ObjectInputStream objectIn = new ObjectInputStream(byteIn);
            person2 = (Person) objectIn.readObject();

            // 通过序列化实现的对象复制完成
            System.out.println("使用序列化进行对象复制:");
            System.out.println("原始对象:" + person1);
            System.out.println("复制后的对象:" + person2);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }

        // 使用对象克隆进行对象复制
        Person person3 = new Person("Bob", 30);

        Person person4 = null;
        try {
            // 调用person3的clone()方法进行对象克隆
            person4 = (Person) person3.clone();

            // 通过对象克隆实现的对象复制完成
            System.out.println("使用对象克隆进行对象复制:");
            System.out.println("原始对象:" + person3);
            System.out.println("复制后的对象:" + person4);
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

注意:clone()方法在java.lang.Object类中是受保护的成员,意味着它只能被同一类或子类中的方法访问。这使得对象克隆只能在类内部或继承关系中使用。在示例代码中,要想成功对Person对象进行克隆,必须在Person类中实现Cloneable接口,并覆盖clone()方法,将其访问控制修改为public。通过在Person类中实现Cloneable接口,并重写clone()方法并将其访问控制修改为public,就可以在示例代码中成功地使用对象克隆进行复制了。

Java使用Scanner解析文件

使用默认分隔标记解析文件

创建Scanner对象,并指向要解析的文件,例如:

File file = new File("a.txt");
Scanner sc = new Scanner(file);

那么sc将空格作为分隔标记,调用next()方法依次返回file中的单词,如果file最后一个单词已被next()方法返回,sc调用hasNext()将返回false,否则返回true。

另外,对于数字型的单词,比如108,167.92等可以用nextInt()或nextDouble()方法来代替next()方法,即sc可以调用nextInt()或nextDouble()方法将数字型单词转化为int或double数据返回,但需要特别注意的是,如果单词不是数字型单词,调用nextInt()或nextDouble()方法将发生InputMismatchException异常,在处理异常时可以调用next()方法返回该非数字化单词。

使用正则表达式作为分隔标记解析文件

创建Scanner对象,指向要解析的文件,并使用useDelimiter方法指定正则表达式作为分隔标记,例如:

File file = new File("a.txt");
Scanner sc = new Scanner(file);
sc.useDelimiter(正则表达式);

那么sc将正则表达式作为分隔标记,调用next()方法依次返回file中的单词,如果file最后一个单词已被next()方法返回,sc调用hasNext()将返回false,否则返回true。

另外,对于数字型的单词,比如1979,0.618等可以用nextInt()或nextDouble()方法来代替next()方法,即sc可以调用nextInt()或nextDouble()方法将数字型单词转化为int或double数据返回,但需要特别注意的是,如果单词不是数字型单词,调用nextInt()或nextDouble()方法将发生InputMismatchException异常,那么在处理异常时可以调用next()方法返回该非数字化单词。

以下是使用Scanner解析文件中的数字,并计算这些数字的平均值的代码示例:

a.txt:

张三的成绩是70分,李四的成绩是80分,赵五的成绩是90分。
import java.io.*;
import java.util.*;

public class Main {
    public static void main(String[] args) {
        File file = new File("a.txt");
        Scanner sc = null;
        int count = 0;
        double sum = 0;

        try {
            double score = 0;
            sc = new Scanner(file);
            sc.useDelimiter("[^0123456789.]+");  // 使用非数字字符作为分隔符

            while (sc.hasNextDouble()) {
                score = sc.nextDouble();
                count++;
                sum = sum + score;
                System.out.println(score);
            }

            double average = sum / count;
            System.out.println("平均成绩:" + average);

            // 关闭Scanner
            sc.close();

        } catch (Exception exp) {
            System.out.println(exp);
        }
    }
}

Java文件对话框

文件对话框是一个选择文件的界面。Javax.swing包中的JFileChooser类可以创建文件对话框,使用该类的构造方法JFileChooser()创建初始不可见的有模式文件对话框。然后文件对话框调用下述2个方法:

showSaveDialog(Component a);
showOpenDialog(Component a);

都可以使得对话框可见,只是呈现的外观有所不同,showSaveDialog方法提供保存文件的界面,showOpenDialog方法提供打开文件的界面。上述两个方法中的参数a指定对话框可见时的位置,当a是null时,文件对话框出现在屏幕的中央;如果组件a不空,文件对话框在组件a的正前面居中显示。

用户单击文件对话框上的“确定”、“取消”或“关闭”图标,文件对话框将消失,ShowSaveDialog()showOpenDialog()方法返回下列常量之一:

JFileChooser.APPROVE OPTION
JFileChooser.CANCEL_OPTION

如果希望文件对话框的文件类型是用户需要的几种类型,比如,扩展名是.jpeg等图像类型的文件,可以使用FileNameExtensionFilter类事先创建一个对象,在JDK 1.6版本,FileNameExtensionFilter类在javax.swing.filechooser包中。

下面是一个示例代码片段,演示了如何使用JFileChooser类创建文件对话框,并设置文件类型过滤器:

import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;

public class Main {

    public static void main(String[] args) {
        // 创建文件对话框对象
        JFileChooser fileChooser = new JFileChooser();

        // 设置文件类型过滤器
        FileNameExtensionFilter filter = new FileNameExtensionFilter("图像文件", "jpg", "gif");
        fileChooser.setFileFilter(filter);

        // 显示保存文件对话框并获取用户操作的结果
        int result = fileChooser.showSaveDialog(null);

        // 处理用户的操作结果
        if (result == JFileChooser.APPROVE_OPTION) {
            // 用户点击了"确定"按钮
            String selectedFilePath = fileChooser.getSelectedFile().getPath();
            System.out.println("用户选择的文件路径:" + selectedFilePath);
        } else if (result == JFileChooser.CANCEL_OPTION) {
            // 用户点击了"取消"按钮
            System.out.println("用户取消了操作");
        }
    }
}

在上述示例中,我们创建了一个JFileChooser对象,并通过setFileFilter()方法设置文件类型过滤器为指定的图像文件类型(.jpg和.gif)。然后,我们调用showSaveDialog(null)来显示保存文件的文件对话框。根据用户的操作结果,我们可以处理用户选中的文件路径或取消操作的情况。 

Java带进度条的输入流

ProgressMonitorInputStream是一个可以显示读取进度条的输入流类。它可以在文件读取过程中弹出一个进度条窗口来显示读取速度和进度。其构造方法是:

ProgressMonitor InputStream(Conmponent c,String s,InputStream);
  1. 组件c指定了进度条窗口将显示在哪个组件的前面。可以传入一个具体的组件对象,如JFrame或JPanel,进度条会显示在该组件的正前方。如果你传入null,则进度条将显示在屏幕的正前方。
  2. 字符串s是进度条窗口的标题,用于描述正在进行的操作。可以根据需要给进度条窗口设置一个有意义的标题。
  3. 输入流InputStream是要读取的文件的输入流。通过将文件输入流传给ProgressMonitorInputStream,你可以在读取文件时实时显示进度条并监控读取的进度。

需要注意的是,ProgressMonitorInputStream属于javax.swing包,所以在使用之前需要确保已经导入该包。另外,为了使进度条能够正常显示,需要在图形界面线程中执行文件的读取操作。 

import javax.swing.*;
import java.io.*;

public class Main {
    public static void main(String args[]) {
        byte b[] = new byte[1024]; // 增加缓冲区的大小,以便更好地读取文件内容
        try {
            FileInputStream input = new FileInputStream("a.txt");

            // 创建进度条窗口的父组件(例如:JFrame)
            JFrame frame = new JFrame();

            ProgressMonitorInputStream in = new ProgressMonitorInputStream(
                    frame, "读取txt", input);
            ProgressMonitor p = in.getProgressMonitor(); // 获得进度条

            int bytesRead; // 用来记录每次读取到的字节数
            while ((bytesRead = in.read(b)) != -1) {
                String s = new String(b, 0, bytesRead); // 仅使用读取到的字节构建字符串
                System.out.print(s);
                Thread.sleep(1000); // 为了看清进度条,延迟了一定时间
            }

            in.close();

            // 关闭进度条窗口
            frame.dispose();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Java文件锁

通过使用文件锁,可以确保多个程序对同一个文件进行处理时不会发生混乱。Java在JDK 1.4版本后提供了文件锁功能,可以通过FileLock和FileChannel类来实现。

下面是使用文件锁的基本步骤:

  1. 使用RandomAccessFile流建立指向文件的流对象,并设置读写属性为"rw"。
    RandomAccessFile input = new RandomAccessFile("a.txt","rw");
  2. input流调用方法getChannel()获得一个连接到底层文件的FileChannel对象(信道)。
    FileChannel channel = input.getChannel();
  3. 信道调用tryLock()或lock()方法获得一个FileLock(文件锁)对象,这一过程也称做对文件加锁。
    FileLock lock = channel.tryLock();

文件锁对象产生后,将禁止任何程序对文件进行操作或再进行加锁。对一个文件加锁之后,如果想读、写文件必须让FileLock对象调用release()释放文件锁。

lock.release();

以下是一个代码示例,演示如何使用文件锁进行文件读写: 

import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;

public class Main {
    public static void main(String[] args) {
        RandomAccessFile file = null;
        FileChannel channel = null;
        FileLock lock = null;

        try {
            // 1. 创建RandomAccessFile流对象并设置读写属性为"rw"
            file = new RandomAccessFile("a.txt", "rw");

            // 2. 获取FileChannel对象
            channel = file.getChannel();

            while (true) {
                try {
                    // 3. 尝试加锁
                    lock = channel.tryLock();
                    if (lock != null) {
                        // 文件已被锁定
                        System.out.println("文件已被锁定");
                        // 进行文件读取操作
                        String line = file.readLine();
                        System.out.println("读取的文本: " + line);
                        // 释放文件锁
                        lock.release();
                        System.out.println("释放文件锁");
                        // 继续下一轮循环
                        continue;
                    }
                } catch (Exception e) {
                    // 加锁失败
                    e.printStackTrace();
                }

                // 等待一段时间后再尝试加锁
                Thread.sleep(1000);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (file != null)
                    file.close();
                if (channel != null)
                    channel.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

该示例中,首先创建一个RandomAccessFile对象并设置读写属性为"rw"。然后通过调用getChannel()方法获取与文件连接的FileChannel对象。进入循环后,程序会尝试对文件进行加锁,并检查是否成功获取到FileLock对象。如果成功获取到锁,则进行文件读取操作,读取完毕后释放文件锁。如果未能获取到锁,则等待一段时间后再次尝试。

Supongo que te gusta

Origin blog.csdn.net/m0_74293254/article/details/132411710
Recomendado
Clasificación