每日一言
All of life’s journeys come with meetings, partings, and reunions. — Meryl Stryfe from Trigun
控制台input
BufferedReader & System.in
System.in 是java中的一个静态成员变量,属于 java.lang.System 类,它是一个 InputStream,绑定到键盘,直接使用它进行读取有许多不方便之处:
- 它只能以字节为单位读取数据。
- 读取数据需要手动处理字节到字符的转换,以及处理不同数据类型(例如整数、浮点数等)。
- 中文字符通常占用多个字节,如果一次读取的字节数不足以包含一个完整的中文字符,则需要多次读取并将结果拼接起来。
- 需要手动处理异常
import java.io.IOException;
public class PrintSystemIn {
public static void main(String[] args) {
try {
System.out.println("Enter a character:");
int byteRead = System.in.read();
if (byteRead != -1) {
System.out.println("You entered: " + (char) byteRead);
}
} catch (IOException e) {
System.err.println("Error reading input: " + e.getMessage());
}
}
}

所以,直接通过System.in来进行程序输入是极为不方便的。
所以我们通常将System.in包装在一个InputStreamReader类中,将读取的字节流转换为字符流。在java中,char类型占用两个字节的空间,对于文本数据,我们可以通过InputStreamReader类来将原始的字节流合并为字符流,将两个字节的内容合并的一个char类型数据。
再将整体包装在一个BuffereredReader中,实现缓冲读取字节流转化为字符流的过程。
需要引用以下头文件:
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.*; //可以用这一个代替上面三个
创建方式如下:
BufferedReader br = new BufferedReader(new
InputStreamReader(System.in));
BufferedReader 对象创建后,我们便可以使用 read() 方法从控制台读取一个字符,或者用 readLine() 方法读取一个字符串。
但是在使用BufferedReader进行读取之前,我们还需要了解一点东西。如,我们使用如下的方式进行输入时:
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class BfReader {
public static void main(String[] args) {
System.out.println("请输入一个字符:");
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
char c = (char)br.read();
System.out.println("您输入的字符是:" +c);
}
}
编译器会进行如下报错:

Unhandled exception type IOException:未处理的 IOException 异常类型
BufferedReader.read()方法会抛出一个异常 IOException,因此我们需要使用 try-catch 来进行异常处理,这也就是我们之前引用的第三个头文件。
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
public class BfReader {
public static void main(String[] args) {
System.out.println("请输入一个字符:");
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
try {
char c = (char) br.read();
System.out.println("您输入的字符是:" + c);
} catch (IOException e) {
System.err.println("读取字符时发生错误: " + e.getMessage());
}
}
}
此时就能够正常的读取并打印中文了:

稍作修改就可以改为读取字符串:
注意,read()返回的是一个char类型,readline()返回的是一个String类型。
public class BfReader {
public static void main(String[] args) throws IOException {
System.out.println("请输入字符串:");
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String str = br.readLine();
System.out.println("您输入的字符是:" + str);
}
}

此处我们展示了第二种异常处理的方式:这种异常处理方式并不直接在main()的内部进行异常处理,而是将异常处理交给更高层,让代码看起来更加简洁,关于异常处理的详细内容,我们后续再来详细了解。
读取int、float、double类型数据
通过BufferedReader读取int、float、double类型需要借助三个java静态函数,分别是:
int intValue = Integer.parseInt(bf.readLine());
float floatValue = Float.parseFloat(bf.readLine());
double doubleValue = Double.parseDouble(bf.readLine());
这三个函数的参数传入参数都是String类型,所以一定要通过 readLine()方法进行读取,读取的字符串形式一定为一个标准的整数、小数。否则会触发 NumberFormatException的异常。
Xxx.parseXxx()函数会将读取的字符串形式的数据转化为对应的数据类型返回。
读取代码示例如下:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class ReadInput {
public static void main(String[] args) {
BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
try {
System.out.println("请输入一个字符串:");
String text = bf.readLine();
System.out.println("你输入的字符串是: " + text);
System.out.println("请输入一个整数:");
int intValue = Integer.parseInt(bf.readLine());
System.out.println("你输入的整数是: " + intValue);
System.out.println("请输入一个浮点数:");
float floatValue = Float.parseFloat(bf.readLine());
System.out.println("你输入的浮点数是: " + floatValue);
System.out.println("请输入一个双精度浮点数:");
double doubleValue = Double.parseDouble(bf.readLine());
System.out.println("你输入的双精度浮点数是: " + doubleValue);
} catch (IOException e) {
System.err.println("读取输入时发生错误: " + e.getMessage());
} catch (NumberFormatException e) {
System.err.println("输入的数字格式不正确: " + e.getMessage());
}
}
}
这种读取方式只允许我们一行输入一个数据,比较繁琐。

Scanner类
java.util.Scanner 是 Java5 的新特征,我们可以通过 Scanner 类来获取用户的输入。
引头文件
import java.util.Scanner;
创建方法:
Scanner s = new Scanner(System.in);
next()读取
在使用next读取前,我们通常使用hasNext()方法来检测是否还有内容未读取。
next()方法会以空格,回车等为分割符,一次只会读取一个单词,hasNext()方法会检测是否还有未读的单词,直到读入EOF返回false。
import java.util.Scanner;
public class ScannerDemo {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
// 从键盘接收数据
// next方式接收字符串
System.out.println("next方式接收:");
// 判断是否还有输入
while (scan.hasNext()) {
String str1 = scan.next();
System.out.println("输入的数据为:" + str1);
}
System.out.println("next方式接收结束");
scan.close();
}
}

可以通过按 Ctrl + Z 然后按 Enter 键来发送 EOF(End Of File)信号,从而结束输入。
nextLine()读取
在使用nextLine读取前,我们通常使用hasNextLine()方法来检测是否还有内容未读取。
nextLine()方法将以换行符为分割符,一次读取一行的内容。hasNextLine()方法会检测是否还有未读的行,直到读入EOF返回false。
import java.util.Scanner;
public class ScannerDemo {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
// 从键盘接收数据
// nextLine方式接收字符串
System.out.println("nextLine方式接收:");
// 判断是否还有输入
while (scan.hasNextLine()) {
String str1 = scan.nextLine();
System.out.println("输入的数据为:" + str1);
}
System.out.println("nextLine方式接收结束");
scan.close();
}
}

读取int、float、double类型数据
Scanner类同样对于int、float、double类型的数据提供了支持。但是在读取之前同样建议通过函数 hasNextXxx()进行验证,再通过nextXxx()来读取。
读取示例如下:
import java.util.Scanner;
public class ReadInput {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
try {
System.out.println("请输入一个字符串:");
if (scanner.hasNextLine()) {
String text = scanner.nextLine();
System.out.println("你输入的字符串是: " + text);
}
else {
System.out.println("你没有输入任何字符串");
}
System.out.println("请输入一个整数、一个浮点数、一个双精度浮点数:");
if (scanner.hasNextInt()) {
int intValue = scanner.nextInt();
System.out.println("你输入的整数是: " + intValue);
}
else {
System.out.println("输入的不是整数!");
}
if(scanner.hasNextFloat()) {
float floatValue = scanner.nextFloat();
System.out.println("你输入的浮点数是: " + floatValue);
}
else {
System.out.println("输入的不是浮点数!");
}
if(scanner.hasNextDouble()) {
double doubleValue = scanner.nextDouble();
System.out.println("你输入的双精度浮点数是: " + doubleValue);
}
else {
System.out.println("输入的不是双精度浮点数!");
}
} catch (Exception e) {
System.err.println("读取输入时发生错误: " + e.getMessage());
} finally {
scanner.close();
}
}
}

这样我们就无需对于每一行的数据格式进行严格要求,只要通过空格将我们想要输入的数据隔开就好。
控制台output
java中的控制台输出,通过 System.out对象来实现。System.out 是 java.io.PrintStream的一个指向控制台的实例。
我们这里先介绍三种标准的输出函数
System.out.print(data)
作用:将指定的数据输出到控制台, 不换行 。
public class PrintExample {
public static void main(String[] args) {
System.out.print("Hello, ");
System.out.print("World!");
System.out.print(123);
System.out.print(3.14);
}
}
运行结果:

System.out.println(data)
作用:将指定的数据输出到控制台,每次输出换行。
public class PrintlnExample {
public static void main(String[] args) {
System.out.println("Hello, ");
System.out.println("World!");
System.out.println(123);
System.out.println(3.14);
}
}
运行结果:

对于以上两函数的data可以是:
- int,long,double等数字类型
- char类型
- String类型
- boolean类型
也可以是:
- 对象(类的实例)
第一种情况输出的内容就是变量的值。对于boolean类型输出的就是 true或 false。
我们详细讲解一下当传入参数为对象时,程序会输出什么。
首先我们写这样一个简单的程序:
public class PrintlnExample {
public static void main(String[] args) {
MyClass mc1 = new MyClass(10);
System.out.println(mc1);
}
}
class MyClass{
int a;
String str = "It's my class, a = " + a;
public MyClass(int x){
a = x;
}
}
我们创建了一个对象,并且用println打印它。
运行结果:

打印的结果为:类名+ @ + 对象的哈希码的十六进制表示
好,现在又出现了一个陌生的名词:“对象的哈希码的十六进制表示”。我们先暂时忽视这一概念的内部实现,只需知道该值是根据对象的内部状态计算出来的。
现在我们给MyClass类添加一个名为:toString()的方法
public class PrintlnExample {
public static void main(String[] args) {
MyClass mc1 = new MyClass(10);
System.out.println(mc1);
}
}
class MyClass{
int a;
String str = "It's my class, a = " + a;
public MyClass(int x){
a = x;
}
public String toString(){
return str;
}
}
运行结果:

打印结果变为了我们编写的toString()函数的返回值。
由此,我们可以总结打印对象的过程如下:
- 检查对象的toString()方法
System.out.println()会首先检查传入的对象是否定义了toString()方法。- 如果对象所属的类重写了
toString()方法,那么System.out.println()将调用这个重写后的toString()方法,并打印该方法返回的字符串。 - 如果对象所属的类没有重写
toString()方法,那么System.out.println()将调用该对象从java.lang.Object类继承来的默认toString()方法。
- 默认
toString()方法:
java.lang.Object类提供的默认toString()方法会返回一个字符串,该字符串包含以下信息:- 对象的类名(包括包名,如果存在)。
@符号。- 对象的哈希码的十六进制表示。
- 例如,默认的输出格式可能类似于
com.example.MyClass@1a2b3c4d。
System.err.println()
System.err.println 用于将文本输出到标准错误流(standard error stream)的方法,它与System.out.println的及其相似,但是输出目标不同。
该方法主要目的是将error信息与正常output的输出进行区分,当他们可以分别被重定向到不同位置。
try {
} catch (Exception e) {
System.err.println("错误信息");
}
对于文件的input和output
控制台的输入输出只涉及到字符流的输入输出,但是对于文件的输入输出,可能涉及字符流和字节流两种情况。
字符流的文件输入
字符流的文件输入我们依然可以使用控制台input中的两种方法,只需要将System.in改为对应的文件即可。
假设在如下路径下的文件
D:\windows\desktop\text.txt
用Scanner读入:
ring FilePath = "D:\windows\desktop\text.txt"
File file = new File(FliePath);
Scanner sc = new Scanner(file);
BufferedReader 读入:
BufferedReader bf = new BufferedReader(new InputStreamReader(new FileInputStream(filePath)));
与Scanner直接将 System.in替换为 File 对象不同,BufferedReader需要将 System.in替换为 FileInputStream对象。
或者可以直接使用:
BufferedReader br = new BufferedReader(new java.io.FileReader(filePath))
差距对比:
| 特性 | BufferedReader(new FileReader(filePath)) | BufferedReader(new InputStreamReader(new FileInputStream(filePath))) |
|---|---|---|
| 底层输入流 | FileReader | FileInputStream |
| 字符编码处理 | 使用系统默认编码 | 需要显式指定编码 |
| 编码问题 | 可能出现乱码 | 可以通过指定编码避免乱码 |
| 代码复杂度 | 简单 | 稍复杂 |
| 性能 | 相对较低 | 相对较高 |
| 适用场景 | 读取文本文件,简单场景 | 读取文本文件,需要控制编码,性能要求较高场景 |
字符流的文件输出
字符流的文件输出我们可以通过对于System.out的重定向来实现。
引入头文件:
import java.io.FileOutputStream;
import java.io.PrintStream;
-
创建一个FileOutputStream类,指定输出文件
FileOutputStream fos = new FileOutputStream("Output.txt"); -
创建PrintStream,将FileOutputStream包裹
PrintStream ps = new PrintStream(fos); -
使用System.setOut(),将字符流输出重定向到文件
System.setOut(ps);
之后就可以使用System.out.print等函数进行文件输出。
针对System.err的重定向则使用方法:System.setErr()
-
使用完毕后关闭文件流:
ps.close fos.close
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
public class RedirectSystemOutToFile {
public static void main(String[] args){
try {
// 1. 创建 FileOutputStream,指定输出文件
FileOutputStream fos = new FileOutputStream("output.txt");
// 2. 创建 PrintStream,将 FileOutputStream 包裹
PrintStream ps = new PrintStream(fos);
// 3. 使用 System.setOut() 重定向 System.out
System.setOut(ps);
System.out.println("成功定向到文件!! 恭喜你!!");
// 4. 关闭 PrintStream
ps.close();
fos.close();
} catch (FileNotFoundException e) {
System.err.println("Error creating output file: " + e.getMessage());
} catch (Exception e) {
System.err.println("Error closing stream: " + e.getMessage());
}
}
}
恢复控制台输出
如果在文件输出结束后,你想要恢复文件输出,则需要在更改为文件输出之前,保存原本的输出流
PrintStream originalOut = System.out; // 保存原始的 System.out
恢复:
System.setOut(originalOut); // 恢复 System.out 到控制台
将你从控制台输入的字符转存到文件保存起来
import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.util.Scanner;
public class RecordYourInput {
public static void main(String[] args){
try {
File file = new File("input.txt");
Scanner sc = new Scanner(System.in);
PrintStream os = System.out;
FileOutputStream fos = new FileOutputStream(file);
PrintStream ps = new PrintStream(fos);
System.setOut(ps);
while(sc.hasNextLine()){
String input = sc.nextLine();
System.out.println(input);
if(input.equals("exit")){
break;
}
}
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
}
}
}
我们运行程序输入如下字符:

可以看到工作目录下生成input.txt,内容如下:

字节流的文件输入
对于很多文件,如图片、视频等文件,他们的内容不是以字符编码进行存放,而是以字节为单位进行读取,对于这些文件,我们就不能像读取 .txt文件一样通过字符流读取他们,而是需要通过字节流的方式进行读取。
字节流的input通过 FileInputStream类来实现,创建一个字节输入流:
File f = new File("C:/java/hello");
InputStream in = new FileInputStream(f);
| 方法 | 描述 | 示例代码 |
|---|---|---|
int read() | 读取一个字节的数据,返回值为 0 到 255 之间的整数。如果到达流的末尾,返回 -1。 | int data = in.read(); |
int read(byte[] b) | 从输入流中读取字节,并将其存储在字节数组 b 中,返回实际读取的字节数。如果到达流的末尾,返回 -1。 | byte[] buffer = new byte[1024]; int bytesRead = in.read(buffer); |
int read(byte[] b, int off, int len) | 从输入流中读取最多 len 个字节,并将它们存储在字节数组 b 的 off 偏移位置,返回实际读取的字节数。如果到达流的末尾,返回 -1。 | byte[] buffer = new byte[1024]; int bytesRead = in.read(buffer, 0, buffer.length); |
long skip(long n) | 跳过并丢弃输入流中的 n 个字节,返回实际跳过的字节数。 | long skippedBytes = in.skip(100); |
int available() | 返回可以读取的字节数(不阻塞)。 | int availableBytes = in.available(); |
void close() | 关闭输入流并释放与该流相关的所有资源。 | in.close(); |
void mark(int readlimit) | 在流中的当前位置设置标记,readlimit 是可以读取的字节数上限。 | in.mark(1024); |
void reset() | 将流重新定位到上次标记的位置,如果没有标记或标记失效,抛出 IOException。 | in.reset(); |
boolean markSupported() | 检查当前输入流是否支持 mark() 和 reset() 操作。 | boolean isMarkSupported = in.markSupported(); |
字节流的文件输出
字节流的文件输出通过 FileOutputStream类来实现,创建一个文件输出流:
File f = new File("C:/java/hello");
OutputStream fOut = new FileOutputStream(f);
| 方法 | 描述 | 示例代码 |
|---|---|---|
void write(int b) | 将指定的字节写入输出流,b 的低 8 位将被写入流中。 | fOut.write(255); |
void write(byte[] b) | 将字节数组 b 中的所有字节写入输出流。 | byte[] data = "Hello".getBytes(); fOut.write(data); |
void write(byte[] b, int off, int len) | 将字节数组 b 中从偏移量 off 开始的 len 个字节写入输出流。 | byte[] data = "Hello".getBytes(); fOut.write(data, 0, data.length); |
void flush() | 刷新输出流并强制写出所有缓冲的数据,确保数据被立即写入目标输出。 | fOut.flush(); |
void close() | 关闭输出流并释放与该流相关的所有资源。关闭后不能再写入。 | fOut.close(); |
java程序更换win11壁纸
在本地路径下存有一张好看的图片:D:\OneDrive\图片\picture\19.png

我希望通过一个java程序来帮我将图片替换为我电脑的壁纸。
首先我们需要知道:
- win11壁纸路径为:
C:\Users\%username%\AppData\Roaming\Microsoft\Windows\Themes目录下的TranscodedWallpaper文件,并无文件后缀。
所以我们只需要读取我们喜欢的图片的数据,再覆盖原本的壁纸文件即可。
这是我现在的桌面:

我们执行下面的代码:
import java.io.*;
public class WallPaperChange {
public static void main(String[] args){
String sourcePath = "D:\\OneDrive\\图片\\picture\\19.png";
String targetPath = "C:\\Users\\XXY\\AppData\\Roaming\\Microsoft\\Windows\\Themes\\TranscodedWallpaper";
File sourceFile = new File(sourcePath);
File targetFile = new File(targetPath);
try (InputStream s = new FileInputStream(sourceFile);
OutputStream t = new FileOutputStream(targetFile)) {
byte[] b = new byte[s.available()];
s.read(b);
t.write(b);
System.out.println("Wallpaper changed successfully");
} catch (FileNotFoundException e) {
System.err.println("File not found: " + e.getMessage());
} catch (IOException e) {
System.err.println("An I/O error occurred: " + e.getMessage());
}
}
}
现在我们通过任务管理器将Windows资源管理器重新启动;

可以看到壁纸已经更换为我们想要的壁纸:
