IO流
Java中的I/O(输入/输出)流是用于处理数据传输的机制。Java I/O流可以分为两大类:字节流和字符流。每种类型又可以分为输入流和输出流,用于从源读取数据或向目的地写入数据。
文件操作
File
类在 Java 中用于表示文件和目录路径名的抽象表示形式。创建 File
对象时,可以使用不同的构造函数,例如 File(String pathname)
或 File(String parent, String child)
。一旦创建了 File
对象,你可以使用它提供的各种方法来执行文件系统操作。以下是一些常用的 File
类方法:
- 获取文件信息
getName()
: 获取文件名或目录名。getParent()
: 获取父目录路径名的字符串,如果未指定父目录,则返回 null。getParentFile()
: 获取父目录的 File 对象。getPath()
: 获取路径名的抽象路径名字符串。getAbsolutePath()
: 获取文件的绝对路径。getCanonicalPath()
: 获取文件的规范路径(解析了所有符号链接)。length()
: 获取文件长度(字节数)。lastModified()
: 获取文件最后修改时间。
- 检查文件或目录
exists()
: 判断文件或目录是否存在。isDirectory()
: 判断是否为目录。isFile()
: 判断是否为文件。isHidden()
: 判断文件是否为隐藏文件。
- 创建和删除
createNewFile()
: 当且仅当不存在具有此抽象路径名指定名称的文件时,创建一个新的空文件。mkdir()
: 创建由此抽象路径名命名的目录。mkdirs()
: 创建由此抽象路径名命名的目录,包括所有必需但不存在的父目录。delete()
: 删除由此抽象路径名命名的文件或目录(删除空目录
)。
- 列出目录内容
list()
: 返回一个字符串数组,包含此抽象路径名表示的目录中的文件和目录名称。listFiles()
: 返回一个 File 数组,包含此抽象路径名表示的目录中的文件和目录。
- 重命名和移动
renameTo(File dest)
: 将文件重命名为指定的抽象路径名。
- 文件操作
setReadOnly()
: 将文件设置为只读。setWritable(boolean writable, boolean ownerOnly)
: 设置文件或目录的可写权限。setReadable(boolean readable, boolean ownerOnly)
: 设置文件或目录的可读权限。
字节流
字节流处理的是原始的字节数据,主要用于处理二进制文件。
- InputStream:所有字节输入流的超类,用于从各种数据源读取字节。
FileInputStream
:从文件中读取字节。ByteArrayInputStream
:从字节数组中读取数据。BufferedInputStream
:提供缓冲功能,提高读取效率。ObjectInputStream
: 对象字节输入流
- OutputStream:所有字节输出流的超类,用于向各种数据目的地写入字节。
FileOutputStream
:向文件写入字节。ByteArrayOutputStream
:向字节数组写入数据。BufferedOutputStream
:提供缓冲功能,提高写入效率。ObjectOutputStream
: 对象字节输出流
字符流
字符流处理的是字符数据,主要用于处理文本文件。
- Reader:所有字符输入流的超类,用于从各种数据源读取字符。
FileReader
:从文件中读取字符。StringReader
:从字符串中读取字符。BufferedReader
:提供缓冲功能,支持按行读取。
- Writer:所有字符输出流的超类,用于向各种数据目的地写入字符。
FileWriter
:向文件写入字符。StringWriter
:向字符串写入字符。BufferedWriter
:提供缓冲功能,提高写入效率。
字节流和字符流是Java I/O库中处理数据输入输出的两种主要方式,它们在处理数据时有着本质的区别:
字节流(Byte Streams)
- 用途:字节流主要用于处理二进制数据,如文件、图片、音频、视频等。
- 类层次:字节流的基类是
InputStream
和OutputStream
。 - 操作单位:字节流以字节为单位进行数据的读写操作。
- 使用场景:当你需要处理非文本数据或需要精确控制数据的每个字节时,应使用字节流。
- 例子:
FileInputStream
、FileOutputStream
、ByteArrayInputStream
、ByteArrayOutputStream
等。
字符流(Character Streams)
- 用途:字符流主要用于处理文本数据,如字符串、文本文件等。
- 类层次:字符流的基类是
Reader
和Writer
。 - 操作单位:字符流以字符为单位进行数据的读写操作。
- 使用场景:当你需要处理文本数据,特别是涉及字符编码转换时,应使用字符流。字符流能够更好地处理文本数据,尤其是涉及多字节字符集(如UTF-8、UTF-16)的情况。
- 例子:
FileReader
、FileWriter
、StringReader
、StringWriter
等。
主要区别
- 1.数据单位:字节流处理的是原始的字节数据,而字符流处理的是字符数据。
- 2.编码问题:字符流在处理文本数据时会考虑字符编码,能够正确处理多字节字符集,而字节流则不会。
- 3.适用场景:字节流适用于所有类型的数据传输,字符流适用于文本数据的传输。
- 4.效率:对于文本数据,字符流通常比字节流更高效,因为它们内部进行了优化处理,如缓冲和字符编码转换。
使用建议
- 处理文本数据:使用字符流,特别是当文本数据涉及多种字符编码时。
- 处理二进制数据:使用字节流,如处理图片、音频、视频文件等。
- 文本文件的读写:在读写文本文件时,推荐使用字符流,因为它们能够处理字符编码转换,避免乱码问题。
节点流和处理流
分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 |
---|---|---|---|---|
抽象基类 | InputStream | OutputStream | Reader | Writer |
访问文件 | FileInputStream | FileOutputStream | FileReader | FileWriter |
访问数组 | ByteArrayInputStream | ByteArrayOutputStream | CharArrayReader | CharArrayWriter |
访问管道 | PipedInputStream | PipedOutputStream | PipedReader | PipedWriter |
缓冲流 | BufferedInputStream | BufferedOutputStream | BufferedReader | BufferedWriter |
转换流 | - | - | InputStreamReader | OutputStreamWriter |
对象流 | ObjectInputStream | ObjectOutputStream | ||
打印流 | - | PrintStream | - | PrintWriter |
推回输入流
- PushbackInputStream
- PushbackInputReader
特殊流
DataInputStream
DataOutputSteam
节点流(Node Streams)和处理流(Processing Streams)是Java I/O流的两个基本概念,它们在数据处理中扮演不同的角色,并且通常一起使用以实现更高效和灵活的数据处理。
节点流(Node Streams)
节点流是直接与数据源或目的地进行交互的流。它们是I/O操作的基础,负责实际读取或写入数据。
- 直接连接:节点流直接连接到数据源(如文件、网络连接等)。
- 类型:节点流可以是字节流(如
FileInputStream
、FileOutputStream
)或字符流(如FileReader
、FileWriter
)。 - 功能:节点流提供基本的读取和写入功能,但不提供额外的数据处理功能,如缓冲、格式化等。
- 使用场景:当你需要直接从文件读取数据或向文件写入数据时,会使用节点流。
处理流(Processing Streams)
处理流建立在节点流之上,提供额外的数据处理功能。
- 包装节点流:处理流通常包装一个节点流或其他处理流,提供额外的功能。
- 类型:处理流可以是字节流(如
BufferedInputStream
、BufferedOutputStream
)或字符流(如BufferedReader
、BufferedWriter
)。 - 功能:处理流可以提供缓冲、字符编码转换、数据格式化、数据过滤等高级功能。
- 使用场景:当你需要在读写数据时增加额外的处理功能,比如缓冲以提高效率,或者转换数据格式时,会使用处理流。
关系和区别
关系:处理流通常用于增强节点流的功能。它们不直接与数据源或目的地交互,而是通过包装节点流来实现更复杂的操作。
区别
:
- 直接性:节点流直接与数据源或目的地交互,而处理流通过包装节点流间接操作。
- 功能:节点流提供基本的读写功能,处理流提供额外的数据处理功能。
- 使用:在实际应用中,通常先创建节点流连接到数据源或目的地,然后创建处理流包装节点流以增加所需的功能。
I/O流的使用
使用I/O流时,通常需要进行以下步骤:
- 1.创建流对象,指定数据源或目的地。
- 2.使用流对象的方法进行读写操作。
- 3.关闭流,释放系统资源。
序列化和反序列化
- 1.序列化就是在保存数据时,保存数据的值和数据类型。
- 2.反序列化就是在恢复数据时,恢复数据的值和数据类型。
- 3.要让某个对象支持序列化机制,则必须让其类是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一:Serializable // 这是一个标记接口。Externalizable
注意事项
- 1.读写顺序要一致。
- 2.要求实现序列化或反序列化对象,需要实现 Serializable 接口。
- 3.序列化的类中建议添加 SerialVersionUID,为了提高版本的兼容性。
- 4.序列化对象时,默认将里面所有属性都进行序列化,但除了 static 或 transient 修饰的成员。
- 5.序列化对象时,要求里面属性的类型也需要实现序列化接口。
- 6.序列化具备可继承性,也就是如果某类已经实现了序列化,则它的所有子类也已经默认实现了序列化。
标准输入输出流
在Java中,标准输入输出流指的是用于程序与用户进行交互的标准通道。这些流是预定义的,允许程序读取输入和输出数据到控制台(通常是命令行界面)。以下是Java中标准输入输出流的详细信息:
标准输入流(Standard Input Stream)
- 类名:
java.io.InputStream
- 常用类:
java.io.BufferedReader
和java.util.Scanner
- 描述:标准输入流通常用于从键盘读取数据。在Java中,
System.in
是一个InputStream
对象,代表标准输入流。
示例代码(使用Scanner读取输入)
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入一些文本:");
String input = scanner.nextLine();
System.out.println("你输入的是:" + input);
scanner.close();
}
}
标准输出流(Standard Output Stream)
- 类名:
java.io.OutputStream
- 常用类:
java.io.PrintStream
和java.io.PrintWriter
- 描述:标准输出流通常用于向控制台输出数据。在Java中,
System.out
是一个PrintStream
对象,代表标准输出流。
示例代码(使用PrintStream输出)
public class Main {
public static void main(String[] args) {
System.out.println("这是一个标准输出流的示例。");
}
}
标准错误流(Standard Error Stream)
- 类名:
java.io.OutputStream
- 常用类:
java.io.PrintStream
- 描述:标准错误流用于输出错误信息。在Java中,
System.err
是一个PrintStream
对象,代表标准错误流。它与标准输出流类似,但通常用于输出错误消息,以便于区分正常输出和错误输出。
示例代码(使用PrintStream输出错误信息)
public class Main {
public static void main(String[] args) {
System.err.println("这是一个错误信息。");
}
}
1. 使用 Scanner
类读取输入
Scanner
类是Java标准库中非常灵活的一个类,可以用来读取不同类型的数据。
读取字符串
Scanner scanner = new Scanner(System.in);
System.out.print("请输入字符串:");
String inputString = scanner.nextLine();
System.out.println("你输入的字符串是:" + inputString);
读取整数
System.out.print("请输入一个整数:");
int inputInt = scanner.nextInt();
System.out.println("你输入的整数是:" + inputInt);
读取浮点数
System.out.print("请输入一个浮点数:");
double inputDouble = scanner.nextDouble();
System.out.println("你输入的浮点数是:" + inputDouble);
2. 使用 BufferedReader
和 InputStreamReader
读取输入
对于更复杂的输入处理,可以使用 BufferedReader
和 InputStreamReader
。
读取字符串
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
System.out.print("请输入字符串:");
String inputString = reader.readLine();
System.out.println("你输入的字符串是:" + inputString);
3. 使用 Console
类读取密码或敏感信息
如果你需要读取密码或其他敏感信息,可以使用 Console
类。
读取密码
Console console = System.console();
if (console != null) {
String username = console.readLine("请输入用户名: ");
char[] password = console.readPassword("请输入密码: ");
// 处理用户名和密码
}
注意事项
- 当使用
nextInt()
,nextDouble()
等方法读取输入时,如果输入的不是预期类型的数据,程序会抛出InputMismatchException
异常。因此,你可能需要添加异常处理逻辑来处理这种情况。 - 使用
nextLine()
方法读取输入后,如果紧接着使用nextInt()
,nextDouble()
等方法,可能会遇到输入缓冲区的问题。通常建议在读取数字之前调用nextLine()
来清除缓冲区。
转换流
转换流-InputStreamReader 和 OutputStreamWriter
介绍
- 1.
InputStreamReader
: Reader的子类,可以将InputStream(字节流)包装成Reader(字符流)。 - 2.
OutputStreamWriter
: Writer的子类,实现将OutputStream(字节流)包装成Writer(字符流)。 - 3.当处理纯文本数据时,如果使用字符流效率更高,并且可以有效解决中文问题,所以建议将字节流转换成字符流。
- 4.可以在使用时指定编码格式(比如 utf-8, gbk, gb2312, ISO8859-1 等)。
转换流(Conversion Streams)在Java中指的是 InputStreamReader
和 OutputStreamWriter
这两个类,它们用于在字节流和字符流之间进行转换。它们是处理文本数据时非常重要的工具,尤其是在处理不同编码的文本文件时。
InputStreamReader
InputStreamReader
是 Reader
的一个子类,它将一个字节输入流(InputStream
)转换为字符流(Reader
)。这意味着你可以使用字符流的方法来读取字节流中的数据。
用途:当你需要从字节流中读取文本数据,并且需要按照特定的字符编码来处理这些数据时,
InputStreamReader
就非常有用。它允许你指定字符编码,从而正确地将字节转换为字符。示例代码:
InputStream inputStream = new FileInputStream("example.txt"); InputStreamReader isr = new InputStreamReader(inputStream, "UTF-8");
OutputStreamWriter
OutputStreamWriter
是 Writer
的一个子类,它将一个字符输出流(Writer
)转换为字节流(OutputStream
)。这允许你将字符数据写入字节流中,并且可以指定字符编码。
用途:当你需要将字符数据写入到一个字节流中,并且需要按照特定的字符编码来输出这些数据时,
OutputStreamWriter
就非常有用。它允许你指定字符编码,从而正确地将字符转换为字节。示例代码:
OutputStream outputStream = new FileOutputStream("example.txt"); OutputStreamWriter osw = new OutputStreamWriter(outputStream, "UTF-8");
使用转换流的好处
- 字符编码处理:转换流允许你指定字符编码,这对于处理文本文件非常重要,尤其是当文件使用了非默认编码(如中文、日文、韩文等)时。
- 提高效率:在处理文本数据时,使用字符流比直接使用字节流更高效,因为字符流内部处理了字符编码的转换。
- 兼容性:转换流可以与Java I/O库中的其他字符流类(如
BufferedReader
、BufferedWriter
)一起使用,提供缓冲功能,提高读写效率。
注意事项
- 当使用转换流时,确保正确地关闭流,以释放系统资源。可以使用
try-with-resources
语句来自动管理资源。 - 在处理文件时,指定正确的字符编码非常重要,否则可能会出现乱码问题。
打印流
打印流只有 输出流没有输入流
打印流(Print Streams)是Java I/O库中用于输出数据的特殊流,它们提供了方便的方法来输出各种数据类型(如基本数据类型和字符串)到输出目的地(通常是控制台或文件)。打印流的主要目的是为了方便地打印数据,而不是为了高效地处理大量数据。
主要特点
- 1.格式化输出:打印流允许你以格式化的方式输出数据,例如,可以指定输出的宽度、精度等。
- 2.自动刷新:打印流(如
PrintStream
)可以配置为自动刷新,这意味着在输出数据后,不需要显式调用flush()
方法来刷新缓冲区。 - 3.易于使用:打印流提供了简单的方法来输出各种数据类型,如
print()
和println()
,这些方法可以接受不同类型的参数。
常用的打印流类
- PrintStream:这是最常用的打印流类,它继承自
OutputStream
。PrintStream
提供了多种print
和println
方法来输出不同类型的数据。它通常用于输出到文件或控制台。 - PrintWriter:这个类继承自
Writer
类,提供了与PrintStream
类似的功能,但用于字符流。PrintWriter
也提供了print
和println
方法,并且可以指定字符编码。
示例代码
使用 PrintStream 输出到控制台
import java.io.PrintStream;
public class PrintStreamExample {
public static void main(String[] args) {
PrintStream ps = new PrintStream(System.out);
ps.println("Hello, World!");
ps.println(123);
ps.println(true);
ps.close();
}
}
使用 PrintWriter 输出到文件
import java.io.PrintWriter;
import java.io.FileWriter;
public class PrintWriterExample {
public static void main(String[] args) {
try (PrintWriter pw = new PrintWriter(new FileWriter("output.txt"))) {
pw.println("Hello, World!");
pw.println(123);
pw.println(true);
} catch (Exception e) {
e.printStackTrace();
}
}
}
注意事项
- 打印流虽然方便,但它们通常不是最高效的输出方式,特别是当需要输出大量数据时。在这些情况下,直接使用字节流或字符流可能更合适。
PrintStream
和PrintWriter
都提供了自动刷新功能,这在某些情况下非常有用,但也可能导致性能下降,因为每次输出后都会进行刷新操作。
抽象类(Abstract Class)
- 定义:抽象类是一种不能被实例化的类,它通常作为其他类的基类。
- 作用
- 提供通用模板:抽象类可以定义一些通用的属性和方法,这些属性和方法可以被子类继承和使用。
- 强制实现:抽象类可以包含抽象方法,这些方法没有具体实现,迫使任何继承该抽象类的非抽象子类必须提供这些方法的具体实现。
- 代码复用:通过继承抽象类,子类可以复用基类的代码,减少重复代码的编写。
- 多态性:抽象类支持多态性,允许通过基类的引用来操作派生类的对象。
抽象方法(Abstract Method)
定义:抽象方法是一种没有具体实现的方法,只有方法签名(方法名和参数列表),在抽象类中声明。
作用
:
- 定义接口:抽象方法用于定义子类必须实现的接口,但具体实现细节留给子类决定。
- 强制子类实现:任何继承抽象类的非抽象子类都必须提供所有继承的抽象方法的具体实现。
- 灵活性:抽象方法允许基类定义一个方法的框架,但允许子类根据自己的需求来实现具体逻辑。
修饰器模式
修饰器模式(Decorator Pattern)是一种结构型设计模式,它允许你通过将对象放入包含行为的特殊封装类中来为对象动态地添加新的行为。这种模式提供了一种灵活的替代方案,而不是通过继承来扩展对象的功能。修饰器模式通常用于在不修改现有代码的情况下,给对象添加新的功能。
核心概念
- 组件(Component):定义一个对象接口,可以给这些对象动态地添加职责。
- 具体组件(Concrete Component):实现了组件接口的具体类,是被修饰的对象。
- 装饰器(Decorator):维持一个指向组件对象的引用,并定义一个与组件接口一致的接口。
- 具体装饰器(Concrete Decorator):具体的装饰器类,实现了装饰器接口,并在保持组件接口的同时,添加了新的行为。
优点
- 灵活性:通过组合的方式,可以在运行时动态地给对象添加职责。
- 避免使用继承:装饰器模式避免了创建一个继承了原有类的新子类,从而避免了继承带来的固定性和复杂性。
- 单一职责:装饰器类可以专注于添加一个特定的职责,而不是像继承那样,一个子类可能需要继承多个职责。
缺点
- 小的性能开销:装饰器模式会引入更多的对象,因此会增加一些性能开销。
- 代码复杂性:虽然装饰器模式提供了灵活性,但同时也增加了系统的复杂性,特别是当装饰器链很长时。
应用场景
- 当你想要给对象添加功能,但又不希望使用继承来实现时。
- 当你需要动态地给对象添加职责,这些职责可以被撤销时。
- 当对象的职责可以被独立地拆分时。
示例代码
// 组件接口
interface Component {
void operation();
}
// 具体组件
class ConcreteComponent implements Component {
public void operation() {
System.out.println("ConcreteComponent operation");
}
}
// 装饰器基类
abstract class Decorator implements Component {
protected Component component;
public Decorator(Component component) {
this.component = component;
}
public void operation() {
component.operation();
}
}
// 具体装饰器
class ConcreteDecorator extends Decorator {
public ConcreteDecorator(Component component) {
super(component);
}
public void operation() {
super.operation();
addedBehavior();
}
private void addedBehavior() {
System.out.println("ConcreteDecorator added behavior");
}
}
// 使用示例
public class DecoratorPatternDemo {
public static void main(String[] args) {
Component component = new ConcreteComponent();
Component decorator = new ConcreteDecorator(component);
decorator.operation();
}
}
在这个例子中,ConcreteDecorator
类通过继承 Decorator
类并添加新的行为来扩展 ConcreteComponent
的功能。这种方式允许我们在不修改 ConcreteComponent
类的情况下,动态地添加新的功能。