Java IO

Java IO

学习目标:

  1. 掌握java.io包中类的继承关系
  2. 可以使用File类进行文件的操作。
  3. 可以使用字符流字节流操作文件内容,并区分出字符流与字节流的区别。
  4. 掌握内存操作输入、输出流的使用。
  5. 了解线程通讯流--管道流的使用。
  6. 掌握System类对IO的三个支持:System.out、System.err、System.in
  7. 可以使用打印流方便的打印输出的内容,并可以使用Java新特性格式化输出。
  8. 可以使用BufferedReader类读取缓冲区中的内容。
  9. 了解Java提供的输入工具类Scanner类的使用。
  10. 掌握数据操作流DataInputStream和DataOutputStream类的使用。
  11. 可以使用SequenceInputStream合并两个文件的内容。
  12. 可以使用压缩流完成ZIP文件格式的压缩。
  13. 了解回退流(PushbackInputStream)类的作用及操作原理。
  14. 了解字符的主要编码类型及乱码产生的原因。
  15. 掌握对象序列化的作用以及Serializable接口、Externalizable接口、transient关键字的使用。

java.io包中最重要的5个类和1个接口:

  1. 5个类:File、OutputStream、InputStream、Writer、Reader
  2. 1个接口:Serializable

IO操作类:

1.File类

1.基本介绍

File类是操作文件的类。

说明:

File类是io包中唯一一个与文件本身有关的类。File类可以进行文件的创建、删除等操作。

File类中的常用方法:

序号 方法或常量 类型 描述
1 public static final char pathSeparatorChar 常量 表示路径的分隔符(Windows是:";")
2 public static final String separator 常量 表示路径的分隔符(Windows是:"\")
3 public File(String pathname) 构造 创建File类对象,传入完整路径
4 public boolean createNewFile() throws IOException 普通 创建新文件
5 public boolean delete() 普通 删除文件
6 public boolean exists() 普通 判断文件是否存在
7 public boolean isDirectory() 普通 判断给定的路径是否是一个目录
8 public long length() 普通 返回文件大小
9 public String[] list() 普通 列出指定目录的全部内容、只是列出名称
10 public File[] listFiles() 普通 列出指定目录的全部内容、会列出路径
11 public boolean mkdir() 普通 创建一个目录
12 public boolean renameTo(File var1) 普通 为已有的文件重新命名

注意:这里常量没有使用大写;原因是java的发展历史中遗留的问题。

2.使用File类操作文件

1.创建一新文件

  1. 首先实例化File对象
  2. 使用creatNewFile()创建文件,注意:需要进行异常处理。

案例:在D:\DLU\studyNode\java\IO路径下创建一个test.txt文件。

import java.io.File;
import java.io.IOException;
public class Demo03 {
public static void main(String[] args) {
// 必须给出完整路径
File file = new File("D:\\DLU\\studyNode\\java\\IO\\test.txt");
try{
// 根据给定的路径创建新文件
file.createNewFile();
}catch (IOException e){
e.printStackTrace();
}
}
}

注意:在不同的操作系统中,路径的分割符是不同的,例如:

  • windows中使用反斜杠表示目录的分割符"\"
  • Linux中使用正斜杠表示目录的分割符"/"

为了实现java语言的可移植性,分隔符需要使用File中提供的两个常量: pathSeparatorChar和separator

改进代码:

import java.io.File;
import java.io.IOException;
public class Demo03 {
public static void main(String[] args) {
// 拼凑出可以符合操作系统的路径
String path = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.txt";
File file = new File(path);
try{
// 根据给定的路径创建新文件
file.createNewFile();
}catch (IOException e){
e.printStackTrace();
}
}
}

注意:在操作文件时一定要使用File.separator表示分隔符。

因为在程序开发中往往是在Windows下开发好之后上传到Linux中。

2.删除一个指定的文件

使用delete()方法

案例:删除文件

import java.io.File;
import java.io.IOException;
public class Demo03 {
public static void main(String[] args) {
// 拼凑出可以符合操作系统的路径
String path = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.txt";
File file = new File(path);
file.delete(); //删除文件
}
}

注意:正常的逻辑应该是在删除文件前先判断文件是否存在,如果存在则删除。

判断文件是否存在使用 exists()方法。

改进代码:

import java.io.File;
import java.io.IOException;
public class Demo03 {
public static void main(String[] args) {
// 拼凑出可以符合操作系统的路径
String path = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.txt";
File file = new File(path);
if (file.exists()){
file.delete(); //删除文件
}
}
}

3.创建一个文件夹

使用mkdir()方法

案例:创建一个文件夹

import java.io.File;
import java.io.IOException;
public class Demo03 {
public static void main(String[] args) {
// 拼凑出可以符合操作系统的路径
String path = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test";
File file = new File(path);
file.mkdir(); //创建文件夹
}
}

4.列出指定目录的全部文件

有两种方法:

  1. public String[] list():列出全部名称,返回一个字符串数组。
  2. public File[] listFiles():列出完整的路径,返回一个File对象数组。

案例: 使用list()列出一个目录中的全部内容。

import java.io.File;
import java.io.IOException;
public class Demo03 {
public static void main(String[] args) {
// 拼凑出可以符合操作系统的路径
String path = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO";
File file = new File(path);
String[] list = file.list();
for (int i = 0; i < list.length; i++) {
System.out.println(list[i]);
}
}
}

输出:

案例2:使用listFiles()方法列出一个目录中的全部内容。

import java.io.File;
import java.io.IOException;
public class Demo03 {
public static void main(String[] args) {
// 拼凑出可以符合操作系统的路径
String path = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO";
File file = new File(path);
File[] files = file.listFiles();
for (int i = 0; i < files.length; i++) {
System.out.println(files[i]);
}
}
}

输出:

5.判断一个给定的路径是否是目录

使用isDirectory()方法。

案例:判断一个给定的路径是否是目录

import java.io.File;
import java.io.IOException;
public class Demo03 {
public static void main(String[] args) {
// 拼凑出可以符合操作系统的路径
String path = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO";
File file = new File(path);
if (file.isDirectory()){
System.out.println(file.getPath() + "路径是目录。");
}else {
System.out.println(file.getPath() + "路径不是目录。");
}
}
}

6.列出指定目录的全部内容

要求列出全部内容,因为给定的目录下可能还有子文件夹,所以需要递归遍历。

import java.io.File;
import java.io.IOException;
public class Demo03 {
public static void main(String[] args) {
// 拼凑出可以符合操作系统的路径
String path = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO";
File file = new File(path);
print(file);
}
public static void print(File file){
if (file != null){
if (file.isDirectory()){
File[] files = file.listFiles();
if (files != null){
for (int i = 0; i < files.length; i++) {
print(files[i]);
}
}
}else {
System.out.println(file);
}
}
}
}

2.RandomAccessFile类

随机读写流

作用:

对文件的内容进行操作。这个类属于随机读取类,可以随机的读取一个文件中指定位置的数据。

File类只是针对文件本身进行操作。

注意:如果要实现随机读取,需要保证每个数据的长度一致。

RandomAccessFile类的常用操作方法:

序号 方法 类型 描述
1 public RandomAccessFile(File file, String mode)throws FileNotFoundException 构造 接收File类的对象,指定操作路径,但是在设置时需要设置模式,r只读;w只写;rw读写
2 public RandomAccessFile(String name, String mode) throws FileNotFoundException 构造 不在使用File类对象表示文件,而是直接输入一个固定的文件路径
3 public void close() throws IOException 普通 关闭操作
4 public int read(byte b[]) throws IOException 普通 将内容读取到一个byte数组中
5 public final byte readByte() throws IOException 普通 读取一个字节
6 public final int readInt() throws IOException 普通 从文件中读取整型数据
7 public void seek(long pos) throws IOException 普通 设置读指针的位置
8 public final void writeBytes(String s) throws IOException 普通 将一个字符串写入到文件中,按字节的方式处理
9 public final void writeInt(int v) throws IOException 普通 将一个Int型数据写入文件,长度为4
10 public int skipBytes(int n) throws IOException 普通 指针跳过指定的字节

注意:如果使用rw方式声明RandomAccessFile对象时,要写入的文件不存在,系统将自动进行创建。

1.使用RandomAccessFile类写入数据

案例:写入一下三个数据:

  • zhangsan 30
  • lisi 31
  • wangwu 32

为了保证可以进行随机读取,所有写入的名字都是8个字节,写入的数字是固定的4个字节。

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
public class Demo03 {
// 直接抛出异常,程序中可以不用在进行处理
public static void main(String[] args)throws Exception {
// 拼凑出可以符合操作系统的路径
String path = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.txt";
File file = new File(path);
// 声明一个RandomAccessFile对象
RandomAccessFile rdf = null;
// 以读写的方式打开文件
rdf = new RandomAccessFile(file,"rw");
String name = null;
int age = 0;
name = "zhangsan";
age = 30;
rdf.writeBytes(name);
rdf.writeInt(age);
name = "lisi ";
age = 31;
rdf.writeBytes(name);
rdf.writeInt(age);
name = "wangwu ";
age = 32;
rdf.writeBytes(name);
rdf.writeInt(age);
rdf.close(); //关闭文件
}
}

可以发现年龄age是不显示的

2.使用RandomAccessFile类读取数据

读取时直接使用r的模式即可,以只读的方式打开文件。

读取时所有的字符串只能按照byte数组的方式读取出来,而且所有的长度是8位。

随机读取

import java.io.File;
import java.io.RandomAccessFile;
public class Demo03 {
// 直接抛出异常,程序中可以不用在进行处理
public static void main(String[] args)throws Exception {
// 拼凑出可以符合操作系统的路径
String path = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.txt";
File file = new File(path);
// 声明一个RandomAccessFile对象
RandomAccessFile rdf = null;
// 以只读的方式打开文件
rdf = new RandomAccessFile(file,"r");
String name = null;
int age = 0;
// 准备空间读取姓名
byte[] bytes = new byte[8];
rdf.skipBytes(12);
for (int i = 0; i < bytes.length; i++) {
bytes[i] = rdf.readByte(); //循环读取前8个内容
}
name = new String(bytes); //将读取出来的byte数组变为String
age = rdf.readInt(); //读取数字
System.out.println("第二个人的信息-->姓名:" + name + ";年龄" + age);
rdf.seek(0); //指针回到文件的开头
bytes = new byte[8]; //准备空间读取姓名
for (int i = 0; i < bytes.length; i++) {
bytes[i] = rdf.readByte();
}
name = new String(bytes);
age = rdf.readInt();
System.out.println("第一个人的信息-->姓名:" + name + ";年龄" + age);
rdf.skipBytes(12);
bytes = new byte[8]; //准备空间读取姓名
for (int i = 0; i < bytes.length; i++) {
bytes[i] = rdf.readByte();
}
name = new String(bytes);
age = rdf.readInt();
System.out.println("第三个人的信息-->姓名:" + name + ";年龄" + age);
rdf.close(); //关闭文件
}
}

输出:

程序中可以随机跳过12位读取信息,也可以回到开始点重新读取。

3.字节流与字符流基本操作

介绍:

在程序中所有的数据都是以流的方式进行传输或保存的,程序需要数据时要使用输入流读取数据,而当程序需要将一些数据保存起来时,就要使用输出流

java.io包中流操作主要有:字节流、字符流两大类,两类都有输入、输出操作。

  1. 字节流:
    1. 输出数据主要使用 OutputStream类完成。
    2. 输入数据主要使用 InputStream类完成。
  2. 字符流:
    1. 输出主要使用 Writer类完成。
    2. 输入主要使用 Reader类完成。

Java中IO操作的相应步骤:

  1. 使用File类打开一个文件
  2. 通过字节流或字符流的子类指定输出的位置
  3. 进行读写操作
  4. 关闭输入输出流

1.字节流

字节流主要操作byte类型数据,以byte数组为准,主要操作类是:OutputStream类和InputStream类。

1.字节输入流:OutputStream

OutputStream是整个IO包中字节输出流的最大父类

定义: public abstract class OutputStream implements Closeable, Flushable

注意:OutputStream 类是一个抽象类,要使用这个类就必须先通过子类实例化对象。如果操作的是一个文件可以使用FileOutputStream类。然后向上转型,就可以为OutputStream类实例化。

OutputStream类中的主要操作方法:

序号 方法 类型 描述
1 public void close() throws IOException 普通 关闭输出流
2 public void flush() throws IOException 普通 刷新缓冲区
3 public void write(byte b[]) throws IOException 普通 将一个byte数组写入数据流
4 public void write(byte b[], int off, int len) throws IOException 普通 将一个指定范围的byte数组写入数据流
5 public abstract void write(int b) throws IOException 普通 将一个字节数据写入数据流

FileOutputStream类

构造方法:

  1. public FileOutputStream(File file) throws FileNotFoundException

案例1:向文件写入字符串。

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
public class Demo03 {
// 直接抛出异常,程序中可以不用在进行处理
public static void main(String[] args)throws Exception {
// 拼凑出可以符合操作系统的路径
String path = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.txt";
// 第一步:使用File类找到一个文件
File file = new File(path);
// 第二步:通过子类实例化父类对象(使用流打开文件)
OutputStream out = null; //准备好一个输出流对象
out = new FileOutputStream(file); //通过对象多态性进行实例化
// 第三步:进行写操作
String str = "Hello World!!!"; //准备一个字符串
byte[] bytes = str.getBytes(); //只能输出byte数组,所以将字符串变为byte数组
out.write(bytes); //将内容输出,保存文件
// 第四步:关闭流操作
out.close();
}
}

注意:以上程序在实例化、写、关闭时都会有异常发生,为了方便可以直接在主方法上使用throws。

当文件不存在时,会进行自动创建。

以上是通过将一个字符串转换成byte数组,然后将byte数组直接写入到文件中,当然也可以通过循环把每一个字节一个个地写入到文件之中。

使用write(int t)的方式写入文件内容。

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
public class Demo03 {
// 直接抛出异常,程序中可以不用在进行处理
public static void main(String[] args)throws Exception {
// 拼凑出可以符合操作系统的路径
String path = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.txt";
// 第一步:使用File类找到一个文件
File file = new File(path);
// 第二步:通过子类实例化父类对象(使用流打开文件)
OutputStream out = null; //准备好一个输出流对象
out = new FileOutputStream(file); //通过对象多态性进行实例化
// 第三步:进行写操作
String str = "Hello World!!!"; //准备一个字符串
byte[] bytes = str.getBytes(); //只能输出byte数组,所以将字符串变为byte数组
for (int i = 0; i < bytes.length; i++) {
out.write(bytes[i]); //将内容输出,保存文件
}
// 第四步:关闭流操作
out.close();
}
}

2.追加新内容

之前的操作如果重新执行会覆盖文件中已有的内容。

用法:

可以使用FileOutputStream类中的另外一个构造方法进行实例化:
public FileOutputStream(String name,boolean append)throws FileNotFoundException

将append的值设置为true,则表示在文件的末尾追加内容。

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
public class Demo03 {
// 直接抛出异常,程序中可以不用在进行处理
public static void main(String[] args)throws Exception {
// 拼凑出可以符合操作系统的路径
String path = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.txt";
// 第一步:使用File类找到一个文件
File file = new File(path);
// 第二步:通过子类实例化父类对象(使用流打开文件)
OutputStream out = null; //准备好一个输出流对象
out = new FileOutputStream(file,true); //通过对象多态性进行实例化
// 第三步:进行写操作
String str = "Hello World!!!"; //准备一个字符串
byte[] bytes = str.getBytes(); //只能输出byte数组,所以将字符串变为byte数组
for (int i = 0; i < bytes.length; i++) {
out.write(bytes[i]); //将内容输出,保存文件
}
// 第四步:关闭流操作
out.close();
}
}

如何增加换行?

使用\r\n增加换行

3.字节输入流InputStream

作用:

从文件中把内容读取出来。

InputStream类定义:

public abstract class InputStream implements Closeable

从文件中读取:

可以使用InputStream的子类FileIuputStream类。

FileIuputStream类的构造方法

public FileInputStream(File file) throws FileNotFoundException

InputStream类中的常用方法:

序号 方法 类型 描述
1 public int available() throws IOException 普通 可以取得输入文件的大小
2 public void close() throws IOException 普通 关闭输入流
3 public abstract int read() throws IOException 普通 读取内容,以数字的方式读取
4 public int read(byte b[]) throws IOException 普通 将内容读取到byte数组中,同时返回读入的个数

public abstract int read() throws IOException 方法的详解

一是这个方法的返回值是int类型;二是在这个方法每次从数据源中读取一个byte并返回.很多初次接触Java的读者在看到这里时都会产生下面的疑问,就是这个方法读取的byte是如何以int的形式返回的。在计算机中,所有的文件都是以二进制的形式存储的,换句话说,每个文件不管是什么类型,在计算机中的形式都是一串0和1。而read()方法在读的时候是每次读取8个二进制位,这8个0或1就是我们所谓的一个byte(字节)。read()这个方法完成的事情就是从数据源中读取8个二进制位,并将这8个0或1转换成十进制的整数,然后将其返回。

public int read(byte b[]) throws IOException方法详解:
这个方法使用一个byte的数组作为一个缓冲区,每次从数据源中读取和缓冲区大小(二进制位)相同的数据并将其存在缓冲区中。当然byte数组中存放的仍然是0-255的整数,将二进制转换为十进制这个过程仍然是read方法实现的。
  需要注意的是,虽然我们可以指定缓冲区的大小,但是read方法在读取数据的时候仍然是按照字节来读取的。在utf-8等变长编码中,一个复杂字符(比如汉字)所占字节往往大于1,并且长度往往是不固定的。(参照UTF-8编码规则)按照字节读取数据会将字符割裂,这就导致我们在使用read(byte[] b)方法读取文件时,虽然指定了缓冲区的大小,但是仍然会出现乱码。

案例:从文件中读取内容。

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
public class Demo03 {
// 直接抛出异常,程序中可以不用在进行处理
public static void main(String[] args)throws Exception {
// 拼凑出可以符合操作系统的路径
String path = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.txt";
// 第一步:使用File类找到一个文件
File file = new File(path);
// 第二步:通过子类实例化父类对象(使用流打开文件)
InputStream input = null; //准备好一个输入流对象
input = new FileInputStream(file); //通过对象多态性进行实例化
// 第三步:进行读操作
byte[] bytes = new byte[1024]; //将所有内容读到此数组中
input.read(bytes); //把内容取出,内容读到byte数组中
// 第四步:关闭流操作
input.close();
System.out.println("内容为: " + new String(bytes));
}
}

输出:

出现的问题1:内容虽然读取出来了但是后面会有很多空格。这是因为开辟的byte数组的空间为1024,超出了文件的内容大小。

解决方法1

read()方法有一个返回值,此返回值表示向数组中写入了多少个数据。

改进代码:

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
public class Demo03 {
// 直接抛出异常,程序中可以不用在进行处理
public static void main(String[] args)throws Exception {
// 拼凑出可以符合操作系统的路径
String path = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.txt";
// 第一步:使用File类找到一个文件
File file = new File(path);
// 第二步:通过子类实例化父类对象(使用流打开文件)
InputStream input = null; //准备好一个输入流对象
input = new FileInputStream(file); //通过对象多态性进行实例化
// 第三步:进行读操作
byte[] bytes = new byte[1024]; //将所有内容读到此数组中
int len = input.read(bytes); //把内容取出,内容读到byte数组中
// 第四步:关闭流操作
input.close();
System.out.println("内容为: " + new String(bytes,0,len));
}
}

new String(bytes,0,len))可以将byte数组中指定范围中的内容变成字符串。

出现的问题2:如果文件的内容小的话,会造成很多空间的浪费。

解决方法:根据文件的数据量来选择开辟空间的大小。使用 File类中的length()方法,此方法可以取得文件的大小。

改进代码:开辟指定大小的byte数组。

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
public class Demo03 {
// 直接抛出异常,程序中可以不用在进行处理
public static void main(String[] args)throws Exception {
// 拼凑出可以符合操作系统的路径
String path = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.txt";
// 第一步:使用File类找到一个文件
File file = new File(path);
// 第二步:通过子类实例化父类对象(使用流打开文件)
InputStream input = null; //准备好一个输入流对象
input = new FileInputStream(file); //通过对象多态性进行实例化
// 第三步:进行读操作
byte[] bytes = new byte[(int)file.length()]; //将所有内容读到此数组中,数组的大小由文件决定
input.read(bytes); //把内容取出,内容读到byte数组中
// 第四步:关闭流操作
input.close();
System.out.println("内容为: " + new String(bytes));
}
}

也可以通过循环从文件中一个个地把内容读取出来,直接使用read()方法即可。

使用read()通过循环读取

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
public class Demo03 {
// 直接抛出异常,程序中可以不用在进行处理
public static void main(String[] args)throws Exception {
// 拼凑出可以符合操作系统的路径
String path = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.txt";
// 第一步:使用File类找到一个文件
File file = new File(path);
// 第二步:通过子类实例化父类对象(使用流打开文件)
InputStream input = null; //准备好一个输入流对象
input = new FileInputStream(file); //通过对象多态性进行实例化
// 第三步:进行读操作
byte[] bytes = new byte[(int)file.length()]; //将所有内容读到此数组中,数组的大小由文件决定
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (byte) input.read(); //把内容取出
}
// 第四步:关闭流操作
input.close();
System.out.println("内容为: " + new String(bytes));
}
}

注意: 以上都是已经知道了具体数组大小的情况下开展的,如果不知道要输入的内容有多大,可以通过判断是否读到文件末尾的方式来读取文件。

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
public class Demo03 {
// 直接抛出异常,程序中可以不用在进行处理
public static void main(String[] args)throws Exception {
// 拼凑出可以符合操作系统的路径
String path = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.txt";
// 第一步:使用File类找到一个文件
File file = new File(path);
// 第二步:通过子类实例化父类对象(使用流打开文件)
InputStream input = null; //准备好一个输入流对象
input = new FileInputStream(file); //通过对象多态性进行实例化
// 第三步:进行读操作
int len = 0; //用于记录读取的数据个数
byte[] bytes = new byte[1024]; //将所有内容读到此数组中
int temp = 0; //接收读取的每一个内容
while((temp = input.read()) != -1){
//将每次读取的内容交给temp变量,如果temp的值不是-1,则表示文件没有读完
bytes[len] = (byte) temp;
len++;
}
// 第四步:关闭流操作
input.close();
System.out.println("内容为: " + new String(bytes,0,len));
}
}

文件读到末尾了,则返回的内容为-1

2.字符流

说明:

在程序中一个字符等于两个字节,Java提供了Reader和Writer两个专门操作字符流的类。

1.字符输出流Writer

Writer类的定义:
public abstract class Writer implements Appendable, Closeable, Flushable

关于Appendable接口的说明:

此接口定义如下:

public interface Appendable {
Appendable append(CharSequence csq) throws IOException;
Appendable append(CharSequence csq, int start, int end) throws IOException;
Appendable append(char c) throws IOException;
}

此接口表示的是:内容可以被追加,接收的参数是CharSequence,实际上String类就是实现了此接口,所以可以直接通过此接口的方法向输出流中追加内容。

Writer是抽象类,如果是向文件中写入内容,可以使用FileWriter类。

FileWriter类构造方法:

public FileWriter(File file) throws IOException

Writer类的常用方法:

序号 方法 类型 描述
1 abstract public void close() throws IOException 普通 关闭输出流
2 public void write(String str) throws IOException 普通 将字符串输出
3 public void write(char cbuf[]) throws IOException 普通 将字符数组输出
4 abstract public void flush() throws IOException 普通 强制性清除缓存

案例:向文件中写数据

import java.io.File;
import java.io.FileWriter;
import java.io.Writer;
public class Demo03 {
// 直接抛出异常,程序中可以不用在进行处理
public static void main(String[] args)throws Exception {
// 拼凑出可以符合操作系统的路径
String path = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.txt";
// 第一步:使用File类找到一个文件
File file = new File(path);
// 第二步:通过子类实例化父类对象(使用流打开文件)
Writer out = null; //准备好一个输出流对象
out = new FileWriter(file); //通过对象多态性进行实例化
// 第三步:进行读操作
String str = "Hello World!!!";
out.write(str);
// 第四步:关闭流操作
out.close();
}
}

优点:

可以直接输出字符串,而不用将字符串变为byte数组之后在输出。

2.使用FileWriter追加文件的内容

使用FileWriter类中的构造方法:

public FileWriter(String fileName, boolean append) throws IOException

将append的值设置为true,表示追加。

追加文件内容:

import java.io.File;
import java.io.FileWriter;
import java.io.Writer;
public class Demo03 {
// 直接抛出异常,程序中可以不用在进行处理
public static void main(String[] args)throws Exception {
// 拼凑出可以符合操作系统的路径
String path = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.txt";
// 第一步:使用File类找到一个文件
File file = new File(path);
// 第二步:通过子类实例化父类对象(使用流打开文件)
Writer out = null; //准备好一个输出流对象
out = new FileWriter(file,true); //通过对象多态性进行实例化
// 第三步:进行读操作
String str = "Hello World!!!";
out.write(str);
// 第四步:关闭流操作
out.close();
}
}

3.字符输入流Reader

Reader是使用字符的方式从文件中读取数据,Reader类定义如下:

public abstract class Reader implements Readable, Closeable

要是从文件中读取内容可以使用: FileReader类。

FileReader类构造方法:

public FileReader(File file) throws FileNotFoundException

Reader类常用方法:

序号 方法 类型 描述
1 abstract public void close() throws IOException 普通 关闭输入流
2 public int read() throws IOException 普通 读取单个字符
3 public int read(char cbuf[]) throws IOException 普通 将内容读到字符数组中,返回读入的长度

案例:从文件中读取内容。

import java.io.File;
import java.io.FileReader;
import java.io.Reader;
public class Demo03 {
// 直接抛出异常,程序中可以不用在进行处理
public static void main(String[] args)throws Exception {
// 拼凑出可以符合操作系统的路径
String path = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.txt";
// 第一步:使用File类找到一个文件
File file = new File(path);
// 第二步:通过子类实例化父类对象(使用流打开文件)
Reader input = null; //准备好一个输入流对象
input = new FileReader(file); //通过对象多态性进行实例化
// 第三步:进行读操作
char[] chars = new char[1024]; //所有的内容读到这个数组当中
int len = input.read(chars);
// 第四步:关闭流操作
input.close();
System.out.println("内容为: " + new String(chars,0,len));
}
}

注意:如果不知道数据的长度,也可以使用循环的方式进行内容的读取。

使用循环的方式读取内容

import java.io.File;
import java.io.FileReader;
import java.io.Reader;
public class Demo03 {
// 直接抛出异常,程序中可以不用在进行处理
public static void main(String[] args)throws Exception {
// 拼凑出可以符合操作系统的路径
String path = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.txt";
// 第一步:使用File类找到一个文件
File file = new File(path);
// 第二步:通过子类实例化父类对象(使用流打开文件)
Reader input = null; //准备好一个输入流对象
input = new FileReader(file); //通过对象多态性进行实例化
// 第三步:进行读操作
int len = 0;
char[] chars = new char[1024]; //所有的内容读到这个数组当中
int temp = 0;
while ((temp = input.read()) != -1) {
chars[len] = (char) temp;
len++;
}
// 第四步:关闭流操作
input.close();
System.out.println("内容为: " + new String(chars,0,len));
}
}

3.字节流与字符流的区别

字节流在操作时本身不会用到缓冲区(内存),是文件本身直接操作的,而字符流在操作时使用了缓冲区,通过缓冲区在操作文件。

案例:使用字节流和字符流进行写文件操作,并且不关闭输出流。

使用字节流,并且不关闭

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
public class Demo03 {
// 直接抛出异常,程序中可以不用在进行处理
public static void main(String[] args)throws Exception {
// 拼凑出可以符合操作系统的路径
String path = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.txt";
// 第一步:使用File类找到一个文件
File file = new File(path);
// 第二步:通过子类实例化父类对象(使用流打开文件)
OutputStream out = null; //准备好一个输出流对象
out = new FileOutputStream(file); //通过对象多态性进行实例化
// 第三步:进行写操作
String str = "kangwei";
byte[] bytes = str.getBytes();
out.write(bytes);
// 第四步:关闭流操作
// out.close(); 此时没有关闭
}
}

输出:

此时没有关闭字节流操作,但是文件中存在了输出的内容,证明字节流是直接操作文件本身的。

使用字符流,并且不关闭

import java.io.File;
import java.io.FileWriter;
import java.io.Writer;
public class Demo03 {
// 直接抛出异常,程序中可以不用在进行处理
public static void main(String[] args)throws Exception {
// 拼凑出可以符合操作系统的路径
String path = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.txt";
// 第一步:使用File类找到一个文件
File file = new File(path);
// 第二步:通过子类实例化父类对象(使用流打开文件)
Writer out = null; //准备好一个输出流对象
out = new FileWriter(file); //通过对象多态性进行实例化
// 第三步:进行写操作
String str = "kangwei";
out.write(str);
// 第四步:关闭流操作
// out.close(); 此时没有关闭
}
}

输出:

程序运行后发现文件中没有任何内容,这是因为字符流操作时使用了缓冲区,而在关闭字符流的时候会强制性的将缓冲区中的内容进行输出,但是如果字符流没有关闭,则缓冲区中的内容是无法输出的。

注意:如果想在不关闭字符流的同时将内容输出,可以使用Writer类中的flush()方法。

使用flush()方法

import java.io.File;
import java.io.FileWriter;
import java.io.Writer;
public class Demo03 {
// 直接抛出异常,程序中可以不用在进行处理
public static void main(String[] args)throws Exception {
// 拼凑出可以符合操作系统的路径
String path = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.txt";
// 第一步:使用File类找到一个文件
File file = new File(path);
// 第二步:通过子类实例化父类对象(使用流打开文件)
Writer out = null; //准备好一个输出流对象
out = new FileWriter(file); //通过对象多态性进行实例化
// 第三步:进行写操作
String str = "kangwei";
out.write(str);
out.flush(); //强制性清空缓冲区中的内容
// 第四步:关闭流操作
// out.close(); 此时没有关闭
}
}

输出:

什么是缓冲区?

缓冲区可以简单的理解为一段内存区域。

​ 在某些情况下,如果一个程序频繁的操作一个资源(如文件或者数据库),则性能会很低,此时为了提升性能,就可以将一部分数据暂时读入到内存的一块区域之中,以后直接从此区域中读取数据即可,因为读取内存速度会比较快,这样可以提升程序的性能。

​ 在字符流的操作中,所有的字符都是在内存中形成的,在输出前会将所有的内容暂时保存在内存中,所以使用了缓冲区暂存数据。

使用字节流好还是字符流好?

使用字节流好。

​ 所有的文件在硬盘或传输中都是以字节的方式进行的,包括图片等都是按照字节的方式存储的,而字符只在内存中才会形成,所以在开发中,字节流使用较广泛。

4.转换流--OutputStreamWriter类与InputStreamReader类

字节流--字符流的转换流

  1. OutputStreamWriter:是Writer的子类,将输出的字符流变为字节流,将一个字符流的输出对象变为字节流的输出对象。
    1. 构造方法
      1. public OutputStreamWriter(OutputStream out)
  2. InputStreamReader:是Reader的子类,将输入的字节流变为字符流,将一个字节流的输入对象变为字符流的输入对象。

以文件操作为例:内存中的字符数据需要通过OutputStreamWriter变为字节流才能保存在文件中,读取时需要将读入的字节流通过InputStreamReader变为字符流。

不管怎么操作,最终都是以字节的形式保存在文件中。

案例1:将字节输出流变为字符输出流

import java.io.*;
public class Demo03 {
// 直接抛出异常,程序中可以不用在进行处理
public static void main(String[] args)throws Exception {
// 拼凑出可以符合操作系统的路径
String path = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.txt";
// 第一步:使用File类找到一个文件
File file = new File(path);
// 第二步:通过子类实例化父类对象(使用流打开文件)
Writer out = null; //准备好一个输出流对象
out = new OutputStreamWriter(new FileOutputStream(file)); //字节流变为字符流
// 第三步:进行写操作
String str = "kangwei";
out.write(str);
// 第四步:关闭流操作
out.close();
}
}

案例2:将字节输入流变为字符输入流。

import java.io.*;
public class Demo03 {
// 直接抛出异常,程序中可以不用在进行处理
public static void main(String[] args)throws Exception {
// 拼凑出可以符合操作系统的路径
String path = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.txt";
// 第一步:使用File类找到一个文件
File file = new File(path);
// 第二步:通过子类实例化父类对象(使用流打开文件)
Reader reader = null; //准备好一个输入流对象
reader = new InputStreamReader(new FileInputStream(file)); //字节流变为字符流
// 第三步:进行写操作
char[] chars = new char[1024];
int len = reader.read(chars);
// 第四步:关闭流操作
reader.close();
System.out.println("内容: " + new String(chars,0,len));
}
}

FileWriter和FileReader说明:

从JDK文档中可以知道: FileOutputStream 是 OutputStream 的直接子类,FileInputStream 也是 InputStream 的直接子类,但是在字符流中文件的两个操作类有些特殊,FileWriter不是Writer的直接子类,而是InputStreamWriter的直接子类。 FileReader不是 Reader的直接子类,而是 InputStreamReader的直接子类。从这两个类的继承关系可以看出,不管使用的是字节流还是字符流实际上最终都是以字节的形式操作输入/输出流的。

5.内存操作流

前面所写的程序中的输出和输入都是从文件中来的,也可以将输入和输出的位置设置在内存上。此时就要使用ByteArrayInputStreamByteArrayOutputStream来完成内存的输入和输出。

注意: 输入和输出都是针对于内存来说的。

  1. ByteArrayInputStream

    1. 作用:将内容写入到内存中。

    2. 主要方法

      1. 序号 方法 类型 描述
        1 public ByteArrayInputStream(byte buf[]) 构造 将全部的内存写入内存
        2 public ByteArrayInputStream(byte buf[], int offset, int length) 构造 将指定范围的内存写入到内存
  2. ByteArrayOutputStream

    1. 作用:将内存中的数据输出。

    2. 主要方法

      1. 序号 方法 类型 描述
        1 public ByteArrayOutputStream() 构造 创建对象
        2 public synchronized void write(int b) 普通 将内存从内存中输出

案例:使用内存操作流完成一个大写字母转换为小写字母的程序。

import java.io.*;
public class Demo04 {
public static void main(String[] args) {
String str = "HELLOWORLD";
ByteArrayInputStream bis = null; //声明一个内存的输入流
ByteArrayOutputStream bos = null; //声明一个内存的输出流
bis = new ByteArrayInputStream(str.getBytes()); //向内存中输入内容
bos = new ByteArrayOutputStream(); //准备从ByteArrayInputStream中读取数据
int temp = 0;
while((temp = bis.read()) != -1){
char c = (char) temp; //将读取的数字变为字符
bos.write(Character.toLowerCase(c)); //将字符变为小写
}
String newStr = bos.toString();
try{
bis.close();
bos.close();
}catch (IOException e){
e.printStackTrace();
}
System.out.println(newStr);
}
}

以上全部的操作都是在内存中完成的。

内存操作流的使用

内存操作流一般在生成一些临时信息时才会使用,如果将这些临时信息保存在文件中,则代码执行完成后还需要删除这个临时文件,这就比较麻烦,这时使用内存操作流是最合适的。

6.管道流

作用:

进行两个线程间的通信。

  1. 管道输出流 PipedOutputStream
  2. 管道输入流 PipedInputStream

注意:如果要进行管道输出,必须把输出流连在输入流上。

使用以下方法连接管道:

public synchronized void connect(PipedInputStream snk) throws IOException

案例:验证管道流

import java.io.*;
class Send implements Runnable{
private PipedOutputStream pos = null; //声明一个管道输出流
public Send(){
this.pos = new PipedOutputStream(); //在构造方法中实例化管道输出流对象
}
@Override
public void run() {
String str = "Hello World!!!";
try{
this.pos.write(str.getBytes());
}catch (IOException e){
e.printStackTrace();
}
try{
this.pos.close();
}catch (IOException e){
e.printStackTrace();
}
}
public PipedOutputStream getPos(){ //通过线程类得到输出流
return this.pos;
}
}
class Receive implements Runnable{
private PipedInputStream pis = null;
public Receive(){
this.pis = new PipedInputStream();
}
@Override
public void run() {
byte[] bytes = new byte[1024];
int len = 0;
try{
len = this.pis.read(bytes);
}catch (IOException e){
e.printStackTrace();
}
try{
this.pis.close();
}catch (IOException e){
e.printStackTrace();
}
System.out.println("接收的内容为: " + new String(bytes,0,len));
}
public PipedInputStream getPis(){
return this.pis;
}
}
public class Demo04 {
public static void main(String[] args) {
Send send = new Send();
Receive receive = new Receive();
try{
send.getPos().connect(receive.getPis()); //连接管道
}catch (IOException e){
e.printStackTrace();
}
new Thread(send).start(); //启动线程
new Thread(receive).start(); //启动线程
}
}

7.打印流

1.基础结束

  1. 字节打印流PrintStream --> 是 OutputStream的子类。
  2. 字符打印流PrintWriter --> 是 Writer的子类。

打印流可以打印任何的数据类型,如:小数、整数、字符串。

以下讲解字节打印流PrintStream

PrintStream类的常用方法:

序号 方法 类型 描述
1 public PrintStream(File file) throws FileNotFoundException 构造 通过一个File对象实例化PrintStream类
2 public PrintStream(OutputStream out) 构造 接收OutputStream对象,实例化PrintStream类
3 public PrintStream printf(Locale l, String format, Object ... args) 普通 根据指定的Locale进行格式化输出
4 public PrintStream printf(String format, Object ... args) 普通 根据本地环境格式化输出
5 public void print(boolean b) 普通 此方法被重载很多次,输出任意数据
6 public void println(boolean x) 普通 此方法被重载很多次,输出任意数据后换行

构造方法 public PrintStream(OutputStream out)可以直接接收OutputStream类的实例,与OutputStream类相比,PrintStream类可以更加方便的输出数据,相当于将OutputStream类重新包装了一下,使之输出更加方便。

装饰设计模式:

比如这里把一个输出流的实例传递到打印流后,可以更加方便的输出内容,也就是说,是打印流把输出流重新装饰了一下。

案例:使用PrintStream输出。

import java.io.*;
public class Demo04 {
public static void main(String[] args)throws Exception { //所有异常抛出
PrintStream ps = null;
// 此时通过FileOutputStream实例化,意味着所有的输出时向文件打印的
ps = new PrintStream(new FileOutputStream(new File("D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.txt")));
ps.print("hello");
ps.println("world!!!");
ps.print("1 + 1 = " + 2);
ps.close();
}
}

2.使用打印流进行格式化

使用printf()方法

序号 字符 描述
1 %s 表示内容为字符串
2 %d 表示内容为数字
3 %f 表示内容为小数
4 %c 表示内容为字符

案例:格式化输出。

import java.io.*;
public class Demo04 {
public static void main(String[] args)throws Exception { //所有异常抛出
PrintStream ps = null;
// 此时通过FileOutputStream实例化,意味着所有的输出时向文件打印的
ps = new PrintStream(new FileOutputStream(new File("D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.txt")));
String name = "康伟";
int age = 18;
float score = 990.356f;
char sex = 'M';
// 格式化输出
ps.printf("姓名: %s;年龄:%d;分数: %f; 性别: %c",name,age,score,sex);
ps.close();
}
}

8.System类对IO的支持

System类中与IO有关的三个常量:

序号 System类的常量 描述
1 public final static PrintStream out 对应系统标准输出,一般是显示器
2 public final static PrintStream err 错误信息输出
3 public final static InputStream in 对应标准输入,一般是键盘

1.System.out

System.outPrintStream的对象。所以经常使用的System.out.print()System.out.println()语句调用的实际上就是PrintStream类的方法。

因为:

OutputStream --> (子类)FilterOutputStream --> (子类)PrintStream -->(对象)System.out

所以可以使用System.out为OutputStream类进行实例化。并向屏幕上输出信息。

注意:

OutputStream的哪个子类为其实例化,就具备了向哪里输出的能力。

  1. 使用 FileOutputStream表示向文件输出
  2. 使用 System.out表示向显示器输出

2.System.err

表示错误信息输出。

public class Demo04 {
public static void main(String[] args){
String str = "hello"; //声明一个非数字的字符串
try{
System.out.println(Integer.parseInt(str));
}catch (Exception e){
System.err.println(e);
}
}
}

提问:System.err的功能似乎与System.out的功能是一样的?

回答:从概念上解释System.err和System.out。

System.err和System.out都是PrintStream的实例化对象,两者都可以用来输出错误信息。

区别:

  1. System.out是将信息显示给用户看,是正常的信息显示。
  2. System.err是不希望用户看到,会在直接在后台打印,是专门显示错误的。

3.System.in

System.in是一个键盘的输入流,本身是InputStream类型的对象

案例:使用System.in从键盘读取数据。

import java.io.InputStream;
public class Demo04 {
public static void main(String[] args)throws Exception{
InputStream input = System.in; //从键盘接收数据
byte[] bytes = new byte[1024]; //开辟空间,接收数据
System.out.println("请输入内容: ");
int len = input.read(bytes); //接收数据
System.out.println("输出内容: " + new String(bytes,0,len));
input.close();
}
}

存在的问题:

  1. 指定了输入数据的长度,如果现在输入的数据超过了其长度范围,则只能输入部分数据。
  2. 如果指定的byte数组长度是奇数,则还有可能出现中文乱码。
    1. 因为一个中文等于两个字节,最后只剩一个字节,只能装下半个汉字。

案例:如果不指定byte数组的长度接收数据。(不指定大小)使用StringBuffer类接收数据

import java.io.InputStream;
public class Demo04 {
public static void main(String[] args)throws Exception{
InputStream input = System.in; //从键盘接收数据
StringBuffer buffer = new StringBuffer(); //声明StringBuffer用于接收数据
System.out.println("请输入内容: ");
int temp = 0;
while((temp = input.read()) != -1){
char c = (char) temp; //将数据变为字符
if (c == '\n'){
break;
}
buffer.append(c); //追加数据
}
System.out.println("输出内容: " + buffer);
input.close();
}
}

注意:程序中如果输入的是英文字母,不会出现问题。如果输入的是中文,就会产生乱码,因为数据是以一个个字节的方式读进来的,一个汉字是分为两次读取的,所以造成了乱码。

问题:

指定大小会出现空间限制,不指定大小则输入中文时又会产生乱码,怎么的输入数据形式才是最合理的呢?

回答:

最好的输入方式是将全部输入的数据暂时放到一块内存中,然后一次性从内存中读取出数据,这样所有的数据只读了一次,则不会造成乱码,也不会受到长度限制。可以使用 BufferedReader类完成。

4.输入/输出重定向

通过System类可以改变System.in的输入流来源以及System.out和System.err两个输出流的输出位置。

System类提供的重定向方法:

序号 方法 类型 描述
1 public static void setOut(PrintStream out) 普通 重定向"标准"输出流
2 public static void setErr(PrintStream err) 普通 重定向"标准"错误输出流
3 public static void setIn(InputStream in) 普通 重定向"标准"输入流

案例:为System.out输出重定向

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.PrintStream;
public class Demo04 {
public static void main(String[] args)throws Exception{
System.setOut(new PrintStream(new FileOutputStream("D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.txt")));
System.out.print("www.baidu.com"); //输出时,不再向屏幕上输出
System.out.println(",百度"); //而是向指定的重定向位置输出
}
}

案例:设置System.in的输入重定向。

import java.io.*;
public class Demo04 {
public static void main(String[] args)throws Exception{
System.setIn(new FileInputStream("D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.txt"));
InputStream input = System.in; //从文件中读取数据
byte[] bytes = new byte[1024]; //开辟空间,接收数据
int len = input.read(bytes);
System.out.print("输入的内容为: " + new String(bytes,0,len));
}
}

以上代码修改了System.in的输入位置,而将其输入重定向到从文件中读取。

9.BufferedReader类

BufferedReader类用于从缓冲区中读取内容,所有的输入字节数据都将放在缓冲区中。

BufferedReader类的常用方法:

序号 方法 类型 描述
1 public BufferedReader(Reader in) 构造 接收一个Reader类的实例
2 public String readLine() throws IOException 普通 一次性从缓冲区中将内容全部读取进来

注意: Bufferedreader类中定义的构造方法只能接收字符输入流的实例,所以必须使用字符输入流和字节输入流的转换类InputStreamReader将字节输入流System.in变为字符流。

BufferedReader实例化

BufferedReader buf = null;

buf = new BufferedReader(new InputStreamReader(System.in))

1.键盘输入数据的标准格式

案例:从键盘输入数据

import java.io.*;
public class Demo04 {
public static void main(String[] args){
BufferedReader buf = null;
buf = new BufferedReader(new InputStreamReader(System.in));
String str = null;
System.out.println("请输入内容:");
try{
str = buf.readLine();
}catch (IOException e){
e.printStackTrace();
}
System.out.println("输入的内容是:" + str);
}
}

程序接收数据没有了长度的限制,也可以正常接收中文了。

10.Scanner类

1.简介

Scanner类不止可以完成输入数据操作,而且可以方便的对输入数据进行验证。Scanner类位与java.util包中。

Scanner类常用方法:

序号 方法 类型 描述
1 public Scanner(File source) throws FileNotFoundException 构造 从文件中接收内容
2 public Scanner(InputStream source) 构造 从指定的字节输入流中接收内容
3 public boolean hasNext(Pattern pattern) 普通 判断输入的数据是否符合指定的正则标准
4 public boolean hasNextInt() 普通 判断输入的是否是整数
5 public boolean hasNextFloat() 普通 判断输入的是否是小数
6 public String next() 普通 接收内容
7 public String next(Pattern pattern) 普通 接收内容,进行正则验证
8 public int nextInt() 普通 接收数字
9 public float nextFloat() 普通 接收小数
10 public Scanner useDelimiter(String pattern) 普通 设置读取的分隔符

Scanner类可以接收任意的输入流。

在Scanner类中提供了一个可以接收InputStream类型的构造方法,这就表示只要是字节输入流的子类都可以为Scanner类实例化,以方便读取。

2.使用Scanner类输入数据

1.实现基本的数据输入

方法:直接使用Scanner类中的next()方法。

案例:输入数据。

import java.util.Scanner;
public class Demo04 {
public static void main(String[] args){
Scanner scanner = new Scanner(System.in); //从键盘接收数据
System.out.println("输入数据:");
String str = scanner.next();
System.out.println("输入的数据为:" + str);
}
}

存在的问题:如果输入带有空格的内容,则只能取出空格之前的数据。

Scanner将空格当做一个分隔符,可以将分隔符修改为"\n"(回车)

改进代码:

import java.util.Scanner;
public class Demo04 {
public static void main(String[] args){
Scanner scanner = new Scanner(System.in); //从键盘接收数据
scanner.useDelimiter("\n"); //修改输入数据的分隔符
System.out.println("输入数据:");
String str = scanner.next();
System.out.println("输入的数据为:" + str);
}
}

输入int、float:在输入之前最好先用hasNextXxx()方法进行验证。

import java.util.Scanner;
public class Demo04 {
public static void main(String[] args){
Scanner scanner = new Scanner(System.in); //从键盘接收数据
scanner.useDelimiter("\n"); //修改输入数据的分隔符
int i = 0;
float f = 0.0f;
System.out.println("输入整数:");
if (scanner.hasNextInt()){
i = scanner.nextInt();
System.out.println("整数数据:" + i);
}else {
System.out.println("输入的不是整数。");
}
System.out.println("输入小数:");
if (scanner.hasNextFloat()){
f = scanner.nextFloat();
System.out.println("小数数据:" + f);
}else {
System.out.println("输入的不是小数。");
}
}
}

2.实现日期格式的数据输入

在Scanner类中没有提供专门的日期格式输入操作,所以要想获得一个日期类型的数据,需要自己编写正则验证。

案例:得到日期。

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;
public class Demo04 {
public static void main(String[] args){
Scanner scanner = new Scanner(System.in); //从键盘接收数据
scanner.useDelimiter("\n"); //修改输入数据的分隔符
System.out.println("输入日期(yyyy-MM-dd):");
String str = null;
Date date = null;
if (scanner.hasNext("^\\d{4}-\\d{2}-\\d{2}$")){ // 判断输入的格式是否符合要求
str = scanner.next("^\\d{4}-\\d{2}-\\d{2}$"); //接收并判断
try{
date = new SimpleDateFormat("yyyy-MM-dd").parse(str);
}catch (ParseException e){
e.printStackTrace();
}
}else {
System.out.println("输入的日期格式错误!!!");
}
System.out.println(date);
}
}

使用hasNext()对输入的数据进行正则验证,如果合法,则转换成Date类型。

3.从文件中得到数据

直接将File类的实例传入Scanner的构造方法中即可。

案例:读取文件中的内容

import java.io.File;
import java.io.FileNotFoundException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;
public class Demo04 {
public static void main(String[] args) {
String path = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.txt";
File file = new File(path);
Scanner scanner = null;
try {
scanner = new Scanner(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
StringBuffer buffer = new StringBuffer();
while (scanner.hasNext()) { //判断是否还有内容
buffer.append(scanner.next()).append("\n");
}
System.out.println(buffer);
}
}

hasNext()方法判断输入(文件、字符串、键盘等输入流)是否还有下一个输入项,若有,返回true,反之false。

这里引出一个问题: ⭐⭐⭐

再try-catch中申明变量,但是后面在try-catch块外面使用的时候该变量失效了。

原因: Java规定的局部变量只能在它的范围内使用。

一对花括号{ }构成一个局部作用域,局部作用域中定义的变量只在该作用域内有效。不光是try…catch…,任何一对花括号构成的块都是如此。

11.数据操作流

IO包提供了两个与平台无关的数据操作流。

  1. DataOutputStream:数据输出流。
  2. DataInputStream:数据输入流。

作用:

数据输出流会按照一定的格式将数据输出,在通过数据输入流按照一定的格式将数据输入。

1.DataOutputStream

DataOutputStream是OutputStream的子类。

定义:

class DataOutputStream extends FilterOutputStream implements DataOutput

DataOutput接口的作用:

DataOutput是数据的输出接口,其中定义了各种数据的输出操作方法,例如,在DataOutputStream 类中的各种 writeXxx()方法就是此接口定义的。在数据输出时一般都会直接使用DataOutputStream ,只用在对象序列化时才有可能直接用到DataOutput接口。

DataOutputStream类的常用方法:

序号 方法 类型 描述
1 public DataOutputStream(OutputStream out) 构造 实例化对象
2 public final void writeInt(int v) throws IOException 普通 将一个int值以4-byte值形式写入基础输出流中
3 public final void writeDouble(double v) throws IOException 普通 将一个double值以4-byte值形式写入基础输出流中
4 public final void writeChars(String s) throws IOException 普通 将一个字符串写入到输出流中
5 public final void writeChar(int v) throws IOException 普通 将一个字符写入到输出流中

案例:将以下订单数据写入到文件中。

商品名称 商品价格 商品数量
衬衣 98.3 3
手套 30.3 2
围巾 50.0 1
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
public class Demo04 {
public static void main(String[] args)throws Exception {
DataOutputStream dos = null;
String path = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.txt";
File file = new File(path);
dos = new DataOutputStream(new FileOutputStream(file)); //实例化输出流对象
String[] name = {"衬衣","手套","围巾"};
float[] prices = {98.3f,30.3f,50.5f};
int[] nums = {3,2,1};
for (int i = 0; i < name.length; i++) {
dos.writeChars(name[i]);
dos.writeChar('\t');
dos.writeFloat(prices[i]);
dos.writeChar('\t');
dos.writeInt(nums[i]);
dos.writeChar('\n');
}
dos.close();
}
}

写入文件中是乱码:

DataOutputStream提供了将Java各种类型数据的输出方法,但是其将各种数据类型以二进制形式输出,用户无法方便的进行查看。因为DataOutputStream向文件写入时会做特殊的标记,只有DataInputStream才能进行读取

2.DataInputStream

DataInputStream是InputStream的子类,专门负责读取使用DataOutputStream输出的数据。

定义:

public class DataInputStream extends FilterInputStream implements DataInput

DataInput接口的作用:

DataInput接口是读取数据的操作接口,与DataOutput接口提供的各种writeXxx()方法对应,此接口中定义了一系列的readXxx()方法,这些方法在DataInputStream类中都有实现。一般操作直接使用DataInputStream类完成读取功能,只有在对象序列化时才有可能直接利用DataInput接口读取数据。

DataInputStream类中的常用方法:

序号 方法 类型 描述
1 public DataInputStream(InputStream in) 构造 实例化对象
2 public final int readInt() throws IOException 普通 从输入流中读取整数
3 public final float readFloat() throws IOException 普通 从输入流中读取小数
4 public final char readChar() throws IOException 普通 从输入流中读取一个字符

案例:读取以上使用DataOutputStream写入的数据。

import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
public class Demo04 {
public static void main(String[] args)throws Exception {
DataInputStream dis = null;
String path = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.txt";
File file = new File(path);
dis = new DataInputStream(new FileInputStream(file)); //实例化输入流对象
String name = null;
float prices = 0.0f;
int nums = 0;
char[] temp = null; //接收字符串数据
char c = 0;
int len = 0;
try{
while(true){
temp = new char[200];
len =0;
while((c = dis.readChar()) != '\t'){
temp[len] = c;
len++;
}
name = new String(temp,0,len); //将字符数组变为String
prices = dis.readFloat();
dis.readChar(); //读取\t
nums = dis.readInt();
dis.readChar(); //读取\n
System.out.printf("名称: %s;价格: %5.2f;数量: %d\n",name,prices,nums);
}
}catch (Exception e){ //如果读到底会出现异常
}
dis.close();
}
}

12.合并流

作用:

将两个文件的内容合并成一个文件。

使用类: SequenceInputStream

常用方法:

序号 方法 类型 描述
1 public SequenceInputStream(InputStream s1, InputStream s2) 构造 使用两个输入流对象实例化本类对象
2 public int available() throws IOException 普通 返回文件大小

案例:合并两个文件。

import java.io.*;
public class Demo04 {
public static void main(String[] args)throws Exception {
String path1 = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.txt";
String path2 = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test1.txt";
String path3 = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test2.txt";
File file1 = new File(path1);
File file2 = new File(path2);
File file3 = new File(path3);
InputStream is1 = null; //输入流1
InputStream is2 = null; //输入流2
OutputStream os = null; //输出流
SequenceInputStream sis = null; //合并流
is1 = new FileInputStream(file1);
is2 = new FileInputStream(file2);
os = new FileOutputStream(file3);
sis = new SequenceInputStream(is1,is2); //实例化合并流对象
int temp = 0;
while((temp = sis.read()) != -1){
os.write(temp);
}
sis.close();
is1.close();
is2.close();
os.close();
}
}

这个程序:输入中文会乱码。

13.压缩流

在java中为了减少传输时的数据量也提供了专门的压缩流,可以将文件压缩成ZIP、JAR、GZIP格式文件。

1.简介

1.ZIP压缩输入/输出流

在java中要实现ZIP的压缩需要导入java.util.zip包,也可以此包中的ZipFile、ZipOutputStream、ZipInputStream和ZipEntry几个类完成操作。

注意:

在每个压缩文件中都会存在多个子文件,那么每个子文件在java中就使用ZipEntry表示。

ZipEntry类的常用方法:

序号 方法 类型 描述
1 public ZipEntry(String name) 构造 创建对象并指定要创建的ZipEntry名称
2 public boolean isDirectory() 普通 判断此ZipEntry是否是目录

2.JAR压缩

JAR压缩的支持类在java.util.jar包中。

常用类:

  1. JAR压缩输出流: JarOutputStream
  2. JAR压缩输入流: JarInputStream
  3. JAR文件: JARFile
  4. JAR实体: JAREntry

3.GZIP压缩

GZIP是用于UNIX系统的文件压缩,在Linux中经常会使用到 *.gz的文件,就是GZIP格式,GZIP压缩的支持类保存在java.util.zip包中。

常用类:

  1. GZIP压缩输出流: GZIPOutPutStream
  2. GZIP压缩输入流: GZIPInputStream

2.ZipOutputStream类

可以用来压缩一个文件或文件夹。

ZipOutputStream类的常用方法:

序号 方法 类型 描述
1 public ZipOutputStream(OutputStream out) 构造 创建一个新的ZIP输出流
2 public void putNextEntry(ZipEntry e) throws IOException 普通 设置每一个ZipEntry对象
3 public void setComment(String comment) 普通 设置ZIP文件的注释

案例:将test.txt文件压缩成test.zip文件。

import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class Demo04 {
public static void main(String[] args)throws Exception {
String path1 = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.txt";
File file1 = new File(path1);
String path2 = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.zip";
File file2 = new File(path2); //定义压缩文件名称
InputStream input = new FileInputStream(file1); //定义输入文件流
ZipOutputStream zipOut = null; //声明压缩输出流
// 实例化压缩输出流对象,并指定压缩文件的输出路径
zipOut = new ZipOutputStream(new FileOutputStream(file2));
// 每一个被压缩的文件都用ZipEntry表示,需要为每一个压缩后的文件设置名称
zipOut.putNextEntry(new ZipEntry(file1.getName())); //创建ZipEntry
zipOut.setComment("java学习"); //设置注释 这里中文会出现乱码
int temp = 0; //用于接收数据
while ((temp = input.read()) != -1){ //压缩输出内容
zipOut.write(temp);
}
input.close();
zipOut.close();
}
}

案例:压缩一个文件夹。

实现时应该列出文件夹中的全部内容,并把每一个内容设置成ZipEntry对象,保存到压缩文件中。

import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class Demo04 {
public static void main(String[] args)throws Exception {
String path1 = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test";
File file1 = new File(path1); //要压缩的文件夹
String path2 = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.zip";
File file2 = new File(path2); //定义压缩文件名称
InputStream input = null; //定义输入文件流
ZipOutputStream zipOut = null; //声明压缩输出流
// 实例化压缩输出流对象,并指定压缩文件的输出路径
zipOut = new ZipOutputStream(new FileOutputStream(file2));
zipOut.setComment("javaStudy"); //设置注释 这里中文会出现乱码
if (file1.isDirectory()){ //判断是否是目录
File[] files = file1.listFiles(); //列出全部文件
for (int i = 0; i < files.length; i++) {
input = new FileInputStream(files[i]);
// 每一个被压缩的文件都用ZipEntry表示,需要为每一个压缩后的文件设置名称
zipOut.putNextEntry(new ZipEntry(file1.getName() + File.separator + files[i].getName())); //创建ZipEntry
int temp = 0;
while ((temp = input.read()) != -1){
zipOut.write(temp);
}
input.close();
}
}
zipOut.close();
}
}

3.ZipFile类

每一个压缩的文件都可以使用ZipFile表示,还可以使用ZipFile根据压缩后的文件名称找到每一个压缩文件中的ZipEntry并将其进行解压。

ZipFile类的常用方法:

序号 方法 类型 描述
1 public ZipFile(File file) throws ZipException, IOException 构造 根据File类实例化ZipFile对象
2 public ZipEntry getEntry(String name) 普通 根据名称找到其对应的ZipEntry
3 public InputStream getInputStream(ZipEntry entry) throws IOException 普通 根据ZipEntry取得InputStream实例
4 public String getName() 普通 得到压缩文件的路径名称

案例:实例化ZipFile类对象

import java.io.*;
import java.util.zip.ZipFile;
public class Demo04 {
public static void main(String[] args)throws Exception {
String path = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.zip";
File file = new File(path); //找到压缩文件
ZipFile zipFile = new ZipFile(file); //实例化ZipFile对象
System.out.println("压缩文件的名称为:" + zipFile.getName());
}
}

输出:

案例:解压缩文件。

import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
public class Demo04 {
public static void main(String[] args)throws Exception {
String path1 = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test1.zip";
String path2 = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test2.txt";
File file1 = new File(path1); //找到压缩文件
File file2 = new File(path2); // 定义解压缩的文件名称
ZipFile zipFile = new ZipFile(file1); //实例化ZipFile对象
ZipEntry entry = zipFile.getEntry("test1.txt"); //得到一个压缩实体
InputStream inputStream = zipFile.getInputStream(entry); //取得ZipEntry输入流
FileOutputStream fileOutputStream = new FileOutputStream(file2); //实例化输出流对象
int temp = 0;
while ((temp = inputStream.read()) != -1) {
fileOutputStream.write(temp);
}
inputStream.close();
fileOutputStream.close();
}
}

注意:这个程序只适合压缩文件中存在一个ZipEntry的情况,如果在一个压缩文件中存在文件夹或者多个ZipEntry就无法使用了。必须结合ZipInputStream类完成。

4.ZipInputStream类

通过此类可以方便的读取ZIP格式的压缩文件。

ZipInputStream类的常用方法:

序号 方法 类型 描述
1 public ZipInputStream(InputStream in) 构造 实例化ZipInputStream对象
2 public ZipEntry getNextEntry() throws IOException 普通 取得下一个ZipEntry

使用ZipInputStream可以像ZipFile一样取得ZIP压缩文件中的每一个ZipEntry。

案例:取得test.zip中的一个ZipEntry。

import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public class Demo04 {
public static void main(String[] args)throws Exception {
String path1 = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.zip";
File file1 = new File(path1); //找到压缩文件
ZipInputStream input = null; //定义压缩输入流
input = new ZipInputStream(new FileInputStream(file1)); //实例化压缩输入流
ZipEntry entry = input.getNextEntry(); //得到一个压缩实体
System.out.println("压缩实体名称:" + entry.getName());
input.close();
}
}

File类的createNewFile()方法可以创建新文件

案例:解压缩test.zip文件。

import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
public class Demo04 {
public static void main(String[] args)throws Exception {
String path1 = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.zip";
String path2 = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO";
File file = new File(path1);
File outFile = null;
ZipFile zipFile = new ZipFile(file);
ZipInputStream zipInput = new ZipInputStream(new FileInputStream(file));
ZipEntry entry = null;
InputStream input = null;
OutputStream out = null;
while ((entry = zipInput.getNextEntry()) != null){
System.out.println("解压缩:" + entry.getName() + "文件");
outFile = new File(path2 + File.separator + entry.getName());
if (!outFile.getParentFile().exists()){
outFile.getParentFile().mkdir(); //创建文件夹
}
if (!outFile.exists()){
outFile.createNewFile();
}
input = zipFile.getInputStream(entry);
out = new FileOutputStream(outFile);
int temp = 0;
while ((temp = input.read()) != -1){
out.write(temp);
}
input.close();
out.close();
}
}
}

14.回退流

简介:

在JavaIO中所有的数据都是采用顺序的读取方式,即对于一个输入流来说,都是采用从头到尾的顺序读取的。如果在输入流中某个不需要的内容被读取进来,则只能通过程序将这些不需要的数据处理掉。为了解决这样的读取问题,在Java中提供了一种回退输入流(PushbackInputStream、PushbackReader),可以把读取进来的某些数据重新退回到输入流的缓冲区中。

回退流操作机制:

对于不需要的数据可以使用unread()方法将内容重新送回到输入流的缓冲区中。

PushbackInputStream类的常用方法:

序号 方法 类型 描述
1 public PushbackInputStream(InputStream in) 构造 将输入流放到回退流中
2 public int read() throws IOException 普通 读取数据
3 public int read(byte[] b, int off, int len) throws IOException 普通 读取指定范围的数据
4 public void unread(int b) throws IOException 普通 回退一个数据到缓冲区前面
5 public void unread(byte[] b) throws IOException 普通 回退一组数据到缓冲区前面
6 public void unread(byte[] b, int off, int len) throws IOException 普通 回退指定范围的一组数据到缓冲区前面

回退流与输入流的对应:

序号 InputStream PushbackInputStream
1 读取一个 public abstract int read() throws IOException 回退一个 public void unread(int b) throws IOException
2 读取一组 public int read(byte b[]) throws IOException 回退一组 public void unread(byte[] b) throws IOException
3 读取部分 public int read(byte b[], int off, int len) throws IOException 回退部分 public void unread(byte[] b, int off, int len) throws IOException

案例:内存中有一个"www.baidu.com"字符串,只要输入的内容是".",则执行回退操作,即不读取"."

import java.io.ByteArrayInputStream;
import java.io.PushbackInputStream;
public class Demo04 {
public static void main(String[] args)throws Exception {
String str = "www.baidu.com";
PushbackInputStream push = null;
ByteArrayInputStream bai = null; //定义内存输入流
bai = new ByteArrayInputStream(str.getBytes()); //将字符串写入内存
push = new PushbackInputStream(bai);
System.out.println("读取之后的数据为:");
int temp = 0;
while ((temp = push.read()) != -1){
if (temp == '.'){
push.unread(temp);
temp = push.read();
System.out.print("(回退:" + (char) temp + ")");
}else {
System.out.print((char) temp); //输出内容
}
}
}
}

输出:

15.字符编码

Java开发中常见的编码:ISO8859-1、GBK/GB2312、unicode、UTF

  1. ISO8859-1:属于单字节编码,最多只能表示0~255的字符范围,主要在英文上应用。
  2. GBK/GB2312:中文国标编码,专门用来表示汉字,是双字节编码,如果在此编码中出现英文,则使用ISO8859-1编码,GBK可以表示简体中文和繁体中文、而GB2312只能表示简体中文,GBK兼容GB2312。
  3. unicode:java中使用此编码方式,是最标准的一种编码,使用十六进制(两个字节)表示编码。但是此编码不兼容ISO8859-1编码。
  4. UTF由于unicode不支持ISO8859-1编码,而且容易占用更多的空间,而且对于英文字母也需要使用两个字节编码,这样使用unicode不便于传输和存储,因此产生了UTF编码。UTF编码兼容ISO8859-1编码,同时可以用来表示所有的语言字符,不过UTF编码是不定长编码,每个字符的长度为 1~6个字节不等。一般在中文网页中使用此编码可以节省空间。

乱码是比较常见的问题:

产生的原因有一个是: 输出内容的编码(例如:程序指定编码)与接收内容的编码(本机环境默认编码)不一致

1.得到本机的编码显示

使用System类中的如下方法:

public static Properties getProperties()

案例:使用getProperties()方法得到JVM的默认编码。

public class Demo04 {
public static void main(String[] args)throws Exception {
System.out.println("系统默认编码:" + System.getProperty("file.encoding"));
}
}

输出:

2.乱码产生

实现编码的转换可以使用String类中的getBytes(String charset)方法。

案例:指定ISO8859-1编码,出现乱码。

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
public class Demo04 {
public static void main(String[] args)throws Exception {
String path1 = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.txt";
File file = new File(path1);
OutputStream out = new FileOutputStream(file);
byte[] b = "中国,你好!".getBytes("ISO8859-1");
out.write(b);
out.close();
}
}

输出:

16.对象序列化

1.基本概念与Serializable接口

对象序列化就是把一个对象变为二进制的数据流的一种方法。通过对象序列化可以方便的实现对象的传输与存储。

注意:如果一个类的对象想被序列化,则对象所在的类必须实现java.io.Serializable接口。

接口定义: public interface Serializable{}

此接口中没有定义任何方法,此接口是一个"标识接口"。表示一个类具备了被序列化的能力。

案例:定义可序列化的类。

import java.io.Serializable;
class Persion implements Serializable{
private String name;
private int age;
public Persion(String name, int age){
this.name = name;
this.age = age;
}
public String toString(){
return "姓名:" + this.name + ";年龄:" + this.age;
}
}

类实现了序列化接口,所以此类的对象是可以经过二进制数据流进行传输的。如果要完成对象的输入和输出。还需要依靠对象输出流(ObjectOutputStream)和对象输入流(ObjectInputStream)

序列化:使用对象输出流输出序列化对象的步骤。

反序列化:使用对象输入流读入对象的过程。

注意:对象序列化和对象反序列化操作时存在版本兼容性问题。

如果序列化的JDK版本和反序列化的JDK版本不统一则有可能造成异常。故在序列化操作中引入一个常量

serialVersionUID,通过这个常量验证版本的一致性。如果在使用Serializable接口时没有显示的定义这个常量,则java序列化机制在编译时会自动生成。

2.对象输出流ObjectOutputStream

定义:

public class ObjectOutputStream extends OutputStream implements ObjectOutput, ObjectStreamConstants

继承关系:

ObjectOutputStream类属于OutputStream的子类。

常用方法:

序号 方法 类型 描述
1 public ObjectOutputStream(OutputStream out) throws IOException 构造 传入输出流对象
2 public final void writeObject(Object obj) throws IOException 普通 输出对象

问题:到底序列化了哪些内容?

一个对象被序列化后,到底哪些内容被保存下来,是属性还是方法。

回答:只有属性被序列化了。

使用:使用形式与PrintStream相似。根据传入的OutputStream 子类对象的不同,输出位置也不同。

案例:将上面Persion类的对象保存在文件中。

import java.io.*;
class Persion implements Serializable{
private String name;
private int age;
public Persion(String name, int age){
this.name = name;
this.age = age;
}
public String toString(){
return "姓名:" + this.name + ";年龄:" + this.age;
}
}
public class Demo04 {
public static void main(String[] args)throws Exception {
// 拼凑出可以符合操作系统的路径
String path = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.txt";
File file = new File(path);
ObjectOutputStream oos = null;
OutputStream out = new FileOutputStream(file);
oos = new ObjectOutputStream(out);
oos.writeObject(new Persion("张三",18));
oos.close();
}
}

输出:

注意:以上代码将内容保存在文件中,保存的内容全是二进制数据,但是保存的文件本身不可以直接修改,因为会破坏其保存格式。

3.对象输入流ObjectInputStream

作用:
可以直接把被序列化好的对象反序列化。

定义:

public class ObjectInputStream extends InputStream implements ObjectInput, ObjectStreamConstants

继承关系:

ObjectInputStream 类也是InputStream的子类。

使用:与PrintStream类的使用相似,此类同样需要接收InputStream类的实例才可以实例化。

常用方法:

序号 方法 类型 描述
1 public ObjectInputStream(InputStream in) throws IOException 构造 构造输入对象
2 public final Object readObject()throws IOException, ClassNotFoundException 普通 从指定位置读取对象

案例:将上面序列化的Persion对象读取出来(反序列化)。

import java.io.*;
class Persion implements Serializable{
private String name;
private int age;
public Persion(String name, int age){
this.name = name;
this.age = age;
}
public String toString(){
return "姓名:" + this.name + ";年龄:" + this.age;
}
}
public class Demo04 {
public static void main(String[] args)throws Exception {
// 拼凑出可以符合操作系统的路径
String path = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.txt";
File file = new File(path);
ObjectInputStream ois = null;
InputStream input = new FileInputStream(file);
ois = new ObjectInputStream(input);
Object obj = ois.readObject();
ois.close();
System.out.println(obj);
}
}

输出:

4.Externalizable接口

作用:

用户可以指定序列化的内容,与Serializable接口不同,Serializable接口声明的类的对象的内容都将被序列化。

定义:

public interface Externalizable extends java.io.Serializable{
void writeExternal(ObjectOutput out) throws IOException;
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}

方法介绍:

  1. writeExternal(ObjectOutput out):在此方法中指定要保存的属性信息,对象序列化时调用。
  2. readExternal(ObjectInput in):在此方法中读取被保存的信息,对象反序列化时调用。

参数类型是:ObjectOutput 、ObjectInput

ObjectOutput接口定义:

public interface ObjectOutput extends DataOutput, AutoCloseable

ObjectInput 接口定义:
public interface ObjectInput extends DataInput, AutoCloseable

注意:

如果一个类使用Externalizable实现序列化,在此类中必须存在一个无参构造方法,因为反序列化时会默认调用无参构造实例化对象,如果没有此无参构造,则运行时会出现异常。

案例:修改Persion类并实现Externalizable接口

class Persion implements Externalizable{
private String name;
private int age;
// 无参构造
public Persion(){}
public Persion(String name, int age){
this.name = name;
this.age = age;
}
public String toString(){
return "姓名:" + this.name + ";年龄:" + this.age;
}
// 覆写此方法,根据需要可以保存属性或具体内容,序列化时使用
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(this.name); //保存姓名属性
out.writeInt(this.age); //保存年龄属性
}
//覆写此方法,根据需要读取内容,反序列化时使用
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.name = (String) in.readObject(); //读取姓名属性
this.age = in.readInt(); //读取年龄
}
}

可以进行有选择的保存

案例:序列化和反序列化Persion对象。

import java.io.*;
class Persion implements Externalizable{
private String name;
private int age;
// 无参构造
public Persion(){}
public Persion(String name, int age){
this.name = name;
this.age = age;
}
public String toString(){
return "姓名:" + this.name + ";年龄:" + this.age;
}
// 覆写此方法,根据需要可以保存属性或具体内容,序列化时使用
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(this.name); //保存姓名属性
out.writeInt(this.age); //保存年龄属性
}
//覆写此方法,根据需要读取内容,反序列化时使用
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.name = (String) in.readObject(); //读取姓名属性
this.age = in.readInt(); //读取年龄
}
}
public class Demo04 {
public static void main(String[] args)throws Exception {
// 序列化
ser();
// 反序列化
dser();
}
// 序列化操作
public static void ser()throws Exception{
// 拼凑出可以符合操作系统的路径
String path = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.txt";
File file = new File(path);
ObjectOutputStream oos = null;
OutputStream out = new FileOutputStream(file);
oos = new ObjectOutputStream(out);
oos.writeObject(new Persion("张三",29));
oos.close();
}
// 反序列化操作
public static void dser()throws Exception{
String path = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.txt";
File file = new File(path);
ObjectInputStream ois = null;
InputStream input = new FileInputStream(file);
ois = new ObjectInputStream(input);
Object obj = ois.readObject();
ois.close();
System.out.println(obj);
}
}

Externalizable和Serializable接口实现序列化的区别

序号 区别 Serializable Externalizable
1 实现复杂度 实现简单,Java对其有内建支持 实现复杂,由开发人员自己完成
2 执行效率 所有对象由java统一保存,性能较低 开发人员决定哪个对象保存,速度可能提升
3 保存信息 保存时占用空间大 部分存储,占用空间可能减少

5.transient关键字

作用:

当使用Serializable接口实现序列化操作时,如果一个对象中的某个属性不希望被序列化,则可以使用transient关键字进行声明。

案例:Persion中的name属性不希望被序列化。

class Persion implements Serializable{
private transient String name;
private int age;
public Persion(String name, int age){
this.name = name;
this.age = age;
}
public String toString(){
return "姓名:" + this.name + ";年龄:" + this.age;
}
}

案例:重新保存在读取对象。

import java.io.*;
class Persion implements Serializable{
private transient String name;
private int age;
public Persion(String name, int age){
this.name = name;
this.age = age;
}
public String toString(){
return "姓名:" + this.name + ";年龄:" + this.age;
}
}
public class Demo04 {
public static void main(String[] args)throws Exception {
// 序列化
ser();
// 反序列化
dser();
}
// 序列化操作
public static void ser()throws Exception{
// 拼凑出可以符合操作系统的路径
String path = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.txt";
File file = new File(path);
ObjectOutputStream oos = null;
OutputStream out = new FileOutputStream(file);
oos = new ObjectOutputStream(out);
oos.writeObject(new Persion("张三",29));
oos.close();
}
// 反序列化操作
public static void dser()throws Exception{
String path = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.txt";
File file = new File(path);
ObjectInputStream ois = null;
InputStream input = new FileInputStream(file);
ois = new ObjectInputStream(input);
Object obj = ois.readObject();
ois.close();
System.out.println(obj);
}
}

6.序列化一组对象

使用对象数组进行操作。

因为数组属于引用数据类型所以可以直接使用Object类进行接收。

案例:序列化多个Persion对象

import java.io.*;
class Persion implements Serializable{
private String name;
private int age;
public Persion(String name, int age){
this.name = name;
this.age = age;
}
public String toString(){
return "姓名:" + this.name + ";年龄:" + this.age;
}
}
public class Demo04 {
public static void main(String[] args)throws Exception {
Persion[] per = {new Persion("张三",18),new Persion("李四",30)
,new Persion("王五",20)};
// 序列化
ser(per);
// 反序列化
Object[] o = dser();
for (int i = 0; i < o.length; i++) {
Persion p = (Persion) o[i];
System.out.println(p);
}
}
// 序列化操作
public static void ser(Object obj)throws Exception{
// 拼凑出可以符合操作系统的路径
String path = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.txt";
File file = new File(path);
ObjectOutputStream oos = null;
OutputStream out = new FileOutputStream(file);
oos = new ObjectOutputStream(out);
oos.writeObject(obj);
oos.close();
}
// 反序列化操作
public static Object[] dser()throws Exception{
String path = "D:" + File.separator + "DLU" + File.separator + "studyNode" +
File.separator + "java" + File.separator + "IO" + File.separator + "test.txt";
File file = new File(path);
ObjectInputStream ois = null;
InputStream input = new FileInputStream(file);
ois = new ObjectInputStream(input);
Object[] obj = (Object[]) ois.readObject();
ois.close();
return obj;
}
}

输出:

对象数组可以保存对个对象,但是数组本身存在长度限制。为了解决数组中长度的问题,可以使用动态对象数组(类集)完成。

posted @   e路有你  阅读(9)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示