Java的IO流
IO 流
IO 流:存储和读取数据的解决方案
File:表示系统中的文件或者文件夹的路径
注意:File 类只能对文件本身进行操作,不能读写文件里面存储的数据*,*
而 IO 流 用于读写文件中的数据(可以读写文件,或网络中的数据...)
IO 流的分类

纯文本文件:用 Windows 的记事本打开能读懂的(例:txt 文件,md 文件,xml 文件,lrc文件等)
IO 流的体系结构

其中,InputStream, OutputStream, Reader, Writer 都是抽象类,不能直接创建对象,需要使用其实现类。
FileOutputStream
操作本地文件的字节输出流,可以把程序中的数据写到本地文件中。
书写步骤:
-
创建字节输出流对象
- 细节1:参数可以是字符串表示的路径,也可以是 File 对象
- 细节2:如果文件不存在,会自动创建,但是要保证父级路径是存在的
- 细节3:如果文件存在,会清空文件再写出
-
写数据
- 细节:write 方法的形参是整数,但是实际上写入本地文件的是整数在 ASCII 表中对应的字符
-
释放资源
- 每次使用完流都要释放资源
package com.faxont.bytestream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class ByteStreamDemo1 {
public static void main(String[] args) throws IOException {
/*
演示:字节输出流 FileOutputStream
实现需求:将一段文字写出到本地文件中(暂时不写中文)
步骤:
1. 创建字节输出流对象
细节1:参数可以是字符串表示的路径,也可以是 File 对象
细节2:如果文件不存在,会自动创建,但是要保证父级路径是存在的
细节3:如果文件存在,会清空文件再写出
2. 写数据
细节:write 方法的形参是整数,但是实际上写入本地文件的是整数在 ASCII 表中对应的字符
3. 释放资源
每次使用完流都要释放资源
*/
// 1. 创建 FileOutputStream 对象
FileOutputStream fos = new FileOutputStream("io\\test.txt");
// 2. 写入数据
fos.write("hello world".getBytes());
// 3. 释放资源
fos.close();
}
}
FileOutputStream 写数据的3种方式
| 方法名称 | 说明 |
|---|---|
| void write(int b) | 一次写一个字节数据 |
| void write(byte[] b) | 一次写一个字节数组数据 |
| void write(byte[] b, int off, int len) | 一次写一个字节数组的部分数据 |
package com.faxont.bytestream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class ByteStreamDemo2 {
public static void main(String[] args) throws IOException {
/*
void write(int b) 一次写一个字节数据
void write(byte[] b) 一次写一个字节数组数据
void write(byte[] b, int off, int len) 一次写一个字节数组的部分数据
*/
// 1. 创建对象
FileOutputStream fos = new FileOutputStream("io\\test.txt");
byte[] b = {97, 98, 99};
// 2. 写数据
fos.write(97);// a
fos.write(b);// abc
/**
* 参数一:字节数组
* 参数二:起始索引
* 参数三:写出字节数
*/
fos.write(b, 1, 2);// bc
// 3. 释放资源
fos.close();
}
}
package com.faxont.bytestream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class ByteStreamDemo3 {
public static void main(String[] args) throws IOException {
/*
换行写:
Windows:\r\n
Linux:\n
Mac:\r
细节:
在 Windows 操作系统中,Java 对回车进行了优化
虽然完整的是 \r\n,但是我们写其中一个 \r 或 \n
Java 也可以实现换行,因为 Java 的底层会补全
续写:
如果想要续写,打开续写开关即可
开关位置:创建 FileOutputStream 对象的时候的第二个参数
默认是 false,表示每次写都会清空文件
true 表示每次写都会追加到文件末尾
*/
// 1. 创建 FileOutputStream 对象
FileOutputStream fos = new FileOutputStream("io\\test.txt", true);
// 2. 写数据
fos.write("Kindred\r\n".getBytes());
fos.write("Faxont\n".getBytes());
fos.write("Scroll\r".getBytes());
fos.write("DBD".getBytes());
// 3. 释放资源
fos.close();
}
}
FileInputStream
操作本地文件的字节输入流,可以把本地文件中的数据读取到程序中来。
书写步骤:
-
创建字节输入流
- 细节1:如果文件不存在,直接报错
-
读数据
- 细节1:一次读一个字符,读出来的是数据在 ASCII 表上对应的数字
- 细节2:读到文件末尾了,read 方法返回 -1
-
释放资源
- 细节:每次使用完流都要释放资源
package com.faxont.bytestream2;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class ByteStreamDemo1 {
public static void main(String[] args) throws IOException {
/*
演示:字节输入流 FileInputStream
实现需求:读取文件中的数据(暂时不写中文)
实现步骤:
1. 创建对象
细节1:如果文件不存在,直接报错
2. 获取数据
细节1:一次读一个字符,读出来的是数据在 ASCII 表上对应的数字
细节2:读到文件末尾了,read 方法返回 -1
3. 释放资源
细节:每次使用完流都要释放资源
*/
// 1. 创建 FileInputStream 对象
FileInputStream fis = new FileInputStream("io\\test.txt");
// 2. 获取数据
int b;
while((b = fis.read()) != -1){
System.out.print((char)b);
}
// 3. 释放资源
fis.close();
}
}
练习:文件拷贝
package com.faxont.bytestream2;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class ByteStreamDemo2 {
public static void main(String[] args) throws IOException {
/*
练习:
文件拷贝
把 D:\\java\\hadoop\\spark.txt 文件拷贝到当前模块中
注意:
选择一个比较小的文件
附加练习:
统计一下拷贝时间
*/
// 1. 创建对象
FileInputStream fis = new FileInputStream("D:\\java\\hadoop\\spark.txt");
FileOutputStream fos = new FileOutputStream("io\\copy.txt");
// 2. 拷贝
// 核心思想:边读边写
long start = System.currentTimeMillis();
int b;
while ((b = fis.read()) != -1) {
fos.write(b);
}
long end = System.currentTimeMillis();
// 3. 释放资源
// 规则:先创建的后释放
fos.close();
fis.close();
System.out.println("拷贝完成,耗时:" + (end - start) + "毫秒");
}
}
FileInputStream 一次读多个字节
| 方法名称 | 说明 |
|---|---|
| public int read() | 一次读一个字节数据 |
| public int read(byte[] buffer) | 一次读一个字节数组数据 |
注意:一次读一个字节数组的数据,每次读取会尽可能把数组装满
练习:文件拷贝(一次读写多个字节)
package com.faxont.bytestream2;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class ByteStreamDemo4 {
public static void main(String[] args) throws IOException {
/*
文件拷贝
把 E盘下的 crayon shinchan S1P1 (46.9 MB (49,210,386 字节)) 文件拷贝到当前模块中
*/
// 1. 创建对象
FileInputStream fis = new FileInputStream("E:\\crayon shinchan\\蜡丨笔X新.1-9季+29部剧场版+漫画+特别篇\\蜡笔小新 1-9季\\S01\\第一季 001 买东西.mp4");
FileOutputStream fos = new FileOutputStream("io\\copy.mp4");
byte[] bytes = new byte[1024 * 1024 * 5];
// 2. 获取数据
int len;
long start = System.currentTimeMillis();
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
long end = System.currentTimeMillis();
// 3. 释放资源
fos.close();
fis.close();
System.out.println("拷贝完成,耗时:" + (end - start) + "毫秒");
}
}
try...catch 异常处理
特点:finally 里面的代码一定被执行,除非虚拟机停止
package com.faxont.bytestream2;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class ByteStreamDemo5 {
public static void main(String[] args){
/*
利用 try...catch...finally 捕获文件中代码出现的异常
*/
// 提前声明对象
FileInputStream fis = null;
FileOutputStream fos = null;
try{
// 1. 创建对象
fis = new FileInputStream("E:\\crayon shinchan\\蜡丨笔X新.1-9季+29部剧场版+漫画+特别篇\\蜡笔小新 1-9季\\S01\\第一季 001 买东西.mp4");
fos = new FileOutputStream("io\\copy.mp4");
byte[] bytes = new byte[1024 * 1024 * 5];
// 2. 获取数据
int len;
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
}catch(IOException e){
e.printStackTrace();
}finally{
// 3. 释放资源
if(fos != null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
AutoCloseable
在 JDK 7 的时候,Java 推出了一个接口:AutoCloseable,使用该接口可以在特定情况下自动释放资源

package com.faxont.bytestream2;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class ByteStreamDemo6 {
public static void main(String[] args) {
/*
JDK 7 : IO 流中捕获异常的写法
try 后面的小括号中写创建对象的代码
注意:只有实现了 AutoCloseable 接口的对象,才能在小括号中创建对象
try (资源1;资源2;...) {
可能出现的异常的代码;
}catch(异常类名 变量名){
异常的处理代码;
}
*/
try(FileInputStream fis = new FileInputStream("D:\\p\\Saved Pictures\\NoWayToRun.jpg");
FileOutputStream fos = new FileOutputStream("io\\copy.jpg")){
byte[] bytes = new byte[1024];
// 2. 获取数据
int len;
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
}catch(IOException e){
e.printStackTrace();
}
}
}
package com.faxont.bytestream2;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class ByteStreamDemo7 {
public static void main(String[] args) throws FileNotFoundException {
/*
JDK 9 : IO 流中捕获异常的写法
创建流对象1;
创建流对象2;
try(流1;流2){
可能出现的异常的代码;
}catch(异常类名 变量名){
异常的处理代码;
}
*/
// 1. 创建对象
FileInputStream fis = new FileInputStream("D:\\p\\Saved Pictures\\NoWayToRun.jpg");
FileOutputStream fos = new FileOutputStream("io\\copy.jpg");
try(fis;fos){
byte[] bytes = new byte[1024];
// 2. 获取数据
int len;
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
}catch(IOException e){
e.printStackTrace();
}
}
}
Java 中编码的方法
| String 类中的方法 | 说明 |
|---|---|
| public byte[] getBytes() | 使用默认方式进行编码 |
| public byte[] getBytes(String charsetName) | 使用指定方式进行编码 |
Java 中解码的方法
| String 类中的方法 | 说明 |
|---|---|
| String(byte[] bytes) | 使用默认方式进行解码 |
| String(byte[] bytes, String charsetName) | 使用指定方式进行解码 |
package com.faxont.charset;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
public class CharSetDemo1 {
public static void main(String[] args) throws UnsupportedEncodingException {
/*
Java 中编码的方法:
public byte[] getBytes() 使用默认方式进行编码
public byte[] getBytes(String charsetName) 使用指定方式进行编码
Java 中解码的方法:
String(byte[] bytes) 使用默认方式进行解码
String(byte[] bytes, String charsetName) 使用指定方式进行解码
*/
// 1. 编码
String str = "ai你哟";
byte[] bytes1 = str.getBytes();// utf-8 1 3
System.out.println(Arrays.toString(bytes1));
System.out.println(bytes1.length);
byte[] bytes2 = str.getBytes("GBK");// gbk 2 2
System.out.println(Arrays.toString(bytes2));
System.out.println(bytes2.length);
// 2. 解码
String str2 = new String(bytes1);
System.out.println(str2);
String str3 = new String(bytes2, "GBK");
System.out.println(str3);
}
}
为什么会有乱码?
原因1:读取数据时未读完整个汉字
解决:字符流
原因2:编码和解码时的方式不统一
解决:在编程中统一使用 UTF-8 编码
字符流
字符流的底层其实就是字节流
字符流 = 字节流 + 字符集
使用场景:对于纯文本文件进行读写操作
特点
输入流:一次读一个字节,遇到中文时,一次读多个字节
输出流:底层会把数据按照指定的编码方式进行编码,变成字节再写到文件中
FileReader
- 创建字符输入流对象
| 构造方法 | 说明 |
|---|---|
| public FileReader(File file) | 创建字符输入流关联本地文件 |
| public FileReader(String pathname) | 创建字符输入流关联本地文件 |
细节:如果文件不存在,就直接报错
- 读取数据
| 成员方法 | 说明 |
|---|---|
| public int read() | 读取数据,读到末尾返回-1 |
| public int read(char[] buffer) | 读取多个数据,读到末尾返回-1 |
细节1:按字节进行读取,遇到中文,一次读多个字节,读取后解码,返回一个整数
细节2:读到文件末尾了,read 方法返回-1
- 释放资源
| 成员方法 | 说明 |
|---|---|
| public int close() | 释放资源 / 关流 |
package com.faxont.charstream;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
public class CharStreamDemo1 {
public static void main(String[] args) {
/*
1. 创建字符输入流对象
public FileReader(File file) 创建字符输入流关联本地文件
public FileReader(String pathname) 创建字符输入流关联本地文件
2. 读取数据
public int read() 读取数据,读到末尾返回-1
public int read(char[] buffer) 读取多个数据,读到末尾返回-1
3. 释放资源
public int close() 释放资源 / 关流
我读取的文本如下:
頔di
细节:
read() : 默认是一个字节一个字节读取的,如果遇到中文就会一次读取多个;
在读取后,方法的底层还会进行解码并转成十进制的数字,作为返回值
read(char[] buffer) : 把读取数据,解码,强转三步合并了,把强转之后的字符放到数组中,返回读取的字节数
等同于:空参的 read() + 强制类型转换
*/
// 1. 创建对象
FileReader fr1 = null;
FileReader fr2 = null;
File file = new File("D:\\java\\workspace\\a.txt");
char[] buffer = new char[1024];
try{
fr1 = new FileReader(file);
fr2 = new FileReader("D:\\java\\workspace\\a.txt");
System.out.println((char)fr1.read());// 頔
int len = fr2.read(buffer);
System.out.println(len);// 3
System.out.println(new String(buffer, 0, len));// 頔di
}catch(IOException e){
e.printStackTrace();
}finally{
try{
if(fr1 != null){
fr1.close();
}
if(fr2 != null){
fr2.close();
}
}catch(IOException e){
e.printStackTrace();
}
}
}
}
FileWriter
构造方法
| 构造方法 | 说明 |
|---|---|
| public FileWriter(File file) | 创建字符输出流关联本地文件 |
| public FileWriter(String pathname) | 创建字符输出流关联本地文件 |
| public FileWriter(File file, boolean append) | 创建字符输出流关联本地文件,续写 |
| public FileWriter(String pathname, boolean append) | 创建字符输出流关联本地文件,续写 |
成员方法
| 成员方法 | 说明 |
|---|---|
| void write(int c) | 写出一个字符 |
| void write(String str) | 写出一个字符串 |
| void write(String str, int off, int len) | 写出一个字符串的一部分 |
| void write(char[] cbuf) | 写出一个字符数组 |
| void write(char[] cbuf, int off, int len) | 写出字符数组的一部分 |
书写细节
-
创建字符输出流对象
- 细节1:参数可以是字符串表示的路径或者 File 对象
- 细节2:如果文件不存在就会创建一个新文件,但是要保证父级路径是存在的
- 细节3:如果文件已经存在,则会清空文件,如果不想清空可以打开续写开关
-
写数据
- 细节:如果 write 方法的参数是整数,但是实际上写到本地文件中的是整数在字符集上对应的字符
-
释放资源
- 细节:每次使用完流之后都要释放资源
字符流原理解析
-
创建字符输入流对象
底层:关联文件,并创建缓冲区(长度为8192字节的数组)
-
读取数据
底层:
-
判断缓冲区中是否有数据可以读取
-
缓冲区没有数据:就从文件中获取数据,装缓冲区中,每次尽可能装满缓冲区
;如果文件中也没有数据了,返回 -1
-
缓冲区有数据:就从缓冲区中读取。
- 空参的 read 方法:一次读取一个字节,遇到中文一次读多个字节,把字节解码并转成十进制返回
- 有参的 read 方法:把读取字节,解码,强转三步合并了,强转之后的字符放到数组中
-
字符输出流同样会创建一个缓冲区,那么何时会将缓冲区内的数据存入文件呢:
情况1:装满了
情况2:刷新(调用 flush() 方法)
情况3:关流(调用 close() 方法)
flush 和 close 方法
| 成员方法 | 说明 |
|---|---|
| public void flush() | 将缓冲区中的数据,刷新到本地文件中 |
| public void close() | 释放资源 / 关流 |
两者之间的区别:
flush刷新:刷新之后,还可以继续往文件中写出数据
close 关流:断开通道,无法再往文件中写出数据
字节流和字符流的使用场景
字节流:拷贝任意类型的文件
字符流:读取纯文本文件中的数据,往纯文本文件中写出数据
练习:拷贝
需求:
拷贝一个文件夹,考虑子文件夹
package com.faxont.practice;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class Practice1 {
public static void main(String[] args) {
/*
需求:
拷贝一个文件夹,考虑子文件夹
*/
copyFolder(new File("D:\\java\\workspace\\test"), new File("D:\\java\\workspace\\copy"));
}
public static void copyFolder(File src, File dest) {
if(src.isDirectory()){
if(!dest.exists()){
dest.mkdir();
}
File[] files = src.listFiles();
if(files == null || files.length == 0){
return;
}
for (File f : files) {
copyFolder(f, new File(dest, f.getName()));
}
}else{
if(!dest.getParentFile().exists()){
dest.getParentFile().mkdirs();
}
// try-with-resources 自动释放资源
try(FileInputStream fis = new FileInputStream(src);
FileOutputStream fos = new FileOutputStream(dest)){
byte[] bytes = new byte[1024];
int len;
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
练习:文件加密
为了保证文件的安全性,就需要对原始文件进行加密存储,再使用的时候再对其进行解密处理
加密原理:
对原始文件中的每一个字节数据进行更改,然后将更改以后的数据存储到新的文件中。
解密原理:
读取加密之后的文件,按照加密的规则反向操作,变成原始文件。
package com.faxont.practice;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class Practice2 {
public static void main(String[] args) {
/*
为了保证文件的安全性,就需要对原始文件进行加密存储,再使用的时候再对其进行解密处理
加密原理:
对原始文件中的每一个字节数据进行更改,然后将更改以后的数据存储到新的文件中。
解密原理:
读取加密之后的文件,按照加密的规则反向操作,变成原始文件。
*/
File src = new File("D:\\p\\Saved Pictures\\NoWayToRun.jpg");
File encrypt = new File("io\\encrypt.jpg");
File decrypt = new File("io\\decrypt.jpg");
encrypt(src, encrypt);
decrypt(encrypt, decrypt);
}
// 加密方法
public static void encrypt(File src, File dest){
try(FileInputStream fis = new FileInputStream(src);
FileOutputStream fos = new FileOutputStream(dest)){
byte[] bytes = new byte[1024 * 1024 * 5];
int len;
while ((len = fis.read(bytes)) != -1) {
for (int i = 0; i < len; i++) {
// 进行了异或运算 ^
bytes[i] = (byte) (bytes[i] ^ 20);// 进行了 bytes[i] ^ 20 运算,数据变成了 int 类型,需要进行强转
}
fos.write(bytes, 0, len);
}
}catch(Exception e){
e.printStackTrace();
}
}
// 解密方法
public static void decrypt(File scr, File dest){
try(FileInputStream fis = new FileInputStream(scr);
FileOutputStream fos = new FileOutputStream(dest)){
byte[] bytes = new byte[1024 * 1024 * 5];
int len;
while ((len = fis.read(bytes)) != -1) {
for (int i = 0; i < len; i++) {
bytes[i] = (byte) (bytes[i] ^ 20);
}
fos.write(bytes, 0, len);
}
}catch(Exception e){
e.printStackTrace();
}
}
}
练习:修改文件中的数据
文本文件中有以下的数据:
2-1-9-4-7-8
将文件中的数据进行排序,变成以下的数据:
1-2-4-7-8-9
package com.faxont.practice;
import java.io.*;
import java.util.Arrays;
public class Practice3 {
public static void main(String[] args) {
/*
文本文件中有以下的数据:
2-1-9-4-7-8
将文件中的数据进行排序,变成以下的数据:
1-2-4-7-8-9
*/
sort(new File("D:\\java\\workspace\\test\\original.txt"), new File("io\\sorted.txt"));
sort2(new File("D:\\java\\workspace\\test\\original.txt"), new File("io\\sorted2.txt"));
}
// 方法一
public static void sort(File src, File dest){
try(
FileReader fr = new FileReader(src); // 字符输入流
FileWriter fw = new FileWriter(dest) // 字符输出流
){
StringBuilder sb = new StringBuilder();
int ch;
while ((ch = fr.read()) != -1) { // 一次读一个字符
sb.append((char)ch); // 直接转成字符拼接
}
String[] splitArr = sb.toString().split("-");
int[] numArr = new int[splitArr.length];
for (int i = 0; i < splitArr.length; i++) {
numArr[i] = Integer.parseInt(splitArr[i]);
}
Arrays.sort(numArr);
StringBuilder result = new StringBuilder();
for (int i = 0; i < numArr.length; i++) {
result.append(numArr[i]);
if (i != numArr.length - 1) {
result.append("-");
}
}
fw.write(result.toString()); // 直接写字符串,超级方便
}catch(Exception e){
e.printStackTrace();
}
}
// 方法二
public static void sort2(File src, File dest){
try(FileReader fr = new FileReader(src);
FileWriter fw = new FileWriter(dest)
){
StringBuilder sb = new StringBuilder();
int len;
while ((len = fr.read()) != -1) {
sb.append((char)len);
}
Integer[] arr = Arrays.stream(sb.toString().split("-"))
.map(Integer::parseInt)
.sorted()
.toArray(Integer[]::new);
String s = Arrays.toString(arr)
.replace("[", "")
.replace("]", "")
.replace(", ", "-");
fw.write(s);
}catch(Exception e){
e.printStackTrace();
}
}
}
缓冲流

- 缓冲流自带长度为8192的缓冲区
- 可以显著提高字节流的读写性能
- 对于字符流提升不明显,对于字符缓冲流而言关键点是两个特有的方法
字节缓冲流
底层自带了长度为8192的缓冲区提高性能。
| 方法名称 | 说明 |
|---|---|
| public BufferedInputStream(InputStream is) | 把基本流包装成高级流,提高读取数据的性能 |
| public BufferedOutputStream(OutputStream os) | 把基本流包装成高级流,提高写出数据的性能 |
练习:拷贝文件
利用字节缓冲流拷贝文件
package com.faxont.bufferedstream;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class BufferedStreamDemo1 {
/*
需求:
利用字节缓冲流拷贝文件
字节缓冲输入流的构造方法:
public BufferedInputStream(InputStream is) 把基本流包装成高级流,提高读取数据的性能
字节缓冲输出流的构造方法:
public BufferedOutputStream(OutputStream os) 把基本流包装成高级流,提高写出数据的性能
*/
public static void main(String[] args) {
try(BufferedInputStream is = new BufferedInputStream(new FileInputStream("D:\\java\\workspace\\a.txt"));
BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream("D:\\java\\workspace\\b.txt"));
){
byte[] buffer = new byte[1024];
int len;
while((len = is.read(buffer)) != -1){
os.write(buffer, 0, len);
}
System.out.println("文件拷贝完成!");
}catch(Exception e){
e.printStackTrace();
}
}
}
字符缓冲流
底层自带了长度为8192的缓冲区提高性能。
字符缓冲流的构造方法
| 方法名称 | 说明 |
|---|---|
| public BufferedReader(Reader r) | 把基本流变成高级流 |
| public BufferedWriter(Writer r) | 把基本流变成高级流 |
字符缓冲流特有方法
| 字符缓冲输入流特有方法 | 说明 |
|---|---|
| public String readLine() | 读取一行数据,如果没有数据可读了,会返回 null |
package com.faxont.bufferedstream;
import java.io.BufferedReader;
import java.io.FileReader;
public class BufferedStreamDemo2 {
public static void main(String[] args) {
/*
字符缓冲输入流:
构造方法:
public BufferedReader(Reader r) 把基本流变成高级流
特有方法:
public String readLine() 读取一行数据,如果没有数据可读了,会返回 null
细节:readLine() 方法一次性会读取一整行数据,但是不会把换行符读取到内存当中
*/
try(BufferedReader br = new BufferedReader(new FileReader("D:\\java\\hadoop\\test.txt"))){
String line;
while((line = br.readLine()) != null){
System.out.println(line);
}
System.out.println("文件读取完毕!");
System.out.println(line);// null
}catch(Exception e){
e.printStackTrace();
}
}
}
| 字符缓冲输出流特有方法 | 说明 |
|---|---|
| public void newLine() | 跨平台的换行 |
package com.faxont.bufferedstream;
import java.io.BufferedWriter;
import java.io.FileWriter;
public class BufferedStreamDemo3 {
public static void main(String[] args) {
/*
字符缓冲输出流:
构造方法:
public BufferedWriter(Writer r) 把基本流变成高级流
特有方法:
public void newLine() 跨平台的换行
*/
try(BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\java\\workspace\\a.txt"))){
bw.write("Scroll");
bw.newLine();
bw.write("Faxont");
System.out.println("文件写入完毕!");
}catch(Exception e){
e.printStackTrace();
}
}
}
练习:拷贝文件
四种方式拷贝文件,并统计各自用时
字节流的基本流:一次读写一个字节
字节流的基本流:一次读写一个字节数组
字节缓冲流:一次读写一个字节
字节缓冲流:一次读写一个字节数组
package com.faxont.bufferedstream;
import java.io.*;
public class Practice1 {
public static void main(String[] args) {
/*
四种方式拷贝文件,并统计各自用时
字节流的基本流:一次读写一个字节
字节流的基本流:一次读写一个字节数组
字节缓冲流:一次读写一个字节
字节缓冲流:一次读写一个字节数组
*/
File src = new File("E:\\tool\\Java\\CentOS-7-x86_64-DVD-2003.iso");
File dest = new File("D:\\java\\workspace\\CentOS-7-x86_64-DVD-2003.iso");
long start = System.currentTimeMillis();
copy1(src, dest);
copy2(src, dest);
copy3(src, dest);
copy4(src, dest);
long end = System.currentTimeMillis();
System.out.println((end - start) / 1000.0 + "秒");
}
public static void copy1(File src, File dest) {
try(FileInputStream fis = new FileInputStream(src);
FileOutputStream fos = new FileOutputStream(dest)){
int b;
while((b = fis.read()) != -1){
fos.write(b);
}
}catch(IOException e){
e.printStackTrace();
}
}
public static void copy2(File src, File dest) {
try(FileInputStream fis = new FileInputStream(src);
FileOutputStream fos = new FileOutputStream(dest)){
byte[] buf = new byte[8192];
int len;
while((len = fis.read(buf)) != -1){
fos.write(buf, 0, len);
}
}catch(IOException e){
e.printStackTrace();
}
}
public static void copy3(File src, File dest) {
try(BufferedReader br = new BufferedReader(new FileReader(src));
BufferedWriter bw = new BufferedWriter(new FileWriter(dest))
){
String line;
while((line = br.readLine()) != null){
bw.write(line);
bw.newLine();
}
}catch(IOException e){
e.printStackTrace();
}
}
public static void copy4(File src, File dest) {
try(BufferedReader br = new BufferedReader(new FileReader(src));
BufferedWriter bw = new BufferedWriter(new FileWriter(dest))
){
char[] buf = new char[8192];
int len;
while((len = br.read(buf)) != -1){
bw.write(buf, 0, len);
}
}catch(IOException e){
e.printStackTrace();
}
}
}
练习:修改文本顺序
需求:把《出师表》的文章顺序进行恢复到一个新文件中
package com.faxont.bufferedstream;
import java.io.*;
import java.util.Map;
import java.util.TreeMap;
public class Practice2 {
public static void main(String[] args) {
/*
需求:把《出师表》的文章顺序进行恢复到一个新文件中
*/
try(BufferedReader br = new BufferedReader(new FileReader("D:\\java\\workspace\\csb.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\java\\workspace\\TheMemorialOfDeparture.txt"))
){
Map<Integer, String> map = new TreeMap<>();
String line;
while((line = br.readLine()) != null){
if(line.isEmpty()) continue;
int index = line.indexOf(".");
if(index == -1) continue;
int num = Integer.parseInt(line.substring(0, index));
line = line.substring(index + 1).trim();
map.put(num, line);
}
for(String value : map.values()){
bw.write(value);
bw.newLine();
}
System.out.println("《出师表》恢复完成!");
}catch(IOException e){
e.printStackTrace();
}
}
}
练习:软件运行次数
实现一个验证程序运行次数的小程序,要求如下:
-
当程序运行超过3次时,给出提示:本软件只能免费使用3次,欢迎您注册会员后继续使用~
-
程序运行演示如下:
第一次运行控制台输出:欢迎使用本软件,第1次使用免费~
第二次运行控制台输出:欢迎使用本软件,第2次使用免费~
第三次运行控制台输出:欢迎使用本软件,第3次使用免费~
第四次及以后运行控制台输出:本软件只能免费使用3次,欢迎您注册会员后继续使用~
package com.faxont.bufferedstream;
import java.io.*;
public class Practice3 {
public static void main(String[] args) {
/*
实现一个验证程序运行次数的小程序,要求如下:
1. 当程序运行超过3次时,给出提示:本软件只能免费使用3次,欢迎您注册会员后继续使用~
2. 程序运行演示如下:
第一次运行控制台输出:欢迎使用本软件,第1次使用免费~
第二次运行控制台输出:欢迎使用本软件,第2次使用免费~
第三次运行控制台输出:欢迎使用本软件,第3次使用免费~
第四次及以后运行控制台输出:本软件只能免费使用3次,欢迎您注册会员后继续使用~
*/
method();
method();
method();
method();
method();
}
// IO原则:随用随创建,什么时候不用,什么时候就释放
// 最小持有原则:资源越早释放越好,谁申请、谁负责释放,不用立刻关,绝不长期占用。
public static void method(){
int count = 0;
try(BufferedReader br = new BufferedReader(new FileReader("count.txt"))){
String line = br.readLine();
if(line != null){
count = Integer.parseInt(line);
}
}catch(Exception e){
count = 0;
}
count++;
if(count > 3){
System.out.println("本软件只能免费使用3次,欢迎您注册会员后继续使用~");
}else{
System.out.println("欢迎使用本软件,第" + count + "次使用免费~");
}
try(BufferedWriter bw = new BufferedWriter(new FileWriter("count.txt"))){
bw.write(String.valueOf(count));
}catch(IOException e){
e.printStackTrace();
}
}
}
拓展:什么时候不能使用 try-with-resources 自动释放资源
IO流最小持有原则:资源越早释放越好,谁申请、谁负责释放,不用立刻关,绝不长期占用。
而 try-with-resources 的释放时机是在 try 代码块一结束就立即释放,所以某些情况不能使用 try-with-resource
场景 1:需要跨方法复用同一个 IO 流
举例:
在 A 方法创建 FileOutputStream
传给 B、C 方法分段写入
整个业务流程走完,最后统一关闭
如果用 try-with-resources:A 方法 try 块一结束,流直接被自动关闭,后续 B/C 方法再写入 → 流已关闭异常。
// 错误写法
public OutputStream getStream() {
try (FileOutputStream fos = new FileOutputStream("a.txt")) {
return fos; // 出try就自动close,返回的是一个已关闭的流
}
}
场景 2:需要手动控制关闭时机,中间要暂停、休眠、分批延时写入
比如:
长连接流、持续监听的输入流
循环分批写文件,间隔几秒写一次
需要保留流对象,后续按需读写
try-with-resources 会强制「代码块结束必关」,不符合业务需要。
场景 3:父资源和子资源共用同一个底层流,自动关闭会连锁报错(了解即可)
例:
BufferedReader br = new BufferedReader(new FileReader("a.txt"));
如果只给外层包装流用 try-with-resources:
关闭包装流会自动关闭底层节点流;
但如果你需要单独控制底层流生命周期、不想被连带关闭,就不适合。
场景 4:需要多次读写、重复使用资源对象
比如一个全局日志输出流、全局配置读取流,
项目运行期间要反复读写,不能执行一次就关闭。
反过来:哪些场景必须优先用 try-with-resources
-
单次读取文件、一次性写入
-
数据库连接、Statement、ResultSet 单次操作
-
临时创建、只用一次、作用域局限在当前方法
这些场景:
手动写 finally 容易漏写 close、漏关导致文件占用、句柄泄漏;
try-with-resources 更安全、更贴合 IO 释放原则。
转换流
是字符流和字节流之间的桥梁
作用:
- 指定字符集读写(JDK11 后淘汰了,所以了解即可)
- 字节流想要使用字符流中的方法
练习:转换文件编码
需求1:手动创建一个 GBK 的文件,把文件中的中文读取到内存中,不能出现乱码
package com.faxont.convertstream;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
public class ConvertStreamDemo1 {
public static void main(String[] args) {
/*
需求1:手动创建一个 GBK 的文件,把文件中的中文读取到内存中,不能出现乱码
*/
// try(InputStreamReader isr = new InputStreamReader(new FileInputStream("D:\\java\\workspace\\gbk.txt"), "GBK")){
// char[] chs = new char[1024];
// int len;
// while((len = isr.read(chs)) != -1){
// System.out.println(new String(chs, 0, len));
// }
// }catch(IOException e){
// e.printStackTrace();
// }
try(FileReader fr = new FileReader("D:\\java\\workspace\\gbk.txt", Charset.forName("GBK"))){
char[] chs = new char[1024];
int len;
while((len = fr.read(chs)) != -1){
System.out.println(new String(chs, 0, len));
}
}catch(IOException e){
e.printStackTrace();
}
}
}
需求2:把一段中文按照 GBK 的方式写到本地文件
package com.faxont.convertstream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
public class ConvertStreamDemo2 {
public static void main(String[] args) {
/*
需求2:把一段中文按照 GBK 的方式写到本地文件
*/
// try(OutputStreamWriter isw = new OutputStreamWriter(new FileOutputStream("io\\gbk.txt"), "GBK")){
// isw.write("卷頔是一个非常可爱的小柴犬\r\n它的英文名叫Scroll");
// System.out.println("文件写入完毕!");
// }catch(IOException e){
// e.printStackTrace();
// }
try(FileWriter fw = new FileWriter("io\\gbk.txt", Charset.forName("GBK"))){
fw.write("卷頔是一个非常可爱的小柴犬\r\n它的英文名叫Scroll");
System.out.println("文件写入完毕!");
}catch(IOException e){
e.printStackTrace();
}
}
}
需求3:将本地文件中的 GBK 文件,转成 UTF-8
package com.faxont.convertstream;
import java.io.*;
import java.nio.charset.Charset;
public class ConvertStreamDemo3 {
public static void main(String[] args) throws IOException {
/*
需求3:将本地文件中的 GBK 文件,转成 UTF-8
*/
// InputStreamReader isr = new InputStreamReader(new FileInputStream("io\\gbk.txt"), "GBK");
// char[] chs = new char[1024];
// int len;
// StringBuilder sb = new StringBuilder();
// while((len = isr.read(chs)) != -1){
// sb.append(chs, 0, len);
// }
// isr.close();
//
// OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("io\\gbk.txt"), "UTF-8");
// osw.write(sb.toString());
// osw.close();
FileReader fr = new FileReader("io\\gbk.txt", Charset.forName("GBK"));
char[] chs = new char[1024];
int len;
StringBuilder sb = new StringBuilder();
while((len = fr.read(chs)) != -1){
sb.append(chs, 0, len);
}
fr.close();
FileWriter fw = new FileWriter("io\\gbk.txt", Charset.forName("UTF-8"));
fw.write(sb.toString());
fw.close();
System.out.println("文件转换完毕!");
}
}
练习:读取文件中的数据
利用字节流读取文件中的数据,每次读一整行,而且不能出现乱码
package com.faxont.convertstream;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
public class ConvertStreamDemo4 {
public static void main(String[] args) {
/*
利用字节流读取文件中的数据,每次读一整行,而且不能出现乱码
*/
try(BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("io\\gbk.txt")))){
String line;
while((line = br.readLine()) != null){
System.out.println(line);
}
}catch (IOException e){
e.printStackTrace();
}
}
}
序列化
序列化流 / 对象操作输出流
可以把 Java 中的对象写到本地文件中
| 构造方法 | 说明 |
|---|---|
| public ObjectOutputStream(OutputStream out) | 把基本流包装成高级流 |
| 成员方法 | 说明 |
|---|---|
| public final void writeObject(Object obj) | 把对象序列化(写出)到文件中去 |
package com.faxont.objectstream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class ObjectStreamDemo1 {
public static void main(String[] args) {
/*
需求:
利用序列化流 / 对象操作输出流,把一个对象写到本地文件中
构造方法:
public ObjectOutputStream(OutputStream out) 把基本流变成高级流
成员方法:
public final void writeObject(Object obj) 把对象序列化(写出)到文件中去
*/
// 1. 创建学生对象
Student s = new Student("张三", 23, "杭州");
// 2. 创建对象操作输出流
try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("io\\student.txt"))){
oos.writeObject(s);
System.out.println("文件写出完毕!");
}catch (IOException e){
e.printStackTrace();
}
}
}
package com.faxont.objectstream;
import java.io.Serial;
import java.io.Serializable;
/**
* JavaBean 类必须要实现 Serializable 接口,否则会报错
* Serializable 接口里面是没有抽象方法的,是一个标记型接口
* 一旦实现了这个接口,那么就表示当前 Student 类可以被序列化
* 可以将标记型理解为一个物品的合格证
* JavaBean 类中,会定义一个序列化版本号,这个版本号是唯一的,不能重复
* 这个版本号,是 JVM 在序列化对象的时候,会去检查对象的版本号,如果版本号不一致,那么就会报错
* 如果没有设置版本号,那么 JVM 会自动生成一个版本号
* 此时,如果我们修改了 Student 类,那么就会生成一个新的版本号,此时,就会导致此前序列化得到的对象与 Student 类内序列化版本号不一致,就会报错
*/
public class Student implements Serializable {
@Serial
private static final long serialVersionUID = -7243920406040933579L;
private String name;
private int age;
// transient:瞬态关键字,其修饰的成员变量,不会被序列化
private transient String address;
public Student() {
}
public Student(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}
/**
* 获取
* @return address
*/
public String getAddress() {
return address;
}
/**
* 设置
* @param address
*/
public void setAddress(String address) {
this.address = address;
}
public String toString() {
return "Student{" + "name = " + name + ", age = " + age + ", address = " + address + "}";
}
}
序列化流的小细节
使用对象输出流将对象保存到文件时会出现 NotSerializableException 异常
解决方案:需要让 JavaBean 类实现 Serializable 接口
反序列化流 / 对象操作输入流
可以把序列化到本地文件中的对象,读取到程序中来
| 构造方法 | 说明 |
|---|---|
| public ObjectInputStream(InputStream in) | 把基本流变成高级流 |
| 成员方法 | 说明 |
|---|---|
| public Object readObject() | 把序列化到本地文件中的对象,读取到程序中来 |
package com.faxont.objectstream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class ObjectStreamDemo2 {
public static void main(String[] args) {
/*
需求:
利用反序列化流 / 对象操作输入流,把序列化到本地文件中的对象,读取到程序中来
构造方法:
public ObjectInputStream(InputStream in) 把基本流变成高级流
成员方法:
public Object readObject() 把序列化到本地文件中的对象,读取到程序中来
*/
try(ObjectInputStream ois = new ObjectInputStream(new FileInputStream("io\\student.txt"))){
Student stu = (Student) ois.readObject();
System.out.println(stu);// Student{name = 张三, age = 23, address = null}
}catch(IOException | ClassNotFoundException e){
e.printStackTrace();
}
}
}
序列化流 / 反序列化流的细节汇总
- 使用序列化流将对象写到文件时,需要让 JavaBean 类实现 Serializable 接口;否则,会出现 NotSerializableException 异常
- 序列化流写到文件中的数据是不能修改的,一旦修改就无法读回来了
- 序列化对象后,修改了 JavaBean 类,再次反序列化,会不会有问题?
会出问题:会抛出 InvalidClassException 异常
解决方案:给 JavaBean 类添加 serialVersionUID(序列号、版本号)
- 如果一个对象中的某个成员变量的值不想被序列化,又该如何实现呢?
解决方案:给该成员变量加 transient 关键字修饰,该关键字标记的成员变量不参与序列化过程
练习:用对象流读写多个对象
需求:
将多个自定义对象序列化到文件中,但是由于对象的个数不确定,反序列化流该如何读取呢?
package com.faxont.objectstream;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
public class Practice1 {
public static void main(String[] args) {
/*
将多个自定义对象序列化到文件中,但是由于对象的个数不确定,反序列化流该如何读取呢?
*/
// 1. 创建多个对象
Student s1 = new Student("张三", 23, "杭州");
Student s2 = new Student("王五", 25, "北京");
Student s3 = new Student("赵六", 26, "上海");
Student s4 = new Student("孙七", 27, "广州");
Student s5 = new Student("周八", 28, "深圳");
List<Student> list = new ArrayList<>();
list.add(s1);
list.add(s2);
list.add(s3);
list.add(s4);
list.add(s5);
// 2. 序列化
try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("io\\practice1.txt"))){
oos.writeObject(list);
}catch (IOException | NullPointerException e){
e.printStackTrace();
}
// 3. 反序列化
try(ObjectInputStream ois = new ObjectInputStream(new FileInputStream("io\\practice1.txt"))){
// while(true){
// try {
// Student stu = (Student) ois.readObject();
// System.out.println(stu);
// } catch (EOFException e) {
// // 序列化多个对象时,读到最后没有更多对象了,不会返回 null!
// // 而是直接抛出 EOFException(文件结束异常)!
// break;
// }
// }
List<Student> result = (List<Student>) ois.readObject();
for(Student stu : result){
System.out.println(stu);
}
}catch (IOException | ClassNotFoundException e){
e.printStackTrace();
}
}
}
打印流
分类:打印流一般是指:PrintStream,PrintWriter 两个类
特点1:打印流只操作文件目的地,不操作数据源
特点2:特有的写出方法可以实现,数据原样写出
例如:打印:97 文件中:97
打印:true 文件中:true
特点3:特有的写出方法,可以实现自动刷新,自动换行
打印一次数据 = 写出 + 换行 + 刷新
字节打印流
| 构造方法 | 说明 |
|---|---|
| public PrintStream(OutputStream / File /String) | 关联字节输出流 / 文件 / 文件路径 |
| public PrintStream(String fileName, Charset charset) | 指定字符编码 |
| public PrintStream(OutputStream out, boolean autoFlush) | 自动刷新 |
| public PrintStream(OutputStream out, boolean autoFlush, String encoding) | 指定字符编码且自动刷新 |
| 成员方法 | 说明 |
|---|---|
| public void write(int b) | 常规方法:规则跟之前一样,将指定的字节写出 |
| public void println(Xxx xx) | 特有方法:打印任意数据,自动刷新,自动换行 |
| public void print(Xxx xx) | 特有方法:打印任意数据,不换行 |
| public void printf(String format, Object... args) | 特有方法:带有占位符的打印语句,不换行 |
package com.faxont.printstream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.charset.Charset;
public class PrintStreamDemo1 {
public static void main(String[] args) {
/*
字节打印流:
构造方法:
public PrintStream(OutputStream / File /String) 关联字节输出流 / 文件 / 文件路径
public PrintStream(String fileName, Charset charset) 指定字符编码
public PrintStream(OutputStream out, boolean autoFlush) 自动刷新
public PrintStream(OutputStream out, boolean autoFlush, String encoding) 指定字符编码且自动刷新
成员方法:
public void write(int b) 常规方法:规则跟之前一样,将指定的字节写出
public void println(Xxx xx) 特有方法:打印任意数据,自动刷新,自动换行
public void print(Xxx xx) 特有方法:打印任意数据,不换行
public void printf(String format, Object... args) 特有方法:带有占位符的打印语句,不换行
*/
try(PrintStream ps = new PrintStream(new FileOutputStream("io\\printStream.txt"), true , Charset.forName("UTF-8"))){
ps.write(97);
ps.println(97);
ps.print("true");
ps.printf("%.2f", 3.1415);
ps.printf("\r\n%s爱上了%s", "阿珍", "阿强");
}catch (IOException e){
e.printStackTrace();
}
}
}
自动刷新 autoFlush
字节流底层没有缓冲区,开不开自动刷新都一样;
但是自动刷新 autoFlush,根本不是给「字节流」用的,是给「包装后的打印流行为」用的;
(如:BufferedOutputStream 有字节缓冲区;此时 autoFlush 就至关重要,每次 println 自动刷缓冲区,数据立刻写入文件; 这才是 autoFlush 设计的初衷)
为什么要留这个构造?
-
为了兼容多层包装流
日常都会:缓冲流 + 打印流 嵌套使用,缓冲流必须刷新才能落盘;
-
统一 API 设计
不管底层是字节流 / 字符流 / 缓冲流 / 网络流,PrintStream 统一一套构造;
-
标准输出场景刚需
System.out就是 PrintStream,默认开启自动刷新,
打印日志、控制台输出,必须实时显示,不能卡在缓冲区;
-
为了配合编码、自动刷新组合使用
表格里的重载构造:
PrintStream(OutputStream out, boolean autoFlush, String encoding)
一次性配置:包装流 + 自动刷新 + 指定编码,开发非常方便。
字符打印流
| 构造方法 | 说明 |
|---|---|
| public PrintWriter(Write / File / String) | 关联字节输出流 / 文件 / 文件路径 |
| public PrintWriter(String fileName, Charset charset) | 指定字符编码 |
| public PrintWriter(Writer w, boolean autoFlush) | 自动刷新 |
| public PrintWriter(OutputStream out, boolean autoFlush, Charset charset) | 指定字符编码且自动刷新 |
字符流底部有缓冲区,想要自动刷新需要开启
| 成员方法 | 说明 |
|---|---|
| public void write(...) | 常规方法:规则跟之前一样,写出字节或字符串 |
| public void println(Xxx xx) | 特有方法:打印任意类型的数据并且换行 |
| public void print(Xxx xx) | 特有方法:打印任意类型的数据,不换行 |
| public void printf(String format, Object... args) | 特有方法:带有占位符的打印语句 |
package com.faxont.printstream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
public class PrintStreamDemo2 {
public static void main(String[] args) {
/*
字符打印流:
构造方法:
public PrintWriter(Write / File / String) 关联字节输出流 / 文件 / 文件路径
public PrintWriter(String fileName, Charset charset) 指定字符编码
public PrintWriter(Writer w, boolean autoFlush) 自动刷新
public PrintWriter(OutputStream out, boolean autoFlush, Charset charset) 指定字符编码且自动刷新
字符流底部有缓冲区,想要自动刷新需要开启
成员方法:
public void write(...) 常规方法:规则跟之前一样,写出字节或字符串
public void println(Xxx xx) 特有方法:打印任意类型的数据并且换行
public void print(Xxx xx) 特有方法:打印任意类型的数据,不换行
public void printf(String format, Object... args) 特有方法:带有占位符的打印语句
*/
try(PrintWriter pw = new PrintWriter(new FileOutputStream("io\\printStream.txt"), true)){
pw.write(97);
pw.println(97);
pw.print("true");
pw.printf("%.2f", 3.1415);
pw.printf("\r\n%s爱上了%s", "阿珍", "阿强");
}catch(IOException e){
e.printStackTrace();
}
}
}
package com.faxont.printstream;
import java.io.PrintStream;
public class PrintStreamDemo3 {
public static void main(String[] args) {
/*
打印流的应用场景
*/
// 获取打印流的对象,此打印流在虚拟机启动的时候,由虚拟机创建,默认指向控制台
// 特殊的打印流,也称为系统中的标准输出流,是不能关闭的,在系统中是唯一的
PrintStream ps = System.out;
// 调用打印流中的方法 println
// 写出数据,自动换行,自动刷新
ps.println("hello world");
ps.close();
System.out.println("hello");
System.out.println("world");
}
}
解压缩流 / 压缩流
解压缩流
解压本质:把每一个 ZipEntry 按照层级拷贝到本地另一个文件夹中
package com.faxont.zipstream;
import java.io.*;
import java.nio.charset.Charset;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public class ZipStreamDemo1 {
public static void main(String[] args) {
// Java 中只能识别 zip 文件
File src = new File("D:\\java\\workspace\\Maxwell_Who_Cat.zip");
File dest = new File("D:\\java\\workspace\\test\\aaa");
unZip(src, dest);
}
public static void unZip(File src, File dest){
try(ZipInputStream zis = new ZipInputStream(new FileInputStream(src), Charset.forName("GBK"))){
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
String name = entry.getName();
File file = new File(dest, name);
if(entry.isDirectory()){
file.mkdirs();
}else{
File parentFile = file.getParentFile();
if(!parentFile.exists()){
parentFile.mkdirs();
}
try(FileOutputStream fos = new FileOutputStream(file)){
byte[] bytes = new byte[1024];
int len;
while ((len = zis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
}
}
zis.closeEntry();
}
}catch (IOException e){
e.printStackTrace();
}
}
}
压缩流
压缩本质:把每一个(文件 / 文件夹)看成 ZipEntry 对象放到压缩包中
package com.faxont.zipstream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class ZipStreamDemo2 {
public static void main(String[] args) {
/*
压缩流:
把一个文本文件打成一个压缩包
*/
File src = new File("D:\\java\\workspace\\TheMemorialOfDeparture.txt");
File dest = new File("D:\\java\\workspace");
zipFile(src, dest);
}
public static void zipFile(File src, File dest){
if(!src.exists() || !src.isFile()){
throw new RuntimeException("源文件不存在");
}
String fileName = src.getName();
fileName = fileName.substring(0, fileName.lastIndexOf(".")) + ".zip";
try(ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File(dest, fileName)))){
ZipEntry entry = new ZipEntry(src.getName());
zos.putNextEntry(entry);
try(FileInputStream fis = new FileInputStream(src)){
byte[] bytes = new byte[1024];
int len;
while ((len = fis.read(bytes)) != -1) {
zos.write(bytes, 0, len);
}
}
zos.closeEntry();
} catch (Exception e) {
System.out.println("压缩失败");
e.printStackTrace();
}
}
}
package com.faxont.zipstream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class ZipStreamDemo3 {
public static void main(String[] args) {
/*
压缩整个文件夹
*/
File src = new File("D:\\java\\workspace\\test\\aaa\\猫咪");
File dest = new File("D:\\java\\workspace\\test");
zipFolder(src, dest);
}
public static void zipFolder(File src, File dest){
if(!src.exists() || !src.isDirectory()){
throw new RuntimeException("源文件夹不存在");
}
if(!dest.exists()){
dest.mkdirs();
}
try(ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File(dest, src.getName() + ".zip")), Charset.forName("GBK"))){
compressRecursive(src, src, zos);
System.out.println("压缩完成");
}catch (IOException e){
e.printStackTrace();
}
}
/**
* 递归压缩
* @param root 根文件夹(用来计算相对路径)
* @param current 当前正在遍历的文件/文件夹
* @param zos 压缩流
* @throws IOException
*/
public static void compressRecursive(File root, File current, ZipOutputStream zos) throws IOException {
if(current.isDirectory()){
String dirPath = root.toPath().relativize(current.toPath()) + "/";
zos.putNextEntry(new ZipEntry(dirPath));
zos.closeEntry();
File[] files = current.listFiles();
if(files != null){
for (File f : files) {
compressRecursive(root, f, zos);
}
}
}else{
String filePath = root.toPath().relativize(current.toPath()).toString();
zos.putNextEntry(new ZipEntry(filePath));
try(FileInputStream fis = new FileInputStream(current)){
byte[] bytes = new byte[1024];
int len;
while ((len = fis.read(bytes)) != -1) {
zos.write(bytes, 0, len);
}
zos.closeEntry();
}
}
}
}
Commons-io
Commons-io 是 apache 开源基金组织提供的一组有关 IO 操作的开源工具包
作用:提高 IO 流的开发效率
Commons-io 使用步骤
- 在项目中创建一个文件夹:lib
- 将 jar 包复制粘贴到 lib 文件夹
- 右键点击 jar 包,选择 Add as Library -> 点击 OK
- 在类中导包使用
Commons-io 常见方法
| FileUtils类(文件 / 文件夹相关) | 说明 |
|---|---|
| static void copyFile(File srcFile, File destFile) | 复制文件 |
| static void copyDirectory(File srcDir, File destDir) | 复制文件夹内的所有文件到指定位置 |
| static void copyDirectoryToDirectory(File srcDir, File destDir) | 复制整个文件夹到指定位置 |
| static void deleteDirectory(File directory) | 删除文件夹 |
| static void cleanDirectory(File directory) | 清空文件夹 |
| static String readFileToString(File file, Charset encoding) | 读取文件中的数据变成字符串 |
| static void write(File file, CharSequence data, String encoding) | 写出数据 |
| IOUtils类(流相关) | 说明 |
|---|---|
| public static int copy(InputStream input, OutputStream output) | 复制文件 |
| public static int copyLarge(Reader input, Writer output) | 复制大文件 |
| public static String readLines(Reader input) | 读取数据 |
| public static void write(String data, OutputStream output) | 写出数据 |
package com.faxont.commonsio;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import java.io.*;
public class CommonsIODemo1 {
public static void main(String[] args) throws IOException {
/*
FileUtils类(文件 / 文件夹相关)
static void copyFile(File srcFile, File destFile) 复制文件
static void copyDirectory(File srcDir, File destDir) 复制文件夹
static void copyDirectoryToDirectory(File srcDir, File destDir) 复制文件夹
static void deleteDirectory(File directory) 删除文件夹
static void cleanDirectory(File directory) 清空文件夹
static String readFileToString(File file, Charset encoding) 读取文件中的数据变成字符串
static void write(File file, CharSequence data, String encoding) 写出数据
IOUtils类(流相关)
public static int copy(InputStream input, OutputStream output) 复制文件
public static int copyLarge(Reader input, Writer output) 复制大文件
public static String readLines(Reader input) 读取数据
public static void write(String data, OutputStream output) 写出数据
*/
File srcFile = new File("io\\test.txt");
File destFile = new File("io\\copy_test.txt");
// File srcDir = new File("D:\\java\\workspace\\test\\aaa\\猫咪");
File destDir = new File("D:\\java\\workspace\\test");
// FileUtils.copyFile(srcFile, destFile);
// FileUtils.copyDirectory(srcDir, destDir);// 复制文件夹内的所有文件到指定位置
// FileUtils.copyDirectoryToDirectory(srcDir, destDir);// 复制整个文件夹到指定位置
// FileUtils.deleteDirectory(srcDir);
// FileUtils.cleanDirectory(destDir);
// FileUtils.write(destFile, "hello world", "utf-8");
// System.out.println(FileUtils.readFileToString(destFile, "utf-8"));
FileInputStream fis = new FileInputStream(srcFile);
FileOutputStream fos = new FileOutputStream(destFile);
FileReader frLarge = new FileReader("io\\copy.txt");
FileWriter fwLarge = new FileWriter("io\\copy2.txt");
IOUtils.copy(fis, fos);
IOUtils.close(fis, fos);
IOUtils.copyLarge(frLarge, fwLarge);
IOUtils.close(frLarge, fwLarge);
System.out.println(IOUtils.readLines(new FileReader("io\\copy_test.txt")));
IOUtils.write("hello world", new FileOutputStream("io\\copy_test.txt"));
}
}
Hutool 工具包
| 相关类 | 说明 |
|---|---|
| IoUtil | 流操作工具类 |
| FileUtil | 文件读写和操作的工具类 |
| FileTypeUtil | 文件类型判断工具类 |
| WatchMonitor | 目录、文件监听 |
| ClassPathResorce | 针对 ClassPath 中资源的访问封装 |
| FileReader | 封装文件读取 |
| FileWriter | 封装文件写入 |
package com.faxont.hutool;
import cn.hutool.core.io.FileUtil;
import java.io.File;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class HutoolDemo1 {
public static void main(String[] args) {
/*
FileUtil 类:
file:根据参数创建一个 file 对象
touch:根据参数创建文件
writeLines:把集合中的数据写出到文件中,覆盖模式
appendLines:把集合中的数据写出到文件中,续写模式
readLines:指定字符编码,把文件中的数据,读到集合中
readUtf8Lines:按照 UTF-8 的形式,把文件中的数据读到集合中
copy:拷贝文件或者文件夹
*/
File file = FileUtil.file("D:\\", "java", "workspace", "a.txt");
System.out.println(file);
FileUtil.touch("D:\\java\\workspace\\hututu\\hutool.txt");// 没有父级路径,touch 也能创建,并将父级路径同时创建
ArrayList<String> arr = new ArrayList<>();
Collections.addAll(arr, "aaa", "bbb", "ccc");
FileUtil.writeLines(arr, file, Charset.forName("UTF-8"));
FileUtil.appendLines(arr, file, Charset.forName("UTF-8"));
List<String> list = FileUtil.readLines(file, Charset.forName("UTF-8"));
List<String> listUTF = FileUtil.readUtf8Lines(file);
System.out.println(list);
System.out.println(listUTF);
FileUtil.copy(file, new File("io\\huCopy.txt"), true);
}
}
综合练习
练习:制造假数据
需求:制造假数据也是开发中的一个能力,在各个网上爬取数据,是其中一个方法
获取姓氏:
https://hanyu.baidu.com/shici/detail?pid=0b2f26d4c0ddb3ee693fdb1137ee1b0d&from=kg0
获取男生名字:
http://www.haoming8.cn/baobao/10881.html
获取女生名字:
http://www.haoming8.cn/baobao/7641.html
package com.faxont.com.practice;
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Practice1 {
public static void main(String[] args) throws IOException {
/*
制造假数据:
获取姓氏:
https://hanyu.baidu.com/shici/detail?pid=0b2f26d4c0ddb3ee693fdb1137ee1b0d&from=kg0
获取男生名字:
http://www.haoming8.cn/baobao/10881.html
获取女生名字:
http://www.haoming8.cn/baobao/7641.html
*/
// 1. 定义变量记录网址
String familyName = "https://hanyu.baidu.com/shici/detail?pid=0b2f26d4c0ddb3ee693fdb1137ee1b0d&from=kg0";
String maleName = "http://www.haoming8.cn/baobao/10881.html";
String femaleName = "http://www.haoming8.cn/baobao/7641.html";
// 2. 爬取数据,把网址内的数据拼接成一个字符串
String familyNameStr = webCrawler(familyName);
String maleNameStr = webCrawler(maleName);
String femaleNameStr = webCrawler(femaleName);
// 3. 截取数据
familyNameStr = subStringBetween(familyNameStr, "赵钱孙李", "百家姓终");
maleNameStr = subStringBetween(maleNameStr, "龙宝宝起名男宝免费", "宝宝起名注意事项");
femaleNameStr = subStringBetween(femaleNameStr, "好听有诗意的女孩名字大全", "相关文章");
// 4. 通过正则表达式,把其中符合要求的数据获取出来
// 这里把(.{4})替换成([\u4E00-\u9FA5]{4})可以防止出现em">,就是匹配中文
ArrayList<String> familyNameTempList = getData(familyNameStr, "([\\u4E00-\\u9FA5]{4})(?=,|。|$)", 1);
ArrayList<String> maleNameTempList = getData(maleNameStr, "([\\u4E00-\\u9FA5]{2})(?=、|。|$)", 1);
ArrayList<String> femaleNameTempList = getData(femaleNameStr, "([\\u4E00-\\u9FA5]{2})(?=[ \\r\\n]|$)", 1);
// 5. 处理数据
// 姓氏
ArrayList<String> familyNameList = new ArrayList<>();
for (String s : familyNameTempList) {
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
familyNameList.add(String.valueOf(c));
}
}
// 男生名字(去重)
ArrayList<String> maleNameList = new ArrayList<>();
for (String s : maleNameTempList) {
if (!maleNameList.contains(s)) {
maleNameList.add(s);
}
}
// 女生名字(去重)
ArrayList<String> femaleNameList = new ArrayList<>();
for (String s : femaleNameTempList) {
if (!femaleNameList.contains(s)) {
femaleNameList.add(s);
}
}
// 6. 生成数据
// 姓名(唯一)-性别-年龄
// 70 男 40 女
ArrayList<String> infoList = getInfo(familyNameList, maleNameList, femaleNameList, 70, 40);
// 7. 写出数据
BufferedWriter bw = new BufferedWriter(new FileWriter("io\\name.txt"));
for (String s : infoList) {
bw.write(s);
bw.newLine();
}
bw.close();
}
/**
* 作用:
* 从网络中爬取数据,把数据拼接成字符串返回
* 形参:
* 网址
* 返回值:
* 爬取到的所有数据
* @param url
* @return
* @throws IOException
*/
public static String webCrawler(String url) throws IOException {
// 1. 定义 StringBuilder 对象
StringBuilder sb = new StringBuilder();
// 2. 创建一个 URL 对象
URL net = new URL(url);
// 3. 链接上这个网址
// 细节:保证网络是通畅的,而且这个网址是可以访问的
URLConnection conn = net.openConnection();
// 4. 读取数据
InputStreamReader isr = new InputStreamReader(conn.getInputStream());
int ch;
while ((ch = isr.read()) != -1) {
sb.append((char) ch);
}
// 5. 释放资源
isr.close();
// 6. 返回数据
return sb.toString();
}
public static ArrayList<String> getData(String url, String regex, int index){
// 1. 创建集合存放数据
ArrayList<String> list = new ArrayList<>();
// 2. 按照正则表达式的规则去匹配数据
Pattern p = Pattern.compile(regex);
// 按照 p 的规则到 url 中匹配数据
Matcher matcher = p.matcher(url);
while(matcher.find()){
list.add(matcher.group(index));
}
return list;
}
// 截取数据
/**
* 作用:生成男生和女生的信息
* 形参:
* 参数一:装有姓氏的集合
* 参数二:装有男生名字的集合
* 参数三:装有女生名字的集合
* 参数四:男生的个数
* 参数五:女生的个数
* @param content
* @param startStr
* @param endStr
* @return
*/
public static String subStringBetween(String content, String startStr, String endStr){
int start = content.indexOf(startStr);
int end = content.indexOf(endStr, start);
if(start == -1 || end == -1) return content;
return content.substring(start, end);
}
// 生成数据
public static ArrayList<String> getInfo(ArrayList<String> familyNameList, ArrayList<String> maleNameList, ArrayList<String> femaleNameList
, int maleCount, int femaleCount){
// 1. 生成不重复的姓名
HashSet<String> maleNameSet = new HashSet<>();
HashSet<String> femaleNameSet = new HashSet<>();
Random r = new Random();
while(maleNameSet.size() < maleCount){
String family = familyNameList.get(r.nextInt(familyNameList.size()));
String name = maleNameList.get(r.nextInt(maleNameList.size()));
String fullName = family + name;
maleNameSet.add(fullName);
}
while(femaleNameSet.size() < femaleCount){
// 随机姓氏 + 随机女生名字
String family = familyNameList.get(r.nextInt(familyNameList.size()));
String name = femaleNameList.get(r.nextInt(femaleNameList.size()));
String fullName = family + name;
femaleNameSet.add(fullName);
}
ArrayList<String> nameList = new ArrayList<>();
for (String s : maleNameSet) {
// [18 ~ 27]
// -18 0~9
// 尾巴 + 1 9 + 1 = 10
nameList.add(s + "-男-" + (r.nextInt(10) + 18));
}
for (String s : femaleNameSet) {
nameList.add(s + "-女-" + (r.nextInt(8) + 18));
}
return nameList;
}
}
利用 Hutool 包完成
package com.faxont.com.practice;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.http.HttpUtil;
import java.util.ArrayList;
import java.util.List;
import static com.faxont.com.practice.Practice1.getInfo;
import static com.faxont.com.practice.Practice1.subStringBetween;
public class Practice2 {
public static void main(String[] args) {
/*
制造假数据:
获取姓氏:
https://hanyu.baidu.com/shici/detail?pid=0b2f26d4c0ddb3ee693fdb1137ee1b0d&from=kg0
获取男生名字:
http://www.haoming8.cn/baobao/10881.html
获取女生名字:
http://www.haoming8.cn/baobao/7641.html
*/
// 1. 定义变量记录网址
String familyName = "https://hanyu.baidu.com/shici/detail?pid=0b2f26d4c0ddb3ee693fdb1137ee1b0d&from=kg0";
String maleName = "http://www.haoming8.cn/baobao/10881.html";
String femaleName = "http://www.haoming8.cn/baobao/7641.html";
// 2. 利用 HuTool 包获取字符串数据
String familyNameStr = HttpUtil.get(familyName);
String maleNameStr = HttpUtil.get(maleName);
String femaleNameStr = HttpUtil.get(femaleName);
// 3. 截取数据
familyNameStr = subStringBetween(familyNameStr, "赵钱孙李", "百家姓终");
maleNameStr = subStringBetween(maleNameStr, "龙宝宝起名男宝免费", "宝宝起名注意事项");
femaleNameStr = subStringBetween(femaleNameStr, "好听有诗意的女孩名字大全", "相关文章");
// 4. 利用正则表达式获取数据
List<String> familyNameTempList = ReUtil.findAll("([\\u4E00-\\u9FA5]{4})(?=,|。|$)", familyNameStr, 1);
List<String> maleNameTempList = ReUtil.findAll("([\\u4E00-\\u9FA5]{2})(?=、|。|$)", maleNameStr, 1);
List<String> femaleNameTempList = ReUtil.findAll("([\\u4E00-\\u9FA5]{2})(?=[ \\r\\n]|$)", femaleNameStr, 1);
// 5. 处理数据
// 姓氏
ArrayList<String> familyNameList = new ArrayList<>();
for (String s : familyNameTempList) {
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
familyNameList.add(String.valueOf(c));
}
}
// 男生名字(去重)
ArrayList<String> maleNameList = new ArrayList<>();
for (String s : maleNameTempList) {
if (!maleNameList.contains(s)) {
maleNameList.add(s);
}
}
// 女生名字(去重)
ArrayList<String> femaleNameList = new ArrayList<>();
for (String s : femaleNameTempList) {
if (!femaleNameList.contains(s)) {
femaleNameList.add(s);
}
}
// 6. 生成数据
// 姓名(唯一)-性别-年龄
// 70 男 40 女
ArrayList<String> infoList = getInfo(familyNameList, maleNameList, femaleNameList, 70, 40);
// 7. 写出数据
// 细节:
// HuTool 包的相对路径,不是相对于当前项目而言的,而是相对 class 文件而言的
FileUtil.writeLines(infoList, "name.txt", "UTF-8");
}
}
练习:随机点名器1
需求:
有一个文件里面存储了班级同学的信息,每一个信息占一行
格式为:张三-男-23
要求通过程序实现随机点名器
运行效果:
第一次运行程序:随机同学姓名1(只显示名字)
第二次运行程序:随机同学姓名2(只显示名字)
第三次运行程序:随机同学姓名3(只显示名字)
...
package com.faxont.com.practice;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class Practice3 {
public static void main(String[] args) {
/*
需求:
有一个文件里面存储了班级同学的信息,每一个信息占一行
格式为:张三-男-23
要求通过程序实现随机点名器
运行效果:
第一次运行程序:随机同学姓名1(只显示名字)
第二次运行程序:随机同学姓名2(只显示名字)
第三次运行程序:随机同学姓名3(只显示名字)
...
*/
File file = new File("io\\name.txt");
getRandomName1(file);
getRandomName1(file);
getRandomName1(file);
getRandomName1(file);
}
public static void getRandomName1(File file){
List<String> list = new ArrayList<>();
try(BufferedReader br = new BufferedReader(new FileReader(file))){
String line;
while ((line = br.readLine()) != null){
list.add(line.trim());
}
if(list.isEmpty()){
throw new RuntimeException("文件为空");
}
Random r = new Random();
int index = r.nextInt(list.size());
String randomInfo = list.get(index);
String randomName = randomInfo.substring(0, 3);
System.out.println(randomName);
}catch (IOException e){
e.printStackTrace();
}
}
}
练习:随机点名器2
需求:
一个文件里面存储了班级同学的信息,每一个学生信息占一行
格式为:张三-男-23
要求通过程序实现随机点名器
运行效果:
70%的概率随机到男生
30%的概率随机到女生
总共随机100万次,统计结果
注意观察:看生成男生和女生的比例是不是接近于7:3
package com.faxont.com.practice;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class Practice4 {
public static void main(String[] args) {
/*
需求:
一个文件里面存储了班级同学的信息,每一个学生信息占一行
格式为:张三-男-23
要求通过程序实现随机点名器
运行效果:
70%的概率随机到男生
30%的概率随机到女生
总共随机100万次,统计结果
注意观察:看生成男生和女生的比例是不是接近于7:3
*/
File file = new File("io\\name.txt");
for (int i = 0; i < 10; i++) {
getRandomName2(file);
}
}
public static void getRandomName2(File file){
List<String> maleList = new ArrayList<>();
List<String> femaleList = new ArrayList<>();
try(BufferedReader br = new BufferedReader(new FileReader(file))){
String line;
while((line = br.readLine()) != null){
String[] info = line.split("-");
String name = info[0];
String sex = info[1];
if("男".equals(sex)){
maleList.add(name);
}else if("女".equals(sex)){
femaleList.add(name);
}
}
if(maleList.isEmpty() && femaleList.isEmpty()){
throw new RuntimeException("文件为空");
}
Random r = new Random();
String randomName;
int rate = r.nextInt(10);
if(rate < 7){
if(maleList.isEmpty()){
System.out.println("没有男生了");
return;
}
int index = r.nextInt(maleList.size());
randomName = maleList.get(index);
System.out.println("随机到男生:" + randomName);
}else{
if(femaleList.isEmpty()){
System.out.println("没有女生了");
return;
}
int index = r.nextInt(femaleList.size());
randomName = femaleList.get(index);
System.out.println("随机到女生:" + randomName);
}
}catch(IOException e){
e.printStackTrace();
}
}
}
练习:随机点名器3
需求:
一个文件里面存储了班级同学的姓名,每一个姓名占一行
要求通过程序实现随机点名器
第三次必定是张三同学
运行效果:
第一次运行程序:随机同学姓名1
第二次运行程序:随机同学姓名2
第三次运行程序:张三
...
package com.faxont.com.practice;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class Practice5 {
public static void main(String[] args) {
/*
需求:
一个文件里面存储了班级同学的姓名,每一个姓名占一行
要求通过程序实现随机点名器
第三次必定是张三同学
运行效果:
第一次运行程序:随机同学姓名1
第二次运行程序:随机同学姓名2
第三次运行程序:张三
...
*/
File dataFile = new File("io\\name.txt");
getRandomName3(dataFile, "于弘雪");
}
public static void getRandomName3(File File, String name){
// 1. 先读取姓名文件,获取到所有的姓名
List<String> nameList = new ArrayList<>();
try(BufferedReader nameReader = new BufferedReader(new FileReader(File))){
String line;
while ((line = nameReader.readLine()) != null){
nameList.add(line.trim());
}
}catch (IOException e){
e.printStackTrace();
}
if(nameList.isEmpty()){
throw new RuntimeException("文件为空");
}
// 2. 读取计数文件,如果没有就创建
File countFile = new File("io\\" + name + "_count.txt");
int count = 1;
try(BufferedReader countReader = new BufferedReader(new FileReader(countFile))){
String line = countReader.readLine();
if(line != null){
count = Integer.parseInt(line);
}
}catch (FileNotFoundException e){
System.out.println("文件不存在,创建文件");
}catch (IOException e){
e.printStackTrace();
}
String result;
if(count == 3){
result = name;
}else{
// 3. 随机获取一个姓名
Random r = new Random();
int index = r.nextInt(nameList.size());
String randomInfo = nameList.get(index);
String randomName = randomInfo.substring(0, 3);
result = randomName;
}
System.out.println("第" + count + "次运行程序:" + result);
try(BufferedWriter countWriter = new BufferedWriter(new FileWriter(countFile))){
countWriter.write(String.valueOf(count + 1));
}catch (IOException e){
e.printStackTrace();
}
}
}
练习:随机点名器4
需求:
一个文件里面存储了班级同学的姓名,每一个姓名占一行
要求通过程序实现随机点名器
运行效果:
点到过的学生不会再被点到
如果班级中所有的学生都点完了,需要自动重新开启第二轮点名
细节1:假设班级有10个学生,每一轮中每一位学生只能被点到一次,程序运行10次,第一轮结束
细节2:第11次运行的时候,我们自己不需要手动操作本地文件,要求程序自动开始第二轮点名
package com.faxont.com.practice;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class Practice6 {
private static final String COUNT_FILE_NAME = "io\\count.txt";
private static final String NAME_FILE_NAME = "io\\name.txt";
private static final String USED_FILE_NAME = "io\\used.txt";
private static final Random random = new Random();
public static void main(String[] args) {
/*
需求:
一个文件里面存储了班级同学的姓名,每一个姓名占一行
要求通过程序实现随机点名器
运行效果:
点到过的学生不会再被点到
如果班级中所有的学生都点完了,需要自动重新开启第二轮点名
细节1:假设班级有10个学生,每一轮中每一位学生只能被点到一次,程序运行10次,第一轮结束
细节2:第11次运行的时候,我们自己不需要手动操作本地文件,要求程序自动开始第二轮点名
*/
File dataFile = new File("io\\name.txt");
for (int i = 0; i < 111; i++) {
getRandomName4(dataFile);
}
}
public static void getRandomName4(File File){
List<String> nameList = readList(NAME_FILE_NAME);
List<String> usedList = readList(USED_FILE_NAME);
List<String> nonUsedList = new ArrayList<>(nameList);
nonUsedList.removeAll(usedList);
if(nonUsedList.size() == 0){
System.out.println("所有人已点完,接下来进行下一轮点名");
clearFile(USED_FILE_NAME);
usedList.clear();
nonUsedList = new ArrayList<>(nameList);
}
int index = random.nextInt(nonUsedList.size());
String Info = nonUsedList.get(index);
String name = Info.split("-")[0];
int count = readCount();
writeCount(++count);
System.out.println("第" + count + "轮点到:" + name);
usedList.add(Info);
writeList(USED_FILE_NAME, usedList);
}
// 读取文件到集合
private static List<String> readList(String path){
List<String> list = new ArrayList<>();
try(BufferedReader reader = new BufferedReader(new FileReader(path))){
String line;
while ((line = reader.readLine()) != null){
if (!line.isBlank()) list.add(line.trim());
}
}catch (IOException ignored){}
return list;
}
// 写入集合到文件
private static void writeList(String path, List<String> list){
try(BufferedWriter writer = new BufferedWriter(new FileWriter(path))){
for (String s : list) {
writer.write(s);
writer.newLine();
}
}catch (IOException e){
e.printStackTrace();
}
}
// 读取总次数
private static int readCount(){
try(BufferedReader reader = new BufferedReader(new FileReader(COUNT_FILE_NAME))){
String line = reader.readLine();
return line == null ? 0 : Integer.parseInt(line);
}catch (IOException e){
e.printStackTrace();
}
return 0;
}
// 写入总次数
private static void writeCount(int count){
try(BufferedWriter writer = new BufferedWriter(new FileWriter(COUNT_FILE_NAME))){
writer.write(String.valueOf(count));
}catch (IOException e){
e.printStackTrace();
}
}
// 清空文件
private static void clearFile(String path){
try(BufferedWriter writer = new BufferedWriter(new FileWriter(path))){
writer.write("");
}catch (IOException e){
e.printStackTrace();
}
}
}
练习:随机点名器5
先在 txt 文件中事先准备好一些学生信息,每一个学生的信息独占一行。
要求1:每次被点到的学生,再次被点到的概率在原先的基础上降低一半。
举例:80个学生,点名5次,每次都点到小A,概率变化情况如下:
第一次每人概率:1.25%
第二次小A概率:0.625% 其它学生概率:1.2579%
第三次小A概率:0.3125% 其它学生概率:1.261867%
第四次小A概率:0.15625% 其它学生概率:1.2638449%
第五次小A概率:0.078125% 其它学生概率:1.26483386%
提示:本题的核心就是带权重的随机
package com.faxont.com.practice;
import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Practice7 {
public static void main(String[] args) {
/*
先在 txt 文件中事先准备好一些学生信息,每一个学生的信息独占一行。
要求1:每次被点到的学生,再次被点到的概率在原先的基础上降低一半。
举例:80个学生,点名5次,每次都点到小A,概率变化情况如下:
第一次每人概率:1.25%
第二次小A概率:0.625% 其它学生概率:1.2579%
第三次小A概率:0.3125% 其它学生概率:1.261867%
第四次小A概率:0.15625% 其它学生概率:1.2638449%
第五次小A概率:0.078125% 其它学生概率:1.26483386%
提示:本题的核心就是带权重的随机
*/
// 1. 把所有的学生信息读取到内存中
List<Student> studentList = new ArrayList<>();
try(BufferedReader br = new BufferedReader(new FileReader("io\\name.txt"))){
String line;
while ((line = br.readLine()) != null){
String[] split = line.split("-");
Student student = new Student(split[0], split[1], Integer.parseInt(split[2]), Double.parseDouble(split[3]));
studentList.add(student);
}
}catch (IOException e){
e.printStackTrace();
}
// 2. 计算权重的总和
double sum = studentList.stream().mapToDouble(Student::getWeight).sum();
// 3. 计算每一个人权重的实际占比
double[] ratio = new double[studentList.size()];
for (int i = 0; i < studentList.size(); i++) {
ratio[i] = studentList.get(i).getWeight() / sum;
}
// 4. 计算每一个人的权重范围
double[] range = new double[studentList.size()];
range[0] = ratio[0];
for (int i = 1; i < studentList.size(); i++) {
range[i] = range[i - 1] + ratio[i];
}
// 5. 随机抽取
// 获取一个0.0 ~ 1.0 的随机数
double random = Math.random();
// 二分查找法
// 方法返回:-插入点 - 1
// 插入点 = -结果 - 1
int index = -Arrays.binarySearch(range, random) - 1;
System.out.println(studentList.get(index));
// 6. 改变权值
studentList.get(index).setWeight(studentList.get(index).getWeight() / 2);
// 7. 写出数据
try(BufferedWriter bw = new BufferedWriter(new FileWriter("io\\name.txt"))){
for (Student student : studentList) {
bw.write(student.toString());
bw.newLine();
}
}catch (IOException e){
e.printStackTrace();
}
}
}
package com.faxont.com.practice;
public class Student {
private String name;
private String sex;
private int age;
private double weight;
public Student() {
}
public Student(String name, String sex, int age, double weight) {
this.name = name;
this.sex = sex;
this.age = age;
this.weight = weight;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return sex
*/
public String getSex() {
return sex;
}
/**
* 设置
* @param sex
*/
public void setSex(String sex) {
this.sex = sex;
}
/**
* 获取
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}
/**
* 获取
* @return weight
*/
public double getWeight() {
return weight;
}
/**
* 设置
* @param weight
*/
public void setWeight(double weight) {
this.weight = weight;
}
public String toString() {
return name + "-" + sex + "-" + age + "-" + weight;
}
}
练习:登入注册1
需求:写一个登陆小案例
步骤:
将正确的用户名和密码手动保存在本地的 userinfo.txt 文件中
保存格式为:username=zhangsan&password=123
让用户键盘录入用户名和密码
比较用户录入的和正确的用户名密码是否一致
如果一致则打印登陆成功
如果不一致则打印登陆失败
package com.faxont.com.practice;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.Scanner;
public class Practice8 {
public static void main(String[] args) {
/*
需求:写一个登陆小案例
步骤:
将正确的用户名和密码手动保存在本地的 userinfo.txt 文件中
保存格式为:username=zhangsan&password=123
让用户键盘录入用户名和密码
比较用户录入的和正确的用户名密码是否一致
如果一致则打印登陆成功
如果不一致则打印登陆失败
*/
try(BufferedReader br = new BufferedReader(new FileReader("io\\userinfo.txt"));
Scanner scanner = new Scanner(System.in);
){
String line = br.readLine();
String[] info = line.split("&");
String username = info[0].split("=")[1];
String password = info[1].split("=")[1];
while(true){
System.out.println("请输入用户名:");
String name = scanner.nextLine();
System.out.println("请输入密码:");
String pwd = scanner.nextLine();
if (username.equals(name) && password.equals(pwd)){
System.out.println("登陆成功");
break;
}else {
System.out.println("用户名或密码错误,请重新输入!");
}
}
}catch (IOException e){
e.printStackTrace();
}
}
}
练习:登录注册2
需求:写一个登陆小案例(添加锁定账号功能)
步骤:将正确的用户名和密码手动保存在本地的 userinfo.txt 文件中
保存格式为:username=zhangsan&password=123&count=0
让用户键盘录入用户名和密码
比较用户录入的和正确的用户名密码是否一致
如果一致则打印登陆成功
如果不一致则打印登陆失败,连续输错三次被锁定
package com.faxont.com.practice;
import java.io.*;
import java.util.Scanner;
public class Practice9 {
public static void main(String[] args) {
/*
需求:写一个登陆小案例(添加锁定账号功能)
步骤:将正确的用户名和密码手动保存在本地的 userinfo.txt 文件中
保存格式为:username=zhangsan&password=123&count=0
让用户键盘录入用户名和密码
比较用户录入的和正确的用户名密码是否一致
如果一致则打印登陆成功
如果不一致则打印登陆失败,连续输错三次被锁定
*/
try(BufferedReader br = new BufferedReader(new FileReader("io\\userinfo.txt"));
Scanner sc = new Scanner(System.in)
){
String line = br.readLine();
String[] info = line.split("&");
String username = info[0].split("=")[1];
String password = info[1].split("=")[1];
int count = Integer.parseInt(info[2].split("=")[1]);
while(count < 3){
System.out.println("请输入用户名:");
String name = sc.nextLine();
System.out.println("请输入密码:");
String pwd = sc.nextLine();
if (username.equals(name) && password.equals(pwd)){
System.out.println("登陆成功");
count = 0;
break;
}else {
System.out.println("用户名或密码错误,请重新输入!");
System.out.println("您已输入错误次数:" + (count + 1));
System.out.println("剩余次数:" + (2 - count));
count++;
}
}
if (count == 3){
System.out.println("账号被锁定!");
}
try(BufferedWriter bw = new BufferedWriter(new FileWriter("io\\userinfo.txt"))){
bw.write("username=" + username + "&password=" + password + "&count=" + count);
}catch (IOException e){
e.printStackTrace();
}
}catch (IOException e){
e.printStackTrace();
}
}
}
软件配置
配置文件
好处1:可以把软件的设置永久化存储
好处2:如果我们要修改参数,不需要改动代码,直接修改配置文件就可以了
常见的配置文件:XML,properties,ini,YAML
properties
properties 是一个双列集合集合,拥有 Map 集合所有的特点。
重点:有一些特有的方法,可以把集合中的数据,按照键值对的形式写到配置文件当中。
也可以把配置文件中的数据读取到集合中来
properties 作为双列集合的方法
package com.faxont.com.practice;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
public class Practice10 {
public static void main(String[] args) {
/*
Properties 作为 Map 集合的操作
*/
// 1. 创建集合对象
Properties p = new Properties();
// 2. 添加数据
// 细节:虽然我们可以往 Properties 当中添加任意类型的数据,但是一般只会往里面添加字符串类型的整数
p.setProperty("aaa", "111");
p.setProperty("bbb", "222");
p.setProperty("ccc", "333");
p.setProperty("ddd", "444");
// 3. 遍历集合
// Set<Object> keys = p.keySet();
// for (Object key : keys) {
// String value = p.getProperty(key.toString());
// System.out.println(key + "=" + value);
// }
Set<Map.Entry<Object, Object>> entries = p.entrySet();
for (Map.Entry<Object, Object> entry : entries) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
}
}
properties 中特有的读写方法
package com.faxont.com.practice;
import java.io.*;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
public class Practice11 {
public static void main(String[] args) {
/*
Properties 跟 IO 流结合的操作
*/
// 1. 创建集合对象
Properties p = new Properties();
// 2. 添加数据
p.setProperty("aaa", "111");
p.setProperty("bbb", "222");
p.setProperty("ccc", "333");
p.setProperty("ddd", "444");
// 3. 把集合中的数据以键值对的形式写到本地文件当中
// 特有方法
try(BufferedWriter bw = new BufferedWriter(new FileWriter("p.properties"))){
p.store(bw, "this is a properties file");
}catch (IOException e){
e.printStackTrace();
}
// 原来的方法
// try(BufferedWriter bw = new BufferedWriter(new FileWriter("p.properties"))){
// Set<Map.Entry<Object, Object>> entries = p.entrySet();
// for (Map.Entry<Object, Object> entry : entries) {
// bw.write(entry.getKey() + "=" + entry.getValue());
// bw.newLine();
// }
// }catch (IOException e){
// e.printStackTrace();
// }
}
}
package com.faxont.com.practice;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;
public class Practice12 {
public static void main(String[] args) {
/*
Properties 读取文件
*/
// 1. 创建集合对象
Properties p = new Properties();
// 2. 读取文件
try(BufferedReader bf = new BufferedReader(new FileReader("p.properties"))){
p.load(bf);
System.out.println(p);
}catch (IOException e){
e.printStackTrace();
}
}
}

浙公网安备 33010602011771号