输入流和输出流

输入流(InputStream)

输入流用于从数据源读取数据。常见的数据源包括文件、网络连接、内存中的字节数组等。InputStream 是所有字节输入流的基类,它定义了一些基本的方法,具体的子类实现了这些方法以适应不同的数据源。

输出流(OutputStream)

输出流用于将数据写入目标位置。常见的目标位置包括文件、网络连接、内存中的字节数组等。OutputStream 是所有字节输出流的基类,它定义了一些基本的方法,具体的子类实现了这些方法以适应不同的目标位置。

File文件操作

初识

java.io.File类是一个与文件本身操作有关的类,此类可以实现文件创建、删除、重命名、取得文件大小、修改日期等常见系统文件操作

如果要使用File类则必须提供完整的文件操作路径,对于文件路径的设置可以通过File类的构造方法完成,当获取了正确的文件路径后就可以进行文件创建于删除操作、File类文件基本操作方法

No. 方法 类型 描述
1 public File(String pathname) 构造 给定一个要操作文件的完整路径
2 public File(File parent,String child) 构造 给定要操作文件的父路径和子文件名称
3 public boolean createNewFile() throws IOException 普通 创建文件
4 public boolean delete() 普通 删除文件
5 public boolean exists() 普通 判断给定路径是否存在

示例

import java.io.File;
public class JavaIODemo{
	public static void main(String[] args) throws Exception{
		File file = new File("D:\\1.txt"); //文件路径: "\\"转义为"\"
		if(file.exists()){
			file.delete();
		}
		else{
			System.out.println(file.createNewFile());
		}
	}
}

分隔符

windows中是"\" 而在UNIX或类UNIX操作系统中路径分隔符是"/"

为了解决不同操作系统的路径分隔符问题,在java.io.File类中提供了一个路径分隔符常量。

路径分隔符:public static final String separator,在不同操作系统可以获取不同的分隔符

在实际项目开发中建议使用以下方法定义:

File file = new File("d:" + File.separator + "1.txt");

在使用File类创建文件时必须保证父路径存在,当前程序是直接在根路径下进行文件创建的,所以用户可以直接使用createNewFile()方法创建文件。

如果文件需要保存在特定的目录中,则必须先创建父目录而后才可以进行文件创建。

File类父路径操作方法

No. 方法 类型 描述
1 public File getParentFile() 普通 找到一个指定路径的父路径
2 public boolean mkdirs() 普通 创建指定目录

创建带目录的文件

import java.io.File

public class JavaDemo{
     public static void main(String[] args) throws Exception{
         File file = new File("F:" + File.separator + "hello" + File.separator + "hh" + 							File.separator + "word.txt");
         if (!file.getParentFile().exists()){
             file.getParentFile().mkdirs();
         }
         if(file.exists()){
             file.delete();
         }
         else{
             System.out.println(file.createNewFile());
         }
     }
}
确保父路径存在:当你尝试创建一个新文件时,如果该文件所在的目录(父路径)不存在,那么调用 createNewFile() 方法会失败。这是因为文件系统不允许在一个不存在的目录中创建文件。

直接在根路径下创建文件:如果你是在根路径(如 / 或 C:\)下创建文件,因为根路径总是存在的,所以你可以直接调用 createNewFile() 而无需担心父路径的问题。

创建特定目录下的文件:如果文件需要保存在一个特定的目录中,你需要首先确保这个目录已经存在。如果目录不存在,你需要先创建它。可以使用 File 类的 mkdir() 或 mkdirs() 方法来创建单个或多个层级的目录。

获取文件元数据信息方法

No. 方法 类型 描述
1 public boolean canRead() 普通 文件是否能读
2 public boolean canWrite() 普通 文件是否能写
3 public boolean canExecute() 普通 文件是否能执行
4 public long length() 普通 获取文件大小(返回字节长度)
5 public long lastModified() 普通 获取最后一次修改日期
6 public boolean isDirectory() 普通 是否是目录
7 public boolean isFile() 普通 是否是文件
8 public boolean isHidden() 普通 是否隐藏
9 public File[] listFiles() 普通 列出目录中的所有文件信息

**注:**listFile()方法返回的是File型对象数组,即在获取数组中的每一个File类实例后可以继续进行各个子路径处理。

一个目录非常庞大,经常会出现目录嵌套,可以用递归处理

import java.io.File

public class JavaDemo{
    public static void main(String[] args) throws Exception{
        File file = new File("D: " + File.separator);
        listDir(file);
    }
    public static void listDir(File file){
        if(file.isDirectory){
            File results[] = file.listFiles();
            if(results != null){
                for(int x = 0;x<results.length;x++){
                    listDir(results[x]);//不断递归至不是目录
                }
            }
        }
    }
}

j

字节流与字符流

流是I/O的基本操作单元,在流的设计中都会有输入和输出两方面支持。

在程序中所有数据都是以流的方式进行传输和储存的。

程序需要通过数据文件读取数据时就可以利用输入流来完成,而当程序需要将数据保存到问文件时,就可以使用输出流完成。

字节操作流:OutputStream、InputStream

字符操作流:Writer、Reader

执行数据流资源操作一般要按照以下几个步骤进行,下面以文件操作例子

(1)如果要操作的是文件首先通过File类对象找到一个要操作的文件路径路径不存在就要创建路径
(2)通过字节流或者字符流的子类为字节流或字符流的对象实例化(向上转型)
(3)执行读写操作
(4)一定要关闭操作的资源(close()),不管随后代码如何操作资源永远要关闭

OutputStream类常用方法

No. 方法 类型 描述
1 public abstract void write(int b) throws IOException 普通 输出单个字节数据
2 public void write(byte[] b) throws IOException 普通 输出一组字节数据
3 public void write(byte[] b,int off,int len) throws IOException 普通 输出部分字节数据
4 public void close() throws IOException 普通 关闭输出流
5 public void flush() throws IOException 普通 刷新缓冲区

FileOutputStream类主要目的是为OutputStream 父类实例化(FileOutputStream是其子类)

FileOutputSteam类常用方法

No. 方法 类型 描述
1 public FileOutputStream(File file) throws FileNotFoundException 构造 采用覆盖的形式创建文件输出流
2 public FileOutputStream(File file,boolean append) throws FileNotFoundException 构造 采用覆盖或追加的形式创建文件输出流

是否选择向上转型

向上转型使得代码更加灵活和通用,但同时也限制了对子类特有方法的访问。选择哪种方式取决于你的具体需求。如果你只需要调用 OutputStream 接口中的方法,那么使用 OutputStream 类型的变量是更好的选择。如果你需要调用 FileOutputStream 特有的方法,那么应该使用 FileOutputStream 类型的变量。

pic

通过AutoCloseable自动关闭接口

Closeable继承AutoCloseable

通过OutputStream的继承结构可以发现,OutputStream是AutoCloseable接口子类,所以此时就可以利用try…catch实现自动关闭操作。

//自动关闭输出流
try(OutputStream output = new FileOutputStream(file,true)){
	String str = "hello word\r\n";   //"\r\n为文件换行"
	output..write(str.getBytes());
}catch(IOException e){
	e.printStackTrace();
}

//这个每一次执行代码实际上会导致新的内容替换掉已有的文件数据,如果想追加内容则可以更换FileOutputStream类的构造方法

文件追加内容

OutputStream output = new FileOutputStream(file,true);
String str = "hh";
output.write(str.getBytes());
output.close();

InputStream字节输入流

常用方法

No. 方法 类型 描述
1 public abstract int read() throws IOException 普通 读取单个字节数据,如果现在已经读取到底了,返回-1
2 public int read(byte[] b) throws IOException 普通 读取一组字节数据,返回的是读取的个数;如果没有数据,且已经读取到底则返回-1
3 public int read(byte[] b,int off,int len) throws IOException 普通 读取一组字节数据(只占数组的部分)
4 public void close() throws IOException 普通 关闭输出流
5 public byte[] readAllBytes() throws IOException 普通 读取输入流全部字节数据,JDK1.9后
6 public long transferTo(OutputStream out) throws IOException 普通 输入流转存到输出流,JDK1.9后

InputStream类提供的主要方法为read(),可以实现单个字节或一组字节数据的读取操作

InputStream属于抽象类,对于文件的读取可以通过FileInputStream子类进行实例化

使用readAllBytes()方法可以一次性返回输入流中的所有字节数据,这样开发者在将字节数据内容转为字符串数据的时候就可以不需要进行长度控制了,如( new String(data,0,len) )。

但是使用这个方法需要注意读取的数据内容的字节数据不要过大,否则程序可能出现问题。

Writer字符输出流

在使用OutputStream进行字节数据输出,这类数据适合网络传输,但在操作时需要进行字节数组转换操作。

为了简化输出操作,java提供有字符输出流,直接支持字符串输出

底层通信处理都是依靠字节实现数据交互

关系

字符流的最大特点就是可以直接进行字符串输出。

Writer类的常用方法

No. 方法 类型 描述
1 public Writer append(CharSequence csq) throws IOException 普通 追加输出内容
2 public void write(char[] cbuf) throws IOException 普通 输出字符数组
3 public void write(int c) throws IOException 普通 输出单个字符
4 public void write(String str) throws IOException 普通 输出字符串
5 public abstract void flush() throws IOException 普通 刷新缓冲区
6 public abstract void close() throws IOException 普通 关闭输入流

Writer类进行文件操作时可以利用FileWriter子类进行对象实例化

FileWriter常用方法

No. 方法 类型 描述
1 public FileWriter(File file) throws IOException 构造 采用覆盖的形式创建文件输出流
2 public FileWriter(File file,boolean append) throws IOException 构造 采用覆盖或追加的形式创建文件输出流

Reader字符输入流

Reader是实现字符输入流的操作类,可以实现char数据类型的读取。

R

常用类方法

No. 方法 类型 描述
1 public int read() throws IOException 普通 读取单个字符,无数据读取时返回-1
2 public int read(char[] cbuf) throws IOException 普通 读取多个字符,并返回读取个数
3 public long skip(long n) throws IOException 普通 跳过指定的字符个数后读取
4 public boolean ready() throws IOException 普通 是否可以开始读取数据
5 public abstract void close() throws IOException 普通 关闭输入流

Reader是抽象类,通过文件读取数据可以使用FileReader子类进行实例化

在Writer类输出是使用flush方法,刷新缓冲区,更新内容保存在文件中。

字符流关闭自动清空缓冲区

close()方法进行输出流关闭,在关闭的时候会自动进行缓冲区的强制刷新,所以程序内容也可以正常的保存到文件中。

flush()

flush() 方法是 OutputStream 接口中的一个重要方法,用于确保所有缓冲的输出数据都被立即写入目标位置。在某些情况下,输出流可能会缓冲数据以提高性能,但这可能导致数据不是立即写入目标位置。调用 flush() 方法可以强制将缓冲区中的数据写入目标位置,从而确保数据的一致性和完整性。

主要用途

  1. 确保数据立即写入:在某些情况下,你可能希望确保数据立即写入目标位置,而不是等待缓冲区满或流关闭。例如,在网络通信中,你可能希望确保数据立即发送到服务器。
  2. 避免数据丢失:在关闭流之前调用 flush() 可以确保所有缓冲的数据都被写入目标位置,避免因意外中断而导致数据丢失。

示例

以下是一个简单的示例,展示了如何在使用 FileOutputStream 写入数据时调用 flush() 方法:

import java.io.FileOutputStream;
import java.io.IOException;

public class FlushExample {
    public static void main(String[] args) {
        // 定义文件路径
        String filePath = "example.txt";

        try {
            // 创建 FileOutputStream 对象
            FileOutputStream fos = new FileOutputStream(filePath);

            // 要写入的数据
            String data = "Hello, World! This is a test message.";

            // 将字符串转换为字节数组
            byte[] bytes = data.getBytes();

            // 写入数据到文件
            fos.write(bytes);

            // 强制将缓冲区中的数据写入文件
            fos.flush();

            // 关闭流
            fos.close();

            System.out.println("数据已成功写入文件: " + filePath);

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

解释

  1. 创建 FileOutputStream 对象:使用 new FileOutputStream(filePath) 创建一个 FileOutputStream 对象。
  2. 写入数据:使用 fos.write(bytes) 方法将字节数组写入文件。
  3. 调用 flush() 方法:使用 fos.flush() 方法强制将缓冲区中的数据写入文件。
  4. 关闭流:使用 fos.close() 方法关闭文件输出流,释放系统资源。

注意事项

  • 性能影响:频繁调用 flush() 方法可能会降低性能,因为每次调用都会导致系统 I/O 操作。因此,通常在关键点调用 flush() 方法,而不是频繁调用。
  • 异常处理:调用 flush() 方法可能会抛出 IOException,因此需要在 try-catch 块中处理异常。

其他流的 flush() 方法

不仅仅是 FileOutputStream,其他继承自 OutputStream 的类也提供了 flush() 方法。例如:

  • BufferedOutputStream:用于缓冲输出流,调用 flush() 方法会将缓冲区中的数据写入底层输出流。
  • PrintStream:用于格式化输出,调用 flush() 方法会将缓冲区中的数据写入目标位置。

转换流

R

p

每一种流程处理都有一个文件处理类

字节文件流都是字节流的直接子类,而字符流中的两个文件操作留都是转换流的子类

JDK1.9后,使用transferTo()可以很方便的将输入流的数据保存到输出流

内存操作流

输入流

i

输出流

o

内存操作流是 Java 中用于在内存中读写数据的流。这些流允许你在内存中创建和操作数据,而不需要与文件系统或其他外部资源进行交互。内存操作流特别适用于临时数据处理和缓存数据。

常见的内存操作流

  1. ByteArrayInputStream:从内存中的字节数组读取数据。
  2. ByteArrayOutputStream:将数据写入内存中的字节数组。
  3. CharArrayReader:从内存中的字符数组读取数据。
  4. CharArrayWriter:将数据写入内存中的字符数组。

示例代码

使用 ByteArrayInputStreamByteArrayOutputStream

以下是一个简单的示例,展示了如何使用 ByteArrayInputStreamByteArrayOutputStream 在内存中读写数据。

示例 1:使用 ByteArrayOutputStream 写入数据
import java.io.ByteArrayOutputStream;
import java.io.IOException;

public class ByteArrayOutputStreamExample {
    public static void main(String[] args) {
        try {
            // 创建 ByteArrayOutputStream 对象
            ByteArrayOutputStream baos = new ByteArrayOutputStream();

            // 要写入的数据
            String data = "Hello, World! This is a test message.";

            // 将字符串转换为字节数组
            byte[] bytes = data.getBytes();

            // 写入数据到内存
            baos.write(bytes);

            // 获取内存中的数据
            byte[] result = baos.toByteArray();

            // 打印结果
            System.out.println(new String(result));

            // 关闭流
            baos.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
示例 2:使用 ByteArrayInputStream 读取数据
import java.io.ByteArrayInputStream;
import java.io.IOException;

public class ByteArrayInputStreamExample {
    public static void main(String[] args) {
        try {
            // 要读取的数据
            String data = "Hello, World! This is a test message.";

            // 将字符串转换为字节数组
            byte[] bytes = data.getBytes();

            // 创建 ByteArrayInputStream 对象
            ByteArrayInputStream bais = new ByteArrayInputStream(bytes);

            // 读取数据
            int byteRead;
            while ((byteRead = bais.read()) != -1) {
                // 打印读取的字符
                System.out.print((char) byteRead);
            }

            // 关闭流
            bais.close();

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

解释

ByteArrayOutputStream

  1. 创建 ByteArrayOutputStream 对象:使用 new ByteArrayOutputStream() 创建一个 ByteArrayOutputStream 对象。
  2. 写入数据:使用 baos.write(bytes) 方法将字节数组写入内存。
  3. 获取内存中的数据:使用 baos.toByteArray() 方法将内存中的数据转换为字节数组。
  4. 关闭流:使用 baos.close() 方法关闭输出流,释放系统资源。

ByteArrayInputStream

  1. 创建 ByteArrayInputStream 对象:使用 new ByteArrayInputStream(bytes) 创建一个 ByteArrayInputStream 对象。
  2. 读取数据:使用 bais.read() 方法从内存中读取一个字节,直到返回 -1 表示数据读取完毕。
  3. 关闭流:使用 bais.close() 方法关闭输入流,释放系统资源。

其他内存操作流

CharArrayReaderCharArrayWriter

这些类类似于 ByteArrayInputStreamByteArrayOutputStream,但它们操作的是字符数组而不是字节数组。

示例:使用 CharArrayWriter 写入数据
import java.io.CharArrayWriter;
import java.io.IOException;

public class CharArrayWriterExample {
    public static void main(String[] args) {
        try {
            // 创建 CharArrayWriter 对象
            CharArrayWriter caw = new CharArrayWriter();

            // 要写入的数据
            String data = "Hello, World! This is a test message.";

            // 写入数据到内存
            caw.write(data);

            // 获取内存中的数据
            String result = caw.toString();

            // 打印结果
            System.out.println(result);

            // 关闭流
            caw.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
示例:使用 CharArrayReader 读取数据
import java.io.CharArrayReader;
import java.io.IOException;

public class CharArrayReaderExample {
    public static void main(String[] args) {
        try {
            // 要读取的数据
            String data = "Hello, World! This is a test message.";

            // 将字符串转换为字符数组
            char[] chars = data.toCharArray();

            // 创建 CharArrayReader 对象
            CharArrayReader car = new CharArrayReader(chars);

            // 读取数据
            int charRead;
            while ((charRead = car.read()) != -1) {
                // 打印读取的字符
                System.out.print((char) charRead);
            }

            // 关闭流
            car.close();

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

System类对I/O的支持

System类的3个I/O常量

No. 常量 类型 描述
1 public static final PrintStream err 常量 错误输出
2 public static final PrintStream out 常量 系统输出
3 public static final InputStream in 常量 系统输入

BufferedReader缓冲输入流

No. 方法 类型 描述
1 public BufferedReader(Reader in) 构造 接取一个Reader类的实例
2 public String readLine() throws IOException 普通 一次性从缓冲区中将内容全部读取出来
BufferedReader定义的构造方法只能接受字符输入的实例,所以必须使用字符输入转换成inputStreamReader类将字节输入流System.in变成字符流

b

b

BufferedReader 是 Java 标准库中的一个类,用于从字符输入流中高效地读取文本数据。它通过内部缓冲来提高读取效率,特别是对于大量文本数据的读取非常有用。BufferedReader 继承自 Reader 类,并提供了额外的方法来方便地读取行数据。

主要特点

  1. 缓冲机制BufferedReader 使用内部缓冲区来减少对底层输入流的读取次数,从而提高读取效率。
  2. 读取行数据:提供了 readLine() 方法,可以方便地按行读取文本数据。
  3. 读取单个字符:提供了 read() 方法,可以读取单个字符。
  4. 读取多个字符:提供了 read(char[] cbuf, int off, int len) 方法,可以读取多个字符到字符数组中。

常见的构造方法

  1. BufferedReader(Reader in):创建一个新的缓冲读取器,使用默认大小的缓冲区。
  2. BufferedReader(Reader in, int size):创建一个新的缓冲读取器,使用指定大小的缓冲区。

示例代码

以下是一个简单的示例,展示了如何使用 BufferedReader 从文件中读取文本数据。

示例:使用 BufferedReader 读取文件

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class BufferedReaderExample {
    public static void main(String[] args) {
        // 定义文件路径
        String filePath = "example.txt";

        try {
            // 创建 FileReader 对象
            FileReader fr = new FileReader(filePath);

            // 包装成 BufferedReader 对象
            BufferedReader br = new BufferedReader(fr);

            // 读取文件内容,按行读取
            String line;
            while ((line = br.readLine()) != null) {
                // 打印读取的每一行
                System.out.println(line);
            }

            // 关闭流
            br.close();

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

解释

  1. 创建 FileReader 对象:使用 new FileReader(filePath) 创建一个 FileReader 对象,用于从文件中读取字符数据。

  2. 包装成 BufferedReader 对象:使用 new BufferedReader(fr) 创建一个 BufferedReader 对象,以提高读取效率。

  3. 读取文件内容

    • 使用 br.readLine() 方法按行读取文件内容。该方法返回读取的行,如果到达文件末尾则返回 null
    • 使用 while 循环不断读取行数据,直到读取到 null 表示文件结束。
  4. 关闭流:使用 br.close() 方法关闭缓冲读取器,释放系统资源。

常用方法

  1. String readLine():读取一行文本。返回的字符串不包含任何行终止符(如 \n\r\n)。如果到达文件末尾则返回 null
  2. int read():读取一个字符,返回读取的字符值,如果到达文件末尾则返回 -1
  3. int read(char[] cbuf, int off, int len):读取最多 len 个字符,并将其存储在字符数组 cbuf 中,从偏移量 off 开始。
  4. void close():关闭此缓冲读取器并释放与之关联的所有系统资源。
  5. long skip(long n):跳过并丢弃此输入流中的 n 个字符。

注意事项

  1. 异常处理:调用 readLine()read() 等方法可能会抛出 IOException,因此需要在 try-catch 块中处理异常。
  2. 资源管理:务必在不再需要时关闭流,以释放系统资源。可以使用 try-with-resources 语句来自动管理资源。

使用 try-with-resources 语句

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class BufferedReaderExample {
    public static void main(String[] args) {
        // 定义文件路径
        String filePath = "example.txt";

        try (FileReader fr = new FileReader(filePath);
             BufferedReader br = new BufferedReader(fr)) {

            // 读取文件内容,按行读取
            String line;
            while ((line = br.readLine()) != null) {
                // 打印读取的每一行
                System.out.println(line);
            }

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

Scanner

Scanner 是 Java 标准库中的一个类,用于解析基本类型和字符串的输入。它可以从标准输入(如键盘输入)、文件、字符串等输入源读取数据,并提供了丰富的解析方法,使得输入处理变得更加方便和灵活。

构造方法

  1. Scanner(InputStream source):从输入流中读取数据。
  2. Scanner(File source):从文件中读取数据。
  3. Scanner(Path source):从路径中读取数据。
  4. Scanner(String source):从字符串中读取数据。
  5. Scanner(Readable source):从可读对象中读取数据。

常用方法

  1. next():读取下一个完整的标记(token),默认分隔符是空白字符(空格、制表符、换行符等)。
  2. nextLine():读取下一行文本,包括换行符。
  3. nextInt():读取下一个整数。
  4. nextDouble():读取下一个双精度浮点数。
  5. nextBoolean():读取下一个布尔值。
  6. hasNext():判断是否还有下一个标记。
  7. hasNextLine():判断是否还有下一行文本。
  8. hasNextInt():判断是否还有下一个整数。
  9. hasNextDouble():判断是否还有下一个双精度浮点数。
  10. hasNextBoolean():判断是否还有下一个布尔值。
  11. useDelimiter(Pattern pattern):设置分隔符模式。
  12. close():关闭扫描器,释放相关资源。

示例代码

以下是一些使用 Scanner 的示例,展示了如何从不同输入源读取数据。

示例 1:从标准输入读取数据

import java.util.Scanner;

public class ScannerExample1 {
    public static void main(String[] args) {
        // 创建 Scanner 对象,从标准输入读取数据
        Scanner scanner = new Scanner(System.in);

        System.out.println("请输入您的姓名:");
        String name = scanner.nextLine();

        System.out.println("请输入您的年龄:");
        int age = scanner.nextInt();

        System.out.println("请输入您的身高(米):");
        double height = scanner.nextDouble();

        System.out.println("您好," + name + ",您今年 " + age + " 岁,身高 " + height + " 米。");

        // 关闭扫描器
        scanner.close();
    }
}

示例 2:从文件读取数据

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

public class ScannerExample2 {
    public static void main(String[] args) {
        // 定义文件路径
        String filePath = "data.txt";

        try {
            // 创建 Scanner 对象,从文件读取数据
            Scanner scanner = new Scanner(new File(filePath));

            // 读取文件内容
            while (scanner.hasNextLine()) {
                String line = scanner.nextLine();
                System.out.println(line);
            }

            // 关闭扫描器
            scanner.close();

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

示例 3:从字符串读取数据

import java.util.Scanner;

public class ScannerExample3 {
    public static void main(String[] args) {
        // 定义字符串
        String input = "John Doe 25 1.75";

        // 创建 Scanner 对象,从字符串读取数据
        Scanner scanner = new Scanner(input);

        // 读取数据
        String name = scanner.next();
        String lastName = scanner.next();
        int age = scanner.nextInt();
        double height = scanner.nextDouble();

        System.out.println("姓名:" + name + " " + lastName);
        System.out.println("年龄:" + age);
        System.out.println("身高:" + height);

        // 关闭扫描器
        scanner.close();
    }
}

解释

  1. 创建 Scanner 对象
    • 从标准输入读取数据:使用 new Scanner(System.in) 创建一个 Scanner 对象,从标准输入读取数据。
    • 从文件读取数据:使用 new Scanner(new File(filePath)) 创建一个 Scanner 对象,从文件读取数据。
    • 从字符串读取数据:使用 new Scanner(input) 创建一个 Scanner 对象,从字符串读取数据。
  2. 读取数据
    • nextLine():读取下一行文本。
    • nextInt():读取下一个整数。
    • nextDouble():读取下一个双精度浮点数。
    • next():读取下一个完整的标记(token)。
  3. 判断是否有更多数据
    • hasNextLine():判断是否还有下一行文本。
    • hasNextInt():判断是否还有下一个整数。
    • hasNextDouble():判断是否还有下一个双精度浮点数。
  4. 关闭扫描器:使用 scanner.close() 方法关闭扫描器,释放相关资源。

注意事项

  1. 异常处理:在从文件读取数据时,可能会抛出 FileNotFoundException,因此需要在 try-catch 块中处理异常。
  2. 资源管理:务必在不再需要时关闭扫描器,以释放系统资源。可以使用 try-with-resources 语句来自动管理资源。

使用 try-with-resources 语句

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

public class ScannerExample2 {
    public static void main(String[] args) {
        // 定义文件路径
        String filePath = "data.txt";

        try (Scanner scanner = new Scanner(new File(filePath))) {
            // 读取文件内容
            while (scanner.hasNextLine()) {
                String line = scanner.nextLine();
                System.out.println(line);
            }

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

注:

Scanner可以接收File,InputStream、Readable类型的输入实例,这样就可以使用统一标准实现数据的读取操作

Scanner默认将空字符作为分隔符,然而.txt文件中的内容是以换行符作为分隔符,所以为了正常数据读取,就必须利用useDelimiter()方法指定数据读取的分隔符。

import java.util.Scanner
    public class JavaDemo{
        public static void main(String[] args) throws Exception{
            Scanner scan = new Scanner(new File(xxx.txt));
            scan.useDelimiter("\n");
            while(scan.hasNext()){
                System.out.println(scan.next());
            }
        }
    }

对象序列化

对象序列化就是把一个变成二进制数据留的一种方法

一个类的对象要想实现序列化,则对象所在的类必须实现java.io.Serializable接口。然而这个接口没有提供任何抽象方法,只是一个标识接口,表示一种对象可以被序列化的能力—–

Java 序列化和反序列化是将对象的状态保存到文件或传输到网络,并从文件或网络中恢复对象状态的过程。序列化将对象转换为字节流,而反序列化则是将字节流还原为对象。这在分布式系统、持久化存储和对象传输中非常有用。

序列化

序列化是指将对象的状态信息转换为可以存储或传输的形式。在 Java 中,可以通过实现 Serializable 接口来使对象支持序列化。

实现 Serializable 接口

import java.io.Serializable;

public class Person implements Serializable {
    private static final long serialVersionUID = 1L; // 可选,用于版本控制

    private String name;
    private int age;

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

    // Getters and Setters
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

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

序列化对象

使用 ObjectOutputStream 将对象写入文件或输出流。

示例:将对象序列化到文件

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class SerializationExample {
    public static void main(String[] args) {
        // 创建一个 Person 对象
        Person person = new Person("John Doe", 30);

        try {
            // 创建 FileOutputStream 对象
            FileOutputStream fos = new FileOutputStream("person.ser");

            // 创建 ObjectOutputStream 对象
            ObjectOutputStream oos = new ObjectOutputStream(fos);

            // 序列化对象
            oos.writeObject(person);

            // 关闭流
            oos.close();
            fos.close();

            System.out.println("对象已成功序列化到文件: person.ser");

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

反序列化

反序列化是指将字节流还原为对象。在 Java 中,可以通过 ObjectInputStream 从文件或输入流中读取对象。

示例:从文件反序列化对象

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class DeserializationExample {
    public static void main(String[] args) {
        try {
            // 创建 FileInputStream 对象
            FileInputStream fis = new FileInputStream("person.ser");

            // 创建 ObjectInputStream 对象
            ObjectInputStream ois = new ObjectInputStream(fis);

            // 反序列化对象
            Person person = (Person) ois.readObject();

            // 关闭流
            ois.close();
            fis.close();

            System.out.println("对象已成功反序列化: " + person);

        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

注意事项

  1. serialVersionUID:建议为每个实现 Serializable 接口的类定义一个 serialVersionUID,用于版本控制。如果不定义,默认会根据类的结构生成一个唯一的 serialVersionUID,这可能导致在类结构发生变化时无法正确反序列化。

  2. 瞬态字段:使用 transient 关键字标记的字段不会被序列化。例如:

    private transient String password;
    
  3. 自定义序列化和反序列化:可以通过实现 writeObjectreadObject 方法来自定义序列化和反序列化过程。例如:

    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.io.Serializable;
    
    public class Person implements Serializable {
        private static final long serialVersionUID = 1L;
    
        private String name;
        private int age;
        private transient String password;
    
        public Person(String name, int age, String password) {
            this.name = name;
            this.age = age;
            this.password = password;
        }
    
        // Getters and Setters
    
        private void writeObject(ObjectOutputStream out) throws IOException {
            out.defaultWriteObject();
            out.writeObject(password);
        }
    
        private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
            in.defaultReadObject();
            password = (String) in.readObject();
        }
    
        @Override
        public String toString() {
            return "Person{name='" + name + "', age=" + age + ", password='" + password + "'}";
        }
    }
    

ObjectOutputStream类可以将对象转为特定格式的二进制数据输出

ObjectInputStream可以读取ObjectOutputStream类输出的二进制对象数据,并将其转为具体类型的对象返回。

ObjectOutputStream类常用方法

No. 方法 类型 描述
1 public ObjectOutputStream(OutputStream out) throws IOException 构造 传入输出的对象
2 public final void writeObject(Object obj) throws IOException 普通 输出对象

ObjectInputStream类常用方法

No. 方法 类型 描述
1 public ObjectInputStream(InputStream in) throws IOException 构造 构造输入对象
2 public final Object readObject() throws IOException,ClassNotFoundException 普通 从指定位置读取对象

拓展:

Java
  • 企业级应用:Java 序列化常用于企业级应用,如分布式系统、RPC(远程过程调用)和消息队列。
  • 持久化存储:用于将对象状态保存到文件或数据库。
PHP
  • Web 应用:PHP 序列化常用于 Web 应用,如会话管理、缓存和配置文件。
  • 数据交换:用于在不同系统之间传递数据。