Java I/O编程
字节输出类 outputStream
字节的数据是以byte
类型为主实现的操作, 在进行字节内容输出的时候可以使用outputStream
类完成, 这个类的基本定义如下:
public abstract class OutputStream extends Object implements Closeable, Flushable
outputStream的继承
outputStream
类定义的事一个公共的输出操作标准, 而在这个操作标准里一共定义了3个内容输出的方法:
- 输出单个字节数据
public abstract void write(int b) throws IOException;
- 输出一组字节数据
public void write(byte[] b) throws IOException;
public void write(byte[] b, int off, int len) throws IOException;
FileOutputStream
类的构造方法
- 覆盖
public FileOutputStream(File file) throws FileNotFoundException;
- 追加
public FileOutputStream(File file, boolean append) throws FileNotFoundException;
往D盘写入一个txt文件, 并往文件里写入'hello world'
public class Main {
public static void main(String[] args) throws Exception {
File file = new File("D:" + File.separator + "hello" +
File.separator + "world.txt"); // 指定文件要操作的目录
if (!file.getParentFile().exists()) { // 如果文件不存在
file.getParentFile().mkdirs(); // 那么创建父目录
}
OutputStream outputStream = new FileOutputStream(file); // 通过子类实例化对象
String str = "hello world";
outputStream.write(str.getBytes()); // 将字符串转为字节并输出
outputStream.close(); // 关闭资源
}
}
将上面代码改为自动关闭
public class Main {
public static void main(String[] args) throws Exception {
File file = new File("D:" + File.separator + "hello" +
File.separator + "world.txt"); // 指定文件要操作的目录
if (!file.getParentFile().exists()) { // 如果文件不存在
file.getParentFile().mkdirs(); // 那么创建父目录
}
try (OutputStream outputStream = new FileOutputStream(file)) { // 通过子类实例化对象
String str = "hello world";
outputStream.write(str.getBytes()); // 将字符串转为字节并输出
} catch (IOException e) {
e.printStackTrace();
}
}
}
将写入文件改为运行多次, 内容追加
public class Main {
public static void main(String[] args) throws Exception {
File file = new File("D:" + File.separator + "hello" +
File.separator + "world.txt"); // 指定文件要操作的目录
if (!file.getParentFile().exists()) { // 如果文件不存在
file.getParentFile().mkdirs(); // 那么创建父目录
}
try (OutputStream outputStream = new FileOutputStream(file, true)) { // 通过子类实例化对象
String str = "hello world";
outputStream.write(str.getBytes()); // 将字符串转为字节并输出
} catch (IOException e) {
e.printStackTrace();
}
}
}
字节输入类 inputStream
InputStream
类主要实现的就是字节数据读取, 该类定义如下:
public abstract class InputStream extends Object implements Closeale
读取单个字节数据, 如果现在已经读取到底了返回-1
public abstract int read() throws IOException
public int read(byte[] b) throws IOException
读取一组字节数据(只占数组的部分)
public int read(byte[] b, int off, int len) throws IOException;
InputStream
类是一个抽象类, 应该依靠它的子类来实例化对象, 如果要从文件读取
一定使用FileInputStream
子类.
读取数据
public class Main {
public static void main(String[] args) throws Exception {
File file = new File("D:" + File.separator + "hello" +
File.separator + "world.txt");
InputStream inputStream = new FileInputStream(file);
byte data [] = new byte[1024]; // 开辟一个缓冲区读取数据
int len = inputStream.read(data); // 读取数据, 数据全部保存在字节数组中, 返回读取的个数
System.out.println("[" + new String(data, 0, len) + "]");
inputStream.close();
}
}
对于直接输入流最麻烦的就是: 使用read()
方法读取的时候只能够以字节数组为主进行接收,在JDK1.9
推出了一个新的方法, 可以一次读取出所有的数据:
public byte[] readAllBytes() throws IOException;
应用
public class Main {
public static void main(String[] args) throws Exception {
File file = new File("D:" + File.separator + "hello" +
File.separator + "world.txt");
InputStream inputStream = new FileInputStream(file);
byte data [] = inputStream.readAllBytes(); // 读取全部数据
System.out.println("[" + new String(data) + "]");
inputStream.close();
}
}
需要注意:
- 如果文件过大, 可能出现卡死的情况, 一般用于不大于10K的文本文件.
Writer字符输出流
定义
public abstract class Writer extends Object implements Appendable, Closeable, Flushable;
Writer的继承关系
输出字符数据
public void write(char[] cbuf) throws IOException;
输出字符串
public void write(String str) throws IOException;
创建一个文本写入hello world
public class Main {
public static void main(String[] args) throws Exception {
File file = new File("D:" + File.separator + "hello" +
File.separator + "world.txt");
if (!file.getParentFile().exists()) { // 如果文件不存在
file.getParentFile().mkdirs(); // 创建一个文件
}
Writer w = new FileWriter(file);
String str = "hello world2222 \n";
w.write(str);
w.close();
}
}
追加
- 方式1:
Writer w = new FileWriter(file, true);
- 方式2
w.append("dshsjh");
Reader字符输入流
定义
public abstract class Reader extends Object implements Readable, Closeable
Reader的继承关系
接收数据
public int read(char[] cbuf) throws IOException;
实现数据读取
public class Main {
public static void main(String[] args) throws Exception {
File file = new File("D:" + File.separator + "hello" +
File.separator + "world.txt");
if (file.getParentFile().exists()) { // 如果文件存在
Reader reader = new FileReader(file);
char data[] = new char[1024];
int len = reader.read(data);
System.out.println("输出数据[" + new String(data, 0, len) + "]");
reader.close();
}
}
}
转换流
转换流指的是字节流和字符流相互转换. java.io
包提供了两个类
InputStreamReader
OutputStreamWriter
类 | OutputStreamWriter | InputStreamReader |
---|---|---|
定义 | public class OutputStreamWriter extends Writer |
public class InputStreamReader extends Reader |
构造方法 | public OutputStreamWriter (OutputSteam out) |
public InputStreamReader(InputSteam in) |
通过类的继承结构和构造方法可以发现, 所谓的转换处理就是将接收到的字节流向上转型转换成字符流.
public class Main {
public static void main(String[] args) throws Exception {
File file = new File("D:" + File.separator + "hello" +
File.separator + "world.txt");
if (!file.getParentFile().exists()) { // 如果文件存在
file.getParentFile().exists();
}
OutputStream output = new FileOutputStream(file);
Writer out = new OutputStreamWriter(output); // 字节流变成字符流
out.write("hello world"); // 直接输出字符串, 字符流适合处理中文
out.close();
}
}
FileWriter的继承结构
FileReader的继承结构
文件传输流程
字符编码
计算机只认识0, 1的数据, 如果想描述一些文字的编码, 就需要对二进制的数据进行一些组合, 如果想正确的显示出一些内容, 那么就需要有对应的解码, 所以编码和解码需要进行同一套的标准
常用的编码
-
GBK/GB2312
: 国标编码, 可以描述中文信息, 其中GB2312
只描述简体中文, 而GBK
包含简体中文和繁体中文. -
ISO8859-1
: 国际通用编码, 可以用其描述所有的字母信息, 如果是象形文字则需要进行编码处理 -
UNICODE
: 采用十六进制的方式存储, 可以描述所有的文字信息, 但是比较大; -
UTF
: 象形文字部分使用十六进制编码, 而普通的文字采用ISO8859-1
的编码, 好处是能够快速传输, 节约带宽, 所以UTF
编码也成为了在开发之中首选的编码, 有UTF-16
和UTF-8
两种编码, 主要使用UTF-8
进行编码.
内存操作流
正常的IO操作都是以文件为终端, 所以一定会有文件的产生,
正常的IO文件操作流
但是如果不想产生文件进行IO操作(临时文件), 那么内存就是IO操作的终端.
内存操作流
Java中提供两种内存操作流
- ByteArrayInputStream(写)
- 构造方法
public ByteArrayInputStream(byte[] buf);
- ByteArrayOutputStream(读)
- 构造方法
public ByteArrayOutputStream()
- 读取保存在内存流中所有的数据
- 获取数据
public byte[] toByteArray();
- 使用字符串的形式来获取
public String toString();
利用内存流实现一个小写字母转大写字母的操作
public class Main {
public static void main(String[] args) throws Exception {
String str = "hello world";
InputStream input = new ByteArrayInputStream(str.getBytes()); // 将数据保存到内存中
OutputStream out = new ByteArrayOutputStream();
int data = 0;
while ((data = input.read()) != -1) { // 每次读一个字节
out.write(Character.toUpperCase(data)); // 保存数据
}
System.out.println(out.toString());
input.close();
out.close();
}
}
管道流
管道流主要的功能是实现两个线程之间的IO处理操作.
管道流
字节管道流
PipedOutputStream
PipedInputStream
-
链接处理:
public void connect(PipedInputStream snk) throws IOException;
字符管道流
PipedWriter
PipedReader
-
链接处理:
public void connect(PipedReader snk) throws IOException;
实现管道操作
class SendThread implements Runnable {
private PipedOutputStream output;
public SendThread() {
this.output = new PipedOutputStream();
}
public PipedOutputStream getOutput() {
return this.output;
}
@Override
public void run() {
try {
this.output.write((Thread.currentThread().getName() + "发送消息\n").getBytes());
} catch (IOException e) {
e.printStackTrace();
}
try {
this.output.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
class ReceiveThread implements Runnable {
private PipedInputStream input;
@Override
public void run() {
byte data[] = new byte[1024];
try {
int len = this.input.read(data);
System.out.println(Thread.currentThread().getName() + ": 接受消息" + new String(data, 0, len) + "\n");
} catch (IOException e) {
e.printStackTrace();
}
try {
this.input.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public ReceiveThread() {
this.input = new PipedInputStream();
}
public PipedInputStream getInput() {
return this.input;
}
}
public class Main {
public static void main(String[] args) throws Exception {
SendThread send = new SendThread();
ReceiveThread receive = new ReceiveThread();
send.getOutput().connect(receive.getInput()); // 进行管道连接
new Thread(receive, "接受").start();
new Thread(send, "发送").start();
}
}
RandomAccessFile(随机存储)
对于文件内容的处理操作主要是通过InputStream(Reader)
和OutputStream(Writer)
进行操作, 但是只能将数据部分部分的读取出来, 如果现在有一个20G大小的文件需要读取, 传统的读取方式无法满足, 这个时候就需要使用RandomAccessFile
, 能够实现文件跳跃式的读取(RandomAccessFile使用的前提需要有一个完善的保存形式, 数据保存位数都要提前确定好)
RandomAccessFile
定义了如下的操作方法:
- 构造方法
public RandomAccessFile(File file, String mode) throws FileNotFoundException;
- 文件处理模式:
r
,rw
;
打印流
所有的文件输出操作都要依靠OutputStream
类完成, 但是OutputStream
类有一个很大的缺点, 所有的数据一定要转为字节数组以后才可以输出, 如果现在要输出long
, doule
, Date
, 那么需要将数据转为字节数组,
设计一个打印流
class PrintUntil implements AutoCloseable {
private OutputStream output;
public PrintUntil(OutputStream output) {
this.output = output;
}
public void print(String str) {
try {
this.output.write(str.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
public void print(long num) {
this.print(String.valueOf(num));
}
public void println(String str) {
this.print(str + "\n");
}
public void println(long num) {
this.println(String.valueOf(num));
}
@Override
public void close() throws Exception {
this.output.close();
}
}
public class Main {
public static void main(String[] args) throws Exception {
File file = new File("d:" + File.separator + "hello.txt");
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
PrintUntil print = new PrintUntil(new FileOutputStream(file));
print.println("你会唱小星星吗");
print.println(20);
print.close();
}
}
输出结果
java自带的打印流
类 | PrintStream | PrintWriter |
---|---|---|
继承关系 | public class PrintStream extends FilterOutputStream implements Appendable, Closeable |
public class printWriter extends Writer |
构造方法 | public PrintStream(OutputStream out) |
public PrintWriter(OutputStream out) public PrintWriter(Writer out)
|
使用PrintWriter实现数据的输出操作
public class Main {
public static void main(String[] args) throws Exception {
File file = new File("d:" + File.separator + "hello.txt");
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
PrintWriter print = new PrintWriter(new FileOutputStream(file));
print.println("你会2唱小星星吗");
print.println(20);
print.close();
}
}
从JDK1.5
开始, printWriter
有追加的格式化支持.
public PrintWriter printf(String format, Object ... args);
格式化输出
public class Main {
public static void main(String[] args) throws Exception {
File file = new File("d:" + File.separator + "hello.txt");
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
PrintWriter print = new PrintWriter(new FileOutputStream(file));
String name = "小强";
int age = 29;
double salary = 28298239.2222;
print.printf("姓名:%s; 年龄:%d; 收入:%7.2f", name, age, salary);
print.close();
}
}
比起直接使用OutputStream
类, 那么使用PrintWriter
, PrintStream
类更加简单, 使用输出的时候最好使用打印流
System对于IO的支持
标准输出(显示器)
public static final PrintStream out;
错误输出
public static final PrintStream err;
标准输入(键盘)
public static final InputStream in;
err和out的区别
public class Main {
public static void main(String[] args) throws Exception {
String eg = "a";
try {
System.out.println(Integer.parseInt(eg));
} catch (Exception e) {
System.out.println(e);
System.err.println(e);
}
}
}
在IDEA中, out
是显示的白色, err
显示的是红色.
在设计的最初, out
是输出想让用户看到的信息, err
是不想让用户看到的信息
修改输出的位置
- 修改
out
的输出位置
public static void setOut(PrintStream out);
- 修改
err
的输出位置
public static void setErr(PrintStream err);
修改System.err的位置
public class Main {
public static void main(String[] args) throws Exception {
System.setErr(new PrintStream(new FileOutputStream(new File("d:" + File.separator + "err.txt"))));
String eg = "a";
try {
System.out.println(Integer.parseInt(eg));
} catch (Exception e) {
System.out.println(e);
System.err.println(e);
}
}
}
这样错误日志就输出到了D盘的err.txt
文本内, 所以修改System.err
的位置一般错误日志的收集
BufferedReader类
BufferedReader
类是一个缓冲输入流, 利用这类可以实现键盘输入数据的标准化定义
读取一行数据
public String readLine() throws IOException;
BufferedReader的继承结构
使用BufferedReader实现键盘输入年龄并校验
public class Main {
public static void main(String[] args) throws Exception {
BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
System.out.println("请输入您的年龄:");
String msg = input.readLine();
if (msg.matches("\\d{1,3}")) {
System.out.println("您的年龄是:" + msg);
} else {
System.out.println("请确认您听明白了我的意思!");
}
}
}
Scanner扫描流
Scanner
是在JDK1.5
以后推出的, 是BufferedReader
的替代类.
构造
public Scanner(InputStream source)
判断是否有数据
public boolean hasNext();
取出数据
public String next()
设置分隔符
public Scanner userDelimiter(String pattern)
Scanner
可以和正则直接做自定义验证
获取输入的是否为生日
public class Main {
public static void main(String[] args) throws Exception {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入您的生日:");
if (scanner.hasNext("\\d{4}-\\d{1,2}-\\d{1,2}")) {
String str = scanner.next();
System.out.println("输入的生日为:" + new SimpleDateFormat("yyyy-MM-dd").parse(str));
}
scanner.close();
}
}
读取某个文件里的文本内容
public class Main {
public static void main(String[] args) throws Exception {
Scanner scanner = new Scanner(new File("d:" + File.separator + "hello.txt"));
scanner.useDelimiter("\n"); // 设置什么是换行符
if (scanner.hasNext()) {
System.out.println(scanner.next());
}
scanner.close();
}
}
对象序列化
将内存中保存的对象以二进制数据流的形式进行处理, 可以实现对象的保存或者是网络的传输.
并不是所有的对象都可以被序列化, 在Java当中有一个强制性的要求, 如果要序列化, 对象必须实现
java.io.Serializable
父接口
类名称 | 序列化:ObjectOuputStream | 反序列化ObjectInputStream |
---|---|---|
类定义 | public class ObjectOutputStream extends OutputStream implements ObjectOutput, ObjectStreamConstants |
public class ObjectInputStream extends InputStream implements ObjectInput, ObjectStreamConstants |
构造方法 | public ObjectOutputStream(OutputStream out) throws IOException |
public ObjectInputStream(InputStream in) throws IOException |
操作方法 | public final void writeObject(Object obj) throws IOException |
public final Object readObject() throws IOException, ClassNotFoundException |
实现序列化和反序列化
class Person implements Serializable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "姓名:" + this.name + ", 年纪:" + this.age;
}
}
public class Main {
private static final File SAVE_FILE = new File("D:" + File.separator + "hello.person");
// 对象反序列化
public static Object loadObject() throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(SAVE_FILE));
Object obj = ois.readObject();
ois.close();
return obj;
}
// 对象序列化
public static void saveObject(Object obj) throws Exception {
ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream(SAVE_FILE));
oss.writeObject(obj);
oss.close();
}
public static void main(String[] args) throws Exception {
// 序列化
saveObject(new Person("tinger", 39));
// 反序列化
System.out.println(loadObject());
}
}
transient关键字
默认情况下当执行了对象序列化以后会将类中的全部属性的内容进行全部的序列化. 但是很多时候可能有一些属性不需要序列化, 所以可以在对应属性的定义上使用transient
关键字.
如果将name
用transient
修饰
private transient String name;
那么name
属性的内容是不会被保留下来的, 读取出的name
的值就是null
.