输入与输出
1. 流从何而来
在Java中,输入流和输出流用于进行数据的输入和输出操作。这些流可以从各种来源和目标中读取和写入数据。以下是一些流可能来源的示例:
- 文件流(File Streams):你可以使用文件输入流(
FileInputStream
)来从文件中读取数据,或使用文件输出流(FileOutputStream
)将数据写入文件。这是处理文件操作的常见方式。 - 内存流(Memory Streams):Java中有
ByteArrayInputStream
和ByteArrayOutputStream
等内存流,它们允许你在内存中读取和写入数据,而不需要实际的文件。 - 网络流(Network Streams):你可以使用套接字流来与网络连接进行数据通信。
Socket
类提供了套接字输入流(getInputStream()
)和套接字输出流(getOutputStream()
)。 - 标准输入输出流(Standard Streams):Java提供了标准输入流(
System.in
)和标准输出流(System.out
),用于从控制台读取输入和将输出发送到控制台。 - 管道流(Pipe Streams):Java还提供了管道输入流(
PipedInputStream
)和管道输出流(PipedOutputStream
),用于在线程之间进行数据传输。 - 字符串流(String Streams):你可以使用
StringReader
和StringWriter
来将数据读取和写入字符串,而不是文件或网络。 - 其他来源:除了上述来源外,你还可以使用各种其他输入流和输出流来与数据库、外部设备、其他应用程序和数据源进行通信。
在Java中,这些流提供了一种通用的方式来处理各种数据来源和目标。你可以选择适合你需求的流,并使用相应的输入和输出流来读取和写入数据。流的API使数据处理变得更加通用和灵活,同时也提供了对数据的高级控制和操作能力。
2. 得到流,最易于使用的静态方法
获取输入和输出流的方式通常使用静态方法,这些方法是用于创建输入流和输出流实例的静态工厂方法。以下是一些常见的静态方法用于获取输入和输出流:
- 文件输入输出流(FileInputStream 和 FileOutputStream):
FileInputStream
:使用构造函数创建。例如:FileInputStream fileInputStream = new FileInputStream("file.txt");
FileOutputStream
:使用构造函数创建。例如:FileOutputStream fileOutputStream = new FileOutputStream("output.txt");
- 字节数组输入输出流(ByteArrayInputStream 和 ByteArrayOutputStream):
ByteArrayInputStream
:使用构造函数创建,将字节数组作为参数传递。例如:ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArray);
ByteArrayOutputStream
:使用构造函数创建。例如:ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
- 字符输入输出流(FileReader 和 FileWriter):
FileReader
:使用构造函数创建。例如:FileReader fileReader = new FileReader("file.txt");
FileWriter
:使用构造函数创建。例如:FileWriter fileWriter = new FileWriter("output.txt");
- 网络输入输出流(Socket 类):
Socket.getInputStream()
:通过Socket
类的getInputStream()
方法获取输入流。例如:InputStream inputStream = socket.getInputStream();
Socket.getOutputStream()
:通过Socket
类的getOutputStream()
方法获取输出流。例如:OutputStream outputStream = socket.getOutputStream();
- 缓冲输入输出流(BufferedInputStream 和 BufferedOutputStream):
BufferedInputStream
:使用构造函数创建,将其他输入流作为参数传递。例如:BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
BufferedOutputStream
:使用构造函数创建,将其他输出流作为参数传递。例如:BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream);
这些静态方法和构造函数用于创建输入流和输出流的实例,以便进行读取和写入操作。你可以根据需要选择适当的方法和类,然后使用它们来操作输入和输出流。
3. 读取字节
3.1 批量读取字节
在 Java 中,你可以使用 read(byte[] b)
方法来批量读取字节,而不是逐个字节地读取。这样可以提高读取的效率,特别是在处理大量数据时。以下是一个示例,演示如何使用 read(byte[] b)
方法来批量读取字节:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class ReadBytesInBulk {
public static void main(String[] args) {
String sourceFileName = "source.txt"; // 输入文件名
String destinationFileName = "destination.txt"; // 输出文件名
try (FileInputStream inputStream = new FileInputStream(sourceFileName);
FileOutputStream outputStream = new FileOutputStream(destinationFileName)) {
byte[] buffer = new byte[1024]; // 创建一个字节数组缓冲区
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead); // 批量写入读取的字节到输出文件
}
System.out.println("File copied successfully.");
} catch (IOException e) {
e.printStackTrace();
}
}
}
在这个示例中,我们使用 read(byte[] b)
方法,它会尝试读取尽可能多的字节(最多 b.length
个字节)并将它们存储在指定的字节数组 buffer
中。然后,我们使用 outputStream.write()
方法将从输入流中读取的字节批量写入输出文件。
这种方式更加高效,因为它减少了读取和写入操作的次数,特别是在处理大文件时。记住在使用完输入和输出流后,要确保它们被正确关闭,可以使用 try-with-resources 来实现自动关闭资源。
3.2 对于文件读取字节
从文件中一次性读取所有字节,Files.readAllBytes(Path path)
是一个非常便捷的方法。这是 Java NIO(New I/O)的一部分,它提供了一种简单的方式来读取文件的全部内容到字节数组中。
以下是一个示例:
import java.nio.file.Files;
import java.nio.file.Path;
import java.io.IOException;
public class ReadFileInOneGo {
public static void main(String[] args) {
Path path = Path.of("example.txt"); // 文件路径
try {
byte[] bytes = Files.readAllBytes(path);
String content = new String(bytes, "UTF-8"); // 将字节数组转换为字符串
System.out.println("File content: " + content);
} catch (IOException e) {
e.printStackTrace();
}
}
}
在这个示例中,我们使用 Files.readAllBytes(Path path)
方法来读取文件的全部内容,并将其存储在 byte[]
中。然后,我们可以将字节数组转换为字符串或进行其他处理。
这种方法非常适合在不需要逐行读取文件内容的情况下,一次性读取整个文件。不过要注意,对于大文件,一次性读取可能会占用较多内存,因此要谨慎使用,特别是在处理大文件时。
3.2 将输入流保存到文件
使用 Files.copy
方法是一个非常便捷的方式来将输入流的内容保存到文件,特别是在 Java 的 NIO(New I/O)中。
以下是一个示例,演示如何使用 Files.copy
方法来将输入流保存到文件:
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.io.IOException;
public class SaveInputStreamToFile {
public static void main(String[] args) {
String sourceFileName = "source.txt"; // 输入文件名
Path destinationFilePath = Path.of("destination.txt"); // 输出文件路径
try (InputStream inputStream = new FileInputStream(sourceFileName)) {
Files.copy(inputStream, destinationFilePath, StandardCopyOption.REPLACE_EXISTING);
System.out.println("Input stream saved to file successfully.");
} catch (IOException e) {
e.printStackTrace();
}
}
}
在这个示例中,我们使用 Files.copy
方法将输入流 inputStream
中的数据复制到指定的文件 destinationFilePath
中。StandardCopyOption.REPLACE_EXISTING
选项指示如果目标文件已经存在,将其替换。如果你不需要替换目标文件,可以省略此选项。
这是一种更简单和便捷的方式来将输入流保存到文件,特别是在 Java 7 及更高版本中引入的 Files
类。这种方法适用于将输入流中的数据保存到文件,不需要手动读取和写入字节。
3.3 java9及更高版本读取所有字节
在 Java 9 及更高版本,你可以使用 InputStream
和 ReadAllBytes
方法来从输入流中读取所有字节。这是一种非常方便的方法,无需手动编写循环来读取字节。以下是如何使用 InputStream.readAllBytes()
方法:
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;
public class ReadAllBytesFromInputStream {
public static void main(String[] args) {
try (InputStream inputStream = new FileInputStream("example.txt")) {
byte[] allBytes = inputStream.readAllBytes();
String content = new String(allBytes, "UTF-8"); // 将字节数组转换为字符串
System.out.println("File content: " + content);
} catch (IOException e) {
e.printStackTrace();
}
}
}
在这个示例中,我们使用 inputStream.readAllBytes()
方法来一次性读取输入流中的所有字节,并将它们存储在 allBytes
字节数组中。然后,我们可以将字节数组转换为字符串或进行其他处理。
这个方法非常简便,适用于从输入流中读取所有字节的情况。请注意,该方法在 Java 9 及更高版本中可用。如果你使用的是较旧的 Java 版本,可以使用以前的示例中的方法来读取所有字节。
3.4 java9及更高版本读取N个字节
在 Java 9 及更高版本,你可以使用 InputStream
的 readNBytes(int len)
方法来一次性读取指定数量(len个)的字节。这个方法在读取字节时非常方便,无需手动编写循环。以下是如何使用 readNBytes
方法:
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;
public class ReadNBytesFromInputStream {
public static void main(String[] args) {
String sourceFileName = "source.txt"; // 输入文件名
int n = 1024; // 读取的字节数
try (InputStream inputStream = new FileInputStream(sourceFileName)) {
byte[] bytes = inputStream.readNBytes(n);
// 检查实际读取的字节数
int bytesRead = bytes.length;
System.out.println("Read " + bytesRead + " bytes from the input stream.");
} catch (IOException e) {
e.printStackTrace();
}
}
}
在这个示例中,我们使用 inputStream.readNBytes(n)
方法来一次性读取指定数量的字节,并将它们存储在 bytes
字节数组中。这个方法非常方便,适用于从输入流中读取指定数量的字节,而无需手动循环。请注意,该方法在 Java 9 及更高版本中可用。
4. 读写文本文件
4.1 读文本文件
4.1.1 InputStreamReader
InputStreamReader
是 Java 中用于将字节流(InputStream
)转换为字符流(Reader
)的类。它提供了字符编码的支持,允许你将字节数据按照特定的字符编码转换为字符数据。这在处理文本文件时非常有用,因为文本文件通常以特定的字符编码进行存储,如UTF-8、UTF-16、ISO-8859-1等。
以下是 InputStreamReader
的一些重要特性和用法:
字符编码支持:你可以在创建
InputStreamReader
时指定字符编码,以确保正确地将字节转换为字符。例如,如果文件是以UTF-8编码存储的,你可以这样创建InputStreamReader
:InputStreamReader isr = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
缓冲:虽然
InputStreamReader
本身不提供缓冲功能,但你可以将它与BufferedReader
结合使用,以提高读取性能。BufferedReader
可以用于逐行读取字符数据,并提供了缓冲机制。BufferedReader reader = new BufferedReader(isr); String line; while ((line = reader.readLine()) != null) { // 处理每一行文本 }
自动关闭:你可以使用
try-with-resources
语句来自动关闭InputStreamReader
,以释放资源。try (InputStreamReader isr = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) { // 读取文件内容 } catch (IOException e) { // 处理IO异常 }
InputStreamReader
是处理文本文件的常见工具之一,它允许你从字节输入流中读取字符数据,并确保正确的字符编码。这对于读取和处理不同字符编码的文本文件非常有用。
4.1.2 Files.readAllBytes()
Files.readAllBytes
是Java中的一个非常便利的方法,用于一次性读取整个文件的内容并返回一个字节数组。但要注意,由于它一次性加载整个文件内容到内存中,因此不适用于非常大的文件,因为这可能导致内存溢出。
下面是使用Files.readAllBytes
读取文件内容的示例:
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class FileReadingExample {
public static void main(String[] args) {
Path filePath = Path.of("your_file_path_here");
try {
byte[] fileBytes = Files.readAllBytes(filePath);
String fileContent = new String(fileBytes, "UTF-8"); // 假设文件采用UTF-8编码
System.out.println(fileContent);
} catch (IOException e) {
e.printStackTrace();
}
}
}
这段代码会将指定文件的所有字节读取到内存中,并将其作为字节数组返回。你可以根据需要将字节数组转换为字符串或以其他方式处理文件内容。但请注意,使用readAllBytes
对大文件不太合适,因为它可能导致内存问题。如果文件很大,你应该使用逐块读取的方法,以避免内存溢出问题。
4.1.3 Files.readAllLines()
Files.readAllLines()
是 Java 中的另一个便捷方法,用于一次性读取整个文本文件的所有行,并将它们存储在一个列表中。每行都存储为列表中的一个字符串元素。这个方法对于文本文件非常有用,但同样也需要注意文件大小以避免内存问题。
以下是使用 Files.readAllLines()
读取文本文件的示例:
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
public class FileReadingExample {
public static void main(String[] args) {
Path filePath = Path.of("your_text_file_path_here");
try {
List<String> lines = Files.readAllLines(filePath);
for (String line : lines) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
这段代码会将指定文本文件的所有行读取到一个列表中,每一行都是列表中的一个字符串。你可以遍历这个列表,处理每一行的内容。
请注意,Files.readAllLines()
也会一次性将整个文件读取到内存中,因此对于非常大的文本文件仍然存在内存问题。如果你需要处理大型文本文件,最好使用逐行读取的方法,以避免消耗过多内存。
4.1.4 Files.lines
Files.lines
是Java NIO (New I/O) 中的一个便捷方法,用于逐行读取文本文件的内容。它返回一个Stream<String>
,你可以使用它来处理文件的每一行,例如过滤、映射、打印等。
下面是一个使用Files.lines
来逐行读取文本文件并打印每行内容的示例:
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class FileReadingExample {
public static void main(String[] args) {
Path filePath = Path.of("your_text_file_path_here");
try {
Files.lines(filePath).forEach(System.out::println);
} catch (IOException e) {
e.printStackTrace();
}
}
}
上述代码会打开指定的文本文件,使用默认的字符编码逐行读取文件内容,并将每行内容打印到控制台。
Files.lines
是一种更适合处理大型文本文件的方法,因为它逐行读取,不需要一次性加载整个文件到内存中,从而减少内存消耗。
4.1.5 Scanner分解数字或文本及单词
Scanner
类从输入流或文件路径构造一个扫描器,然后使用hasNextDouble
方法来检查是否下一个标记是一个双精度浮点数(数字),或使用hasNext
方法来检查是否下一个标记是一个单词。下面是一个示例:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class ScannerExample {
public static void main(String[] args) {
ClassLoader classLoader = Demo0206.class.getClassLoader();
URL resource = classLoader.getResource("static/text.txt");
if (resource != null) {
Scanner scanner = new Scanner(Path.of(resource.toURI()));
while (scanner.hasNext()) {
if (scanner.hasNextDouble()) {
double number = scanner.nextDouble();
System.out.println("Found a number: " + number);
} else {
String word = scanner.next();
System.out.println("Found a word: " + word);
}
}
}
}
}
在上面的示例中,我们从文件路径构造了一个扫描器,然后使用hasNext
方法检查下一个标记的类型。如果下一个标记是双精度浮点数,我们使用nextDouble
方法来获取它。如果不是双精度浮点数,就假设它是一个单词,并使用next
方法获取它。请确保替换文件路径为你要处理的实际文件路径。
设置分隔符
如果你想通过Scanner
来分解单词,并将分隔符设置为任意非字母序列,你可以使用useDelimiter
方法来自定义分隔符。以下是一个示例:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class ScannerExample {
public static void main(String[] args) {
// 从文件路径构造一个扫描器
try {
Scanner scanner = new Scanner(new FileInputStream("input.txt")); // 替换为你的文件路径
// 设置分隔符为任意非字母序列
scanner.useDelimiter("[^a-zA-Z]+");
while (scanner.hasNext()) {
String word = scanner.next();
System.out.println("Found a word: " + word);
}
scanner.close(); // 记得关闭扫描器
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
在上面的示例中,我们使用useDelimiter("[^a-zA-Z]+")
来设置分隔符,该正则表达式会匹配任何非字母字符序列作为分隔符,从而将文本分解为单词。请确保替换文件路径为你要处理的实际文件路径。
这样设置分隔符后,Scanner
会将文本分解为单词,并忽略非字母字符作为分隔符。
4.2 写文本文件
4.2.1 PrintWriter
PrintWriter
以及Files.newBufferedWriter
来将文本写入文件,这是一种非常好的方式,特别是当需要指定字符编码时。这样的做法可以确保文本以指定的编码写入文件。
下面是一个示例,展示如何使用你提供的代码来写入文本到文件:
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.io.PrintWriter;
public class PrintWriterExample {
public static void main(String[] args) {
Path filePath = Path.of("output.txt"); // 指定要写入的文件路径
try (PrintWriter out = new PrintWriter(Files.newBufferedWriter(filePath, StandardCharsets.UTF_8))) {
out.println("Hello, World!");
out.println("This is a sample text.");
out.print("This is another line without a newline character.");
} catch (IOException e) {
e.printStackTrace();
}
}
}
在上述示例中,我们首先创建一个Path
对象,指定要写入的文件路径。然后,我们使用Files.newBufferedWriter
来创建一个BufferedWriter
,并将其传递给PrintWriter
的构造函数。接着,我们使用PrintWriter
来写入文本内容,最后在try-with-resources
块中关闭PrintWriter
,确保数据被刷新到文件中并关闭相关资源。
此示例将文本以UTF-8编码写入文件,你可以根据需要替换文件路径和要写入的内容。
4.2.2 Files.write()
字符串内容写入指定文件路径
Files.write
是Java NIO(New I/O)库中的方法,用于将数据写入文件。它提供了一种简单的方式来将字节数组、字符数组或文本行写入文件。以下是关于Files.write
方法的一些重要信息:
Files.write(Path path, byte[] bytes, OpenOption... options)
path
: 表示要写入的文件的路径的Path
对象。bytes
: 要写入文件的字节数组。options
: 用于配置写入操作的可选参数,例如指定是否追加、创建文件等。通常使用StandardOpenOption
中的常量,如StandardOpenOption.CREATE
、StandardOpenOption.APPEND
等。
示例使用Files.write
将字节数组写入文件:
import java.nio.file.*;
import java.io.IOException;
public class WriteBytesToFile {
public static void main(String[] args) {
Path filePath = Paths.get("output.txt");
byte[] data = "Hello, World!".getBytes();
try {
Files.write(filePath, data, StandardOpenOption.CREATE);
System.out.println("Data has been written to " + filePath);
} catch (IOException e) {
e.printStackTrace();
}
}
}
Files.write
还有其他重载方法,可用于写入字符、文本行等不同类型的数据。你可以根据需要选择合适的方法。
文本行写入文件
Files.write
方法将文本行写入文件,你需要将文本行存储在一个列表(通常是 List<String>
)中,然后将这个列表传递给 Files.write
方法。以下是一个示例,演示如何执行这个操作:
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
public class WriteTextLinesToFile {
public static void main(String[] args) {
Path filePath = Path.of("output.txt"); // 指定要写入的文件路径
// 创建文本行列表
List<String> lines = new ArrayList<>();
lines.add("Hello, World!");
lines.add("This is a sample text.");
lines.add("Another line of text.");
try {
// 使用指定字符编码(例如UTF-8)将文本行列表写入文件
Files.write(filePath, lines, StandardCharsets.UTF_8);
System.out.println("Data has been written to " + filePath);
} catch (IOException e) {
e.printStackTrace();
}
}
}
在上述示例中,我们创建了一个文本行的列表 lines
,然后使用 Files.write
方法将这个列表写入文件 output.txt
,并指定了字符编码为 UTF-8。请确保替换文件路径和文本行列表以适应你的具体需求。这个示例将文本行列表以 UTF-8 编码写入文件。
追加内容
// 指定了字符编码为 UTF-8 以及 StandardOpenOption.APPEND 选项,这样新内容会被添加到文件的末尾
Files.write(filePath, linesToAppend, StandardCharsets.UTF_8, StandardOpenOption.APPEND);
4.2.3 异常信息输出到文件
当你需要将异常信息输出到一个 Writer
对象时,你可以创建一个 PrintWriter
对象,然后将它传递给 Throwable.printStackTrace(PrintWriter out)
方法。以下是一个示例:
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
public class NestedPrintWriterExample {
public static void main(String[] args) {
try {
// 产生一个异常
int result = 5 / 0;
} catch (ArithmeticException e) {
try {
// 创建 PrintWriter 对象,将异常信息输出到文件
PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter("error.log")));
e.printStackTrace(writer);
writer.close();
System.out.println("Exception details have been written to error.log");
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
}
在这个示例中,我们首先制造了一个算术异常,然后捕获了这个异常。在异常处理块中,我们创建了一个 PrintWriter
对象,将异常信息输出到一个名为 "error.log" 的文件。最后,我们关闭 PrintWriter
对象。
这种方法允许你将异常信息输出到文件,以便进行调试和故障排除。确保替换文件路径和异常类型以适应你的具体需求。
5. 处理二进制数据
5.1 DataInput/DataOutput接口
DataInput
和 DataOutput
接口是 Java 中用于读写原始数据类型(如整数、浮点数等)的接口。它们通常与输入流和输出流一起使用,用于将数据从内存读取并写入到文件或其他数据源/目标。这些接口提供了一种跨平台的方式来进行数据序列化和反序列化。
- DataInput 接口:
DataInput
是一个接口,定义了读取原始数据类型的方法。- 主要用于从数据源(如文件或网络流)读取二进制数据,并将其解析为 Java 数据类型。
- 一些常见的方法包括
readByte()
、readShort()
、readInt()
、readLong()
、readFloat()
、readDouble()
等,用于读取不同数据类型。
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
public class DataInputExample {
public static void main(String[] args) throws IOException {
DataInput dataInput = new DataInputStream(new FileInputStream("data.bin"));
int intValue = dataInput.readInt();
double doubleValue = dataInput.readDouble();
System.out.println("Read values: " + intValue + ", " + doubleValue);
}
}
- DataOutput 接口:
DataOutput
也是一个接口,定义了写入原始数据类型的方法。- 主要用于将 Java 数据类型序列化为二进制数据,以便写入数据目标(如文件或网络流)。
- 一些常见的方法包括
writeByte()
、writeShort()
、writeInt()
、writeLong()
、writeFloat()
、writeDouble()
等,用于写入不同数据类型。
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class DataOutputExample {
public static void main(String[] args) throws IOException {
DataOutput dataOutput = new DataOutputStream(new FileOutputStream("data.bin"));
int intValue = 42;
double doubleValue = 3.14159;
dataOutput.writeInt(intValue);
dataOutput.writeDouble(doubleValue);
System.out.println("Data has been written.");
}
}
这些接口允许你以二进制格式读取和写入数据,非常适用于需要进行数据序列化和反序列化的应用程序,特别是涉及原始数据类型的情况。你可以使用不同的输入流和输出流来操作文件、网络连接等数据源和目标。
5.2 字符串以二进制格式写入文件并从中读取
DataOutputStream
的 writeUTF()
方法用于将字符串以UTF-8编码写入输出流,而 DataInputStream
的 readUTF()
方法用于从输入流中读取以UTF-8编码的字符串。这对方法是用于在Java中处理字符串的标准方式,特别适合将字符串以二进制格式写入文件或流,并从中读取。
以下是它们的使用示例:
import java.io.*;
public class TextToBinaryAndBack {
public static void main(String[] args) {
// 从文本文件读取内容并保存为二进制文件
try {
BufferedReader textFileReader = new BufferedReader(new FileReader("textData.txt"));
DataOutputStream binaryFileWriter = new DataOutputStream(new FileOutputStream("binaryData.dat"));
String line;
while ((line = textFileReader.readLine()) != null) {
// 将文本行写入二进制文件
binaryFileWriter.writeUTF(line);
}
textFileReader.close();
binaryFileWriter.close();
System.out.println("Text data has been converted to binary and saved to binaryData.dat.");
} catch (IOException e) {
e.printStackTrace();
}
// 从二进制文件读取内容并解析为文本文件
try {
DataInputStream binaryFileReader = new DataInputStream(new FileInputStream("binaryData.dat"));
BufferedWriter textFileWriter = new BufferedWriter(new FileWriter("restoredTextData.txt"));
while (binaryFileReader.available() > 0) {
// 从二进制文件读取并解析为文本
String line = binaryFileReader.readUTF();
// 将文本行写入文本文件
textFileWriter.write(line);
textFileWriter.newLine();
}
binaryFileReader.close();
textFileWriter.close();
System.out.println("Binary data has been restored to text and saved to restoredTextData.txt.");
} catch (IOException e) {
e.printStackTrace();
}
}
}
请注意,writeUTF()
和 readUTF()
方法是以UTF-8编码处理字符串的,因此它们适用于包含非ASCII字符的字符串。
6. 随机访问文件RandomAccessFile
RandomAccessFile
是 Java 中用于随机访问文件的类。它允许你在文件中执行读取和写入操作,不受文件顺序的限制,因此你可以自由地跳到文件的任何位置进行读写。以下是一些关于 RandomAccessFile
的重要信息和用法:
构造函数:
RandomAccessFile
有两个构造函数,一个用于读取(只读模式),另一个用于读写(读写模式)。RandomAccessFile file = new RandomAccessFile("filename.txt", "r"); // 只读模式 RandomAccessFile file = new RandomAccessFile("filename.txt", "rw"); // 读写模式
读取数据:你可以使用
read
方法来从文件中读取数据,例如字节、字符或其他基本数据类型。int data = file.readInt(); // 读取一个整数
写入数据:你可以使用
write
方法来将数据写入文件,例如字节、字符或其他基本数据类型。file.writeUTF("Hello, World!"); // 写入一个UTF-8字符串
随机访问:使用
seek
方法来移动文件指针到指定位置,然后进行读写操作。file.seek(50); // 将文件指针移动到位置50
文件长度:使用
length
方法来获取文件的长度,可以用于确定文件末尾的位置。long fileLength = file.length();
关闭文件:使用
close
方法来关闭文件,确保在不再需要访问文件时关闭它。file.close();
异常处理:由于文件操作可能会引发异常,因此在使用
RandomAccessFile
时,需要进行适当的异常处理,例如捕获IOException
。
以下是一个Java示例,演示如何使用RandomAccessFile
来随机访问文件并进行读写操作:
import java.io.RandomAccessFile;
public class RandomAccessFileExample {
public static void main(String[] args) {
try {
// 创建一个RandomAccessFile对象以读写模式
String filenameString = "sample.txt";
RandomAccessFile file = new RandomAccessFile(filenameString, "rw");
// 写入数据到文件
file.writeUTF("Hello, World!"); // 写入字符串
// 随机访问文件位置并写入数据
file.seek(12); // 将文件指针移动到位置12
file.writeUTF(" RandomAccess"); // 写入部分字符串
// 读取数据
file.seek(0); // 将文件指针移动到文件开头
String data = file.readUTF(); // 读取字符串
System.out.println("Read data: " + data);
// 关闭文件
file.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
在这个示例中,我们首先创建一个 RandomAccessFile
对象,打开名为 "sample.txt" 的文件以读写模式。然后,我们使用 writeUTF
方法向文件写入字符串 "Hello, World!"。接下来,我们使用 seek
方法将文件指针移动到位置12,并继续写入部分字符串 " RandomAccess"。
最后,我们使用 seek
将文件指针移动到文件开头,再次读取整个字符串并输出。这演示了RandomAccessFile
的随机访问和读写能力。
7. 内存映射文件
Java 内存映射文件(Memory-Mapped Files)是一种高效的文件处理技术,特别适用于需要快速访问大型文件的情况。通过内存映射文件,你可以将文件的一部分或整个文件映射到内存中,从而可以像操作内存一样高效地读取和写入文件数据。这对于处理大型数据文件、数据库文件或日志文件非常有用。
以下是使用 Java 内存映射文件的一般步骤:
打开文件:首先,你需要使用标准的文件 I/O 操作(如
FileInputStream
或FileOutputStream
)打开文件。创建内存映射:接下来,你可以使用
FileChannel
的map
方法来创建内存映射。这将文件的一部分或整个文件映射到内存中。FileChannel fileChannel = FileChannel.open(Paths.get("largefile.dat"), StandardOpenOption.READ, StandardOpenOption.WRITE); MappedByteBuffer mappedBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, fileChannel.size());
在这个示例中,我们创建了一个内存映射,将整个文件映射到内存中,并获得一个
MappedByteBuffer
对象,你可以通过它来读写文件数据。读取和写入数据:现在,你可以像操作普通的
ByteBuffer
一样,使用MappedByteBuffer
来读取和写入数据。由于数据已经在内存中,因此操作非常高效。mappedBuffer.putInt(42); // 写入一个整数 int data = mappedBuffer.getInt(); // 读取一个整数
关闭内存映射和文件通道:最后,不要忘记在不再需要时关闭内存映射和文件通道。
fileChannel.close();
Java 内存映射文件提供了一种高效的处理大型文件的方式,它利用了操作系统的虚拟内存管理机制,将文件的内容直接映射到物理内存中,从而可以实现高性能的文件访问。但需要小心处理,因为如果映射的文件太大,可能会导致内存占用过高。内存映射文件通常适用于需要频繁随机访问文件的应用程序。
8. 如何选择流
在Java中,有多种方法可以进行文件操作,包括使用缓冲流、内存映射文件和随机访问文件。选择哪种方法取决于你的具体需求和文件操作类型。以下是一些指导原则,可以帮助你选择适当的文件访问方式:
- 缓冲流 (BufferedStream):
- 使用缓冲流(如
BufferedReader
和BufferedWriter
)适用于顺序读写文件的情况,特别是文本文件。它们可以提高IO操作的性能,减少文件读写的次数。 - 适用于大多数文本文件和序列化对象。
- 使用缓冲流(如
- 内存映射文件 (Memory-Mapped File):
- 内存映射文件适用于需要频繁随机访问大型文件的情况,以提高读写性能。这对于处理二进制文件、数据库文件等非常有用。
- 内存映射文件通过
FileChannel
和MappedByteBuffer
实现。
- 随机访问文件 (RandomAccessFile):
- 随机访问文件适用于需要随机访问文件内容的情况,可以读写文件的任意部分,而不必按顺序读取。
RandomAccessFile
类提供了这种功能,可以用于读取和写入二进制文件。
根据上述原则,你可以选择以下情景:
- 如果你需要逐行读取文本文件,可以使用
BufferedReader
。 - 如果你需要在内存中处理大型二进制文件,可以使用内存映射文件。
- 如果你需要在文件中执行随机读写操作,可以使用
RandomAccessFile
。
此外,需要根据你的具体需求来考虑文件操作的性能和复杂性,以选择最适合的文件访问方法。不同的文件访问方式具有不同的优势和限制,所以选择取决于你的应用程序要解决的问题。
9. 创建、访问、删除文件和目录
Java中提供了两种主要的文件操作方式:传统IO (java.io) 和 新IO (NIO,java.nio)。这两种方式在处理文件和IO操作时有不同的特点和用途:
1. 传统IO (java.io):
- 基于字节流和字符流的概念。
- 主要使用
InputStream
和OutputStream
进行字节流的操作,以及Reader
和Writer
进行字符流的操作。 - 适用于处理小文件和文本文件。
- 提供了高级文件操作,如读写文本文件、序列化、压缩等。
- 在处理大文件或需要非阻塞IO的情况下性能有限。
2. 新IO (java.nio):
- 基于通道(Channel)和缓冲区(Buffer)的概念。
- 使用
FileChannel
进行文件读写操作,数据通过缓冲区来传递。 - 适用于高性能IO操作,特别是需要处理大文件和网络通信的情况。
- 支持非阻塞IO (NIO2),可用于构建高性能服务器和网络应用程序。
- 具有选择器 (Selector) 和多路复用的功能,支持同时管理多个通道。
下面是一些主要的区别和用途:
- 传统IO适合处理小文件、文本文件和简单的文件操作。它的接口更容易使用,但在处理大型文件和高并发IO时性能较差。
- 新IO(NIO)适合需要高性能和非阻塞IO的应用程序。它提供了更强大的控制和更高的性能,但使用起来可能会更复杂。
- 传统IO使用流(Stream)的概念,而新IO使用通道(Channel)和缓冲区(Buffer)的概念,这使得新IO更适用于底层文件和网络操作。
- 新IO提供了非阻塞IO的支持,允许应用程序同时处理多个通道的IO操作。这对于构建高性能服务器和网络应用程序非常有用。
在选择使用哪种IO方式时,你应该考虑你的应用程序需求和性能目标。如果你需要处理小型文件或简单的文件操作,传统IO可能足够了。如果你需要高性能、大文件处理或网络通信,新IO可能更适合。新IO也是构建现代服务器应用程序的首选方法之一。
9.1 Paths辅助类
Paths
是一个工具类,用于辅助处理文件系统路径。它是 Java NIO(New Input/Output)文件系统 API 的一部分,提供了静态方法来方便地创建 Path
对象。Paths
类定义在 java.nio.file
包中。
关键特点和用途
- 路径创建:
Paths
类的主要用途是创建Path
对象。它提供了简单的工厂方法,使得创建路径变得更容易。 - 不可实例化: 由于
Paths
是一个纯工具类,它的构造函数是私有的,因此不能被实例化。
常用方法
get(String first, String... more)
: 这是Paths
类中最重要的方法,用于将字符串转换为Path
对象。first
参数是路径的起始部分,more
参数是一个可变长度的参数,可以用于指定路径的其他部分。这个方法会根据提供的字符串自动处理文件分隔符,生成适用于当前操作系统的路径。示例代码:
Path path = Paths.get("/home", "user", "documents", "file.txt");
使用场景
- 路径创建: 在需要表示文件系统中的路径时,通常会使用
Paths.get()
方法来快速创建Path
对象。 - 与
Files
类配合使用: 创建的Path
对象通常与Files
类一起使用,以执行文件系统操作,如文件读写、属性检查等。
9.2 Path接口
Path
类是一个接口,它位于 java.nio.file
包中。Path
接口是 Java New I/O (NIO) 文件系统 API 的一部分,用于表示文件系统中的路径。这个接口允许以一种与平台无关的方式来操作文件和目录的路径。
关键特点和用途
- 表示文件和目录路径:
Path
可以表示一个文件或目录的路径,这可以是绝对路径也可以是相对路径。
- 路径操作:
Path
提供了各种操作路径的方法,如获取路径的各个部分、解析路径、比较路径、转换路径等。
- 与
Files
类的交互:Path
对象通常与java.nio.file.Files
类一起使用,以执行文件操作,如检查、读取、写入和删除文件。
- 路径遍历和处理:
Path
提供了遍历文件系统和处理路径的方法,例如遍历文件树或定位文件。
常用方法
getFileName()
: 返回路径的文件名部分。getParent()
: 返回路径的父路径。getRoot()
: 返回路径的根组件。isAbsolute()
: 判断路径是否是绝对路径。normalize()
: 规范化路径,移除冗余的名称元素。resolve(Path other)
: 将给定的路径附加到当前路径。resolveSibling(Path other)
: 解析当前路径的兄弟路径。relativize(Path other)
: 构造当前路径与指定路径的相对路径。toAbsolutePath()
: 将路径转换为绝对路径。toFile()
: 将Path
转换为java.io.File
对象。toString()
: 返回路径的字符串表示形式。
获取 Path
对象
要获取 Path
对象,通常使用 java.nio.file.Paths
类的静态方法 get()
。例如:
Path path = Paths.get("/some/path/to/a/file.txt");
9.3 Files类
Files
类是 java.nio.file
包的一部分,提供了一系列静态方法用于操作文件和目录。Files
类是 Java New I/O (NIO) 文件系统 API 的核心组成部分,它允许进行高效的文件操作。
关键特点和用途
- 文件和目录操作:
Files
类提供了创建、复制、移动、修改和删除文件和目录的方法。
- 文件属性访问:
- 通过
Files
类,可以读取和设置文件的属性,如大小、时间戳、权限等。
- 通过
- 文件读写:
- 提供了读取和写入文件的方法,支持多种格式和数据类型。
- 目录遍历:
- 支持遍历目录中的文件和子目录。
- 文件检查和测试:
- 提供了检查文件是否存在、是否可读写、是否为目录或文件等方法。
常用方法
createFile(Path path)
: 创建一个新的空文件。createDirectory(Path dir)
: 创建一个目录。copy(Path source, Path target, CopyOption... options)
: 复制文件或目录。move(Path source, Path target, CopyOption... options)
: 移动或重命名文件或目录。delete(Path path)
: 删除文件或目录。exists(Path path, LinkOption... options)
: 检查文件是否存在。isReadable(Path path)
: 检查文件是否可读。isWritable(Path path)
: 检查文件是否可写。size(Path path)
: 返回文件的大小。newBufferedReader(Path path)
: 创建一个用于读取文件的BufferedReader
。newBufferedWriter(Path path, OpenOption... options)
: 创建一个用于写入文件的BufferedWriter
。readAllBytes(Path path)
: 读取文件的所有字节到一个字节数组中。readAllLines(Path path)
: 读取文件的所有行到一个字符串列表中。write(Path path, byte[] bytes, OpenOption... options)
: 将字节数组写入文件。list(Path dir)
: 列出目录中的所有项。
10. 处理互联网上的数据
10.1 HttpURLConnection
HttpURLConnection
类是 Java 的一个核心类,用于处理 HTTP 请求。它是 java.net
包的一部分,提供了一种简单的方式来发送和接收 HTTP 类型的数据。这个类允许应用程序通过 HTTP 协议发送请求和读取响应。
关键特点
- 支持 HTTP 方法:
- 支持标准的 HTTP 方法,如 GET、POST、HEAD、OPTIONS、PUT、DELETE 等。
- 灵活的请求头管理:
- 允许设置和修改 HTTP 请求头。
- 读取响应:
- 可以读取从服务器返回的响应数据,包括状态码、响应头和响应体。
- 连接管理:
- 管理网络连接,包括连接超时和读取超时设置。
- 错误处理:
- 可以捕获和处理服务器返回的错误响应。
常用方法
setRequestMethod(String method)
: 设置 HTTP 请求的方法类型(如 "GET" 或 "POST")。setRequestProperty(String key, String value)
: 添加一个请求头。connect()
: 打开到资源的连接。getResponseCode()
: 获取 HTTP 响应代码。getInputStream()
: 读取响应数据。getOutputStream()
: 用于写入请求体,例如在 POST 请求中。disconnect()
: 关闭连接。setConnectTimeout(int timeout)
: 设置连接超时时间。setReadTimeout(int timeout)
: 设置读取超时时间。
使用示例
以下是一个使用 HttpURLConnection
发送 GET 请求的基本示例:
URL url = new URL("http://example.com");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
int status = con.getResponseCode();
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuilder content = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();
con.disconnect();
System.out.println(content.toString());
注意事项
- 异常处理:
- 网络操作可能会抛出异常,例如
IOException
,因此需要进行适当的异常处理。
- 网络操作可能会抛出异常,例如
- 资源管理:
- 确保在使用完连接后关闭它,以释放网络资源。
- 线程安全:
HttpURLConnection
实例不是线程安全的。每个线程应该使用自己的HttpURLConnection
实例。
10.2 HttpClient
从 Java 11 开始,Java 引入了一个新的 HTTP 客户端 API - HttpClient
,它位于 java.net.http
包中。这个新的 HTTP 客户端提供了一个现代化的方式来发送请求和接收响应,同时支持 HTTP/1.1 和 HTTP/2。它旨在替代旧的 HttpURLConnection
类,并提供更多的功能和更好的性能。
关键特点
- HTTP/2 支持:
- 默认支持 HTTP/2,同时兼容 HTTP/1.1。
- 异步和同步模式:
- 提供了异步和同步两种方式发送请求。
- 易于使用的构建器模式:
- 使用构建器模式创建请求和客户端,使代码更清晰易读。
- WebSocket 支持:
- 内置对 WebSocket 的支持,允许创建 WebSocket 客户端。
主要组件
HttpClient
:- 用于发送请求和接收响应的客户端。它可以被配置为处理不同类型的请求。
HttpRequest
:- 表示一个 HTTP 请求。使用
HttpRequest.Builder
来构建请求。
- 表示一个 HTTP 请求。使用
HttpResponse
:- 表示一个 HTTP 响应。包含状态码、响应头和响应体。
常用方法和类
HttpClient.newBuilder()
: 创建一个HttpClient
的构建器。HttpClient.send()
: 同步发送一个请求,返回HttpResponse
。HttpClient.sendAsync()
: 异步发送一个请求,返回CompletableFuture<HttpResponse>
。HttpRequest.newBuilder()
: 创建一个HttpRequest
的构建器。HttpRequest.Builder.uri(URI uri)
: 设置请求的 URI。HttpRequest.Builder.GET()
,POST(BodyPublisher body)
,PUT(BodyPublisher body)
等: 设置 HTTP 方法和请求体。HttpResponse.body()
: 获取响应体。
使用示例
以下是使用 HttpClient
发送 GET 请求的基本示例:
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://example.com"))
.GET()
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
11. 正则表达式
Java 中的正则表达式提供了一种强大的方式来匹配、搜索和操作文本。正则表达式(Regular Expressions,简称 Regex)是一种特定的字符串模式,用于匹配一系列符合某个句法规则的字符串。Java 通过 java.util.regex
包中的类来支持正则表达式。
关键类
Pattern
:- 代表编译后的正则表达式。用
Pattern.compile(String regex)
方法来创建。 - 提供了多种标志(如
CASE_INSENSITIVE
、MULTILINE
等)来改变匹配行为。
- 代表编译后的正则表达式。用
Matcher
:- 用于执行匹配操作的引擎。
- 通过
Pattern
对象的matcher(CharSequence input)
方法创建。 - 提供了各种方法进行匹配、查找、替换等操作。
常用方法
Pattern.compile(String regex)
: 将给定的正则表达式编译成Pattern
对象。Matcher.find()
: 尝试在目标字符串中查找与模式匹配的下一个子序列。Matcher.matches()
: 尝试将整个区域与模式匹配。Matcher.group()
: 返回由上一个匹配操作所匹配的输入子序列。Pattern.matches(String regex, CharSequence input)
: 作为一个快捷方式,用于检查整个字符串是否匹配给定的正则表达式。
正则表达式语法
Java 正则表达式的语法丰富且灵活,以下是一些基本元素:
- 字符类:
[abc]
: 匹配任何一个列在方括号中的字符(a、b 或 c)。[^abc]
: 匹配任何不在方括号中的字符。[a-zA-Z]
: 匹配任何一个字母。
- 预定义字符类:
.
: 匹配任何字符(可能不匹配换行符)。\d
: 匹配任何数字,等价于[0-9]
。\D
:匹配非数字。\s
: 匹配任何空白字符,包括空格、制表符、换行符等。\S
:匹配非空白字符。\w
: 匹配任何字母数字字符,等价于[a-zA-Z_0-9]
。\W
:匹配非字母、数字、下划线。
- 量词:
*
: 零次或多次。+
: 一次或多次。?
: 零次或一次。{n}
: 正好 n 次。{n,}
: 至少 n 次。{n,m}
: 至少 n 次,但不超过 m 次。
- 边界匹配器:
^
: 行的开头。$
: 行的结尾。\b
: 单词边界。
使用示例
以下是一个简单的示例,展示如何使用正则表达式:
String text = "abc123xyz";
String patternString = "\\d+"; // 正则表达式,匹配一个或多个数字
Pattern pattern = Pattern.compile(patternString);
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
System.out.println("Found: " + matcher.group());
}
12. 理解序列化
Java 序列化是一种机制,用于将 Java 对象的状态转换成字节流,从而可以将其存储在文件中或通过网络传输。相对的,反序列化是将这些字节流重新构造成原始对象的过程。这个机制在 Java 中主要由 java.io.Serializable
接口和相关的 I/O 类实现。
实现序列化
为了使一个 Java 类可序列化,你需要做的最少工作是实现 java.io.Serializable
接口。这个接口是一个标记接口,没有方法需要实现:
public class MyObject implements Serializable {
// 类的成员变量和方法
}
序列化过程
使用 ObjectOutputStream
类来序列化一个对象。这个类将对象写入到一个输出流,这个输出流可以是文件输出流,也可以是其他类型的输出流,如网络连接的输出流。
// 序列化对象
try (ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("person.ser"))) {
Person person = new Person("Alice", 30);
outputStream.writeObject(person);
System.out.println("Object serialized successfully.");
} catch (IOException e) {
e.printStackTrace();
}
反序列化过程
反序列化是使用 ObjectInputStream
类来实现的。这个类从输入流中读取字节流,并将其转换回原始对象。
// 反序列化对象
try (ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("person.ser"))) {
Person restoredPerson = (Person) inputStream.readObject();
System.out.println("Object deserialized successfully.");
System.out.println(restoredPerson);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
注意事项
- 序列化 ID (
serialVersionUID
):- 每个可序列化的类都建议有一个名为
serialVersionUID
的静态常量(如果没有显式定义,Java 运行时会根据类的细节自动生成一个)。 - 这个 ID 用于确保序列化和反序列化过程中类的版本兼容性。
- 每个可序列化的类都建议有一个名为
- 不可序列化的对象:
- 如果类中包含不可序列化的对象,那么在序列化过程中会抛出
NotSerializableException
。
- 如果类中包含不可序列化的对象,那么在序列化过程中会抛出
- 静态和瞬态字段:
- 静态字段(类的字段)不会被序列化。
- 使用
transient
关键字标记的字段也不会被序列化。
- 安全性考虑:
- 序列化可以用于持久化数据,但也可能导致安全性问题。因此,在处理来自不可信源的序列化数据时要特别小心。
- 性能考虑:
- 序列化和反序列化是一个相对昂贵的操作,可能影响性能。
应用场景
- 对象持久化:将对象保存到磁盘,以便以后可以重新构造这些对象。
- 远程方法调用(RMI):在远程通信中,对象需要被序列化以便于传输。
- 分布式对象:在分布式系统中,对象需要在网络上进行传输。