Java--IO 文件处理和流传输

不管是什么开发语言,都要提供对硬盘数据的处理功能,Java自然也不能例外。什么是I/O呢?I/O(input/output)即输入/输出。Java中的I/O操作是通过输入/输出数据流的形式来完成的,因此它也称作数据流操作。

简单来说,I/O实际上就是一种数据的输入/输出方式,其中输入模式是指允许程序读取外部程序(包括来自磁盘、光盘等存储设备的数据)、用户输入的数据。这些数据源都是文件、网络、压缩包或其他数据。下图即为输入模式:

未命名文件 (1)

输出模式与输入模式恰好相反,输出模式是指允许程序记录运行状态,将程序数据输出到磁盘、光盘等存储设备中。

Java I/O操作主要指的是使用 Java 进行输入、输出操作,Java 中的所有操作类都存放在 java.io 包中,在使用时需要导入此包。

在整个 java.io 包中最重要的就是 5 个类 和 1个接口,这5个类分别是 File、OutputStream、InputStream、Writer 和 Reader,1个接口是 Serializable。

File 类

在整个 I/O 包中,唯一与文件有关的类就是 File 类。使用 File 类可以实现创建或删除文件等常用的操作功能。File 类的方法如下:

方法/常量类型描述
public static final String pathSeparator常量路径分隔符,Windows系统中为 “;”
public static final String separator常量路径分隔符,Windows系统中为 “\”
public File(String pathname)构造创建File类对象,传入完整路径
public boolean createNewFile throws IOException普通创建新文件
public boolean delete()普通删除文件
public boolean exists()普通判断文件是否存在
public boolean isDirectory()普通判断给定路径是否是一个目录
public long length()普通返回文件的大小
public String[] list()普通列出指定目录的全部内容,只列出名称
public File[] listFiles()普通列出指定目录的全部内容,会列出路径
public boolean mkdir()普通创建一个目录
public boolean renameTo(File dest)普通为已有的文件重新命名

代码示例1:在 E盘下创建一个名为 test.txt的文件

import java.io.File;
import java.io.IOException;

public class TestFile {

	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		File f = new File("E:" + File.separator + "test.txt");
		if(f.exists()) { // 如果文件已存在,则删除原有文件
			f.delete();
		}
		f.createNewFile();
	}
}

代码示例2:在 E盘下创建一个名为 text 的文件夹

import java.io.File;
import java.io.IOException;

public class TestFile {

	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		File f = new File("E:" + File.separator + "text");
		if(f.exists()) { // 如果目录已存在,则删除原有目录
			f.delete();
		}
		f.mkdir();
	}
}

代码示例3:列出E盘下名为 QQ消息记录文件夹下的全部内容,名称+路径

import java.io.File;
import java.io.IOException;

public class TestFile {

	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		File f = new File("E:" + File.separator + "QQ消息记录");
		
		// 列出指定目录下的全部内容,只列出名称
		String[] list1 = f.list();
		for(String fileName : list1) {
			System.out.println(fileName);
		}
		
		// 列出指定目录下的全部内容,会列出路径
		File[] list2 = f.listFiles();
		for(File file : list2) {
			System.out.println(file.getAbsolutePath());
		}
	}
}

RandomAccessFile 类

File 类针对文件本身进行操作,不对文件内容进行操作。类 RandomAccessFile 属于随机读取类,可以随机读取一个文件中指定位置的数据。RandomAccessFile 类的常用方法如下:

public RandomAccessFile(File file,String mode) throws FileNotFoundException;
// 接收File 类对象,设置模式,r为只读,w为只写,rw为读写
public RandomAccessFile(String name,String mode) throws FileNotFoundException;
// 输入固定的文件路径,设置模式同上
public void close() throws IOException;
// 关闭操作
public int read(byte[] b) throws IOException;
// 将内容读取到一个字节数组中
public final byte readByte() throws IOException;
// 读取一个字节
public final int readInt() throws IOException;
// 从文件中读取整型数据
public void seek(long pos) throws IOException;
// 设置读指针的位置
public final void writeBytes(String s) throws IOException;
// 将一个字符串写入到文件中,按字节的方式处理
public final void writeInt(int v) throws IOException;
// 讲一个int类型数据写入文件,长度为4位
public int skipBytes(int n) throws IOException;
// 指针跳过多少个字节

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

代码示例:使用RandomAccessFile 类读取数据,并在内容末尾加上内容

假设E:\\test.txt文件中有以下三行数据:

Hello World!!!
Hello Wuhan!!!
Hello HUST!!!

代码目标是读取这三行数据输出到控制台,并将Hello CSDN!!!加到数据末尾。

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;

public class TestFile {

	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		// 指定要操作的文件
		File f = new File("E:" + File.separator + "test.txt");
		
		// 声明RandomAccessFile对象,设定读写模式
		RandomAccessFile rdf = new RandomAccessFile(f,"rw");
		
		// 读取文件内容
		byte[] buf = new byte[(int) f.length()];
		int len = rdf.read(buf);
		// 输出读取的内容
		System.out.println(new String(buf));
		
		// 向文件中写入内容
		String s = "\n" + "Hello CSDN!!!";
		byte[] b = s.getBytes();
		rdf.write(b);
	}
}

字节流与字符流

在 java.io包中流操作主要有字节流和字符流类两大类,这两个类都有输入和输出操作。字节流使用 OutputStream 类输出数据,使用 InputStream 类输入数据。字符流使用 Writer 类输出数据,使用 Reader 类完成输入数据。

I/O操作是有相应步骤的,以文件操作为例,主要操作流程包括:

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

字节输出流

OutputStream 是字节输出流的最大父类,它是一个抽象类,要先通过子类实例化对象才能使用此类。OutputStream 类的常用方法如下:

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

代码示例:向 E盘中的 test.txt 文件中写入 Hello World!!!

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class TestFile {

	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		// 1.使用类 File 打开一个文件
		File f = new File("E:" + File.separator + "test.txt");
		
		// 2.使用OutputStream的子类指定输出位置
		OutputStream out = new FileOutputStream(f);
		
		// 3.进行读写操作
		String str = "Hello World!!!";
		byte[] b = str.getBytes();
		out.write(b);
		
		// 4.关闭输入/输出
		out.close();
	}
}

代码示例2:在E盘中的 test.txt 文件中追加数据 Hello CSDN!!!

上面的代码,如果重新执行程序,则会覆盖文件中的内容。想要在原数据后追加新的数据,可以使用子类 FileOutputStream的另一个构造方法:

public FileOutputStream(File file,boolean append) throws FileNotFoundException;// append设为true,即为追加
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class TestFile {

	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		// 1.使用类 File 打开一个文件
		File f = new File("E:" + File.separator + "test.txt");
		
		// 2.使用OutputStream的子类指定输出位置
		OutputStream out = new FileOutputStream(f,true);
		
		// 3.进行读写操作
		String str = "\r\n Hello World!!!";
		byte[] b = str.getBytes();
		out.write(b);
		
		// 4.关闭输入/输出
		out.close();
	}
}

注意:对于写入的数据要换行。直接在字符串要换行处加入一个“\r\n”即可。

字节输入流

InputStream 类也是一个抽象类,其子类是 FileInputStream 类。InputStream 类的主要方法如下:

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

代码示例1:使用read(byte[] b) 方法读取 E盘中 test.txt文件的内容

public class TestFile {

	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		// 1.使用类 File 打开一个文件
		File f = new File("E:" + File.separator + "test.txt");
		
		// 2.使用InputStream的子类指定输出位置
		InputStream input = new FileInputStream(f);
		
		// 3.进行读写操作
		byte[] b = new byte[(int) f.length()];
		int len = input.read(b);
		System.out.println(new String(b));
		
		// 4.关闭输入/输出
		input.close();
	}
}

上述代码中可以通过 f.length() 指到存放数据的字节数组的具体大小,但是在文件长度无法知道的情况下,可以用下面的代码来进行:

代码示例2:未知内容时读取文件的内容

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class TestFile {

	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		// 1.使用类 File 打开一个文件
		File f = new File("E:" + File.separator + "test.txt");
		
		// 2.使用InputStream的子类指定输出位置
		InputStream input = new FileInputStream(f);
		
		// 3.进行读写操作
		byte[] buf = new byte[1024]; // 数组大小由文件决定
		int len = 0;	// 初始化变量len
		int temp = 0;	// 初始化变量temp
		while((temp = input.read()) != -1) {
			// 接收每一个进行来的数据
			buf[len] = (byte)temp;
			len++;
		}
		
		// 4.关闭输入/输出
		input.close();
		System.out.println("内容为:" + new String(buf,0,len));
	}
}

字符输出流

Writer 类是抽象类,其子类为 FileWriter 类,常用的方法如下:

方法类型描述
public abstract void close() throws IOException普通关闭输出流
public void write(String str) throws IOException普通输出字符串
public void write(char[] ch) throws IOException普通输出字符串组
public abstract void flush() throws IOException普通强制性清空缓存

代码示例1:使用FileWriter 向指定文件中写入内容

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

public class TestWriter {

	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		// 使用类File打开一个文件
		File file = new File("E:" + File.separator + "test.txt");
		
		// 获取字符输出流对象
		Writer writer = new FileWriter(file);
		
		// 写入内容
		String str = "Java从入门到精通";
		writer.write(str);
		
		// 关闭输出流
		writer.close();
	}

}

代码示例2:使用FileWriter 类在指定文件中追加内容

public FileWriter(File file,boolean append) throws IOException; // append为true,则追加
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

public class TestWriter {

	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		// 使用类File打开一个文件
		File file = new File("E:" + File.separator + "test.txt");
		
		// 获取字符输出流对象
		Writer writer = new FileWriter(file,true);
		
		// 写入内容
		String str = "\r\n Java 网络编程";
		writer.write(str);
		
		// 关闭输出流
		writer.close();
	}

}

字符输入流

Reader类 同样是抽象类,子类为FileReader类,常用方法如下:

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

代码示例:使用FileReader 读取指定文件中的内容

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;

public class TestReader {

	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		File file = new File("E:" + File.separator + "test.txt");
		
		Reader reader = new FileReader(file);
		
		char[] ch = new char[1024];
		int len = reader.read(ch);
		
		reader.close();
		System.out.println("内容为:" + new String(ch,0,len));
	}

}

字节转换流

流的类型分为字节流和字符流,除此之外,还存在一组“字节流-字符流”的转换类,用于两者之间的转换。

  • OutputStreamWriter:Writer的子类,字符输出流转字节输出流。
  • InputStreamReader:Reader的子类,字节输入流转字符输入流。

代码示例:操作指定文件

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;

public class Test {

	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		File file = new File("E:" + File.separator + "test.txt");
		
		InputStream input = new FileInputStream(file);
		
		// 字节输入流转字符输入流
		Reader reader = new InputStreamReader(input);
		// 读取内容
		char[] ch = new char[1024];
		int len = reader.read(ch);
		// 输出读取内容
		System.out.println("内容为:" + new String(ch,0,len));
		
		// 一定要先关闭流,才能进行后续写操作
		reader.close();
		input.close();
		
		// 定义字节输出流,追加内容
		OutputStream out = new FileOutputStream(file,true);
		
		// 字节输出流转字符输出流
		Writer writer = new OutputStreamWriter(out);
		// 写入内容
		String str = "\r\n Java语言描述 算法与数据结构";
		writer.write(str);		
		
		writer.close();
		out.close();
	}

}

内存操作流

一般在生成一些临时信息时才会使用内存操作流,这些临时信息如果要保存到文件中,则代码执行完成后还要删除这个临时文件,所以此时使用内存操作流是最合适的。内存操作流中 ByteArrayInputStream 类用于将内容写入内存中,ByteArrayOutputStream 类用于输出内存中的数据。

下面为ByteArrayInputStream 类的主要方法:

方法类型描述
public ByteArrayInputStream(byte[] buf)构造将全部内容写入内存中
public ByteArrayInputStream(byte[] buf,int offset,int lenght)构造将指定范围的内容写入到内存中

下面为ByteArrayOutputStream 类的主要方法:

方法类型描述
public ByteArray OutputStream()构造创建对象
public void write(int b)普通将内容从内存中输出

代码示例:使用内存操作流将一个大写字母转换为小写字母

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

public class ByteArrayTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		String str = "HELLOWORLD"; // 定义字符串,全部由大写字母组成
		// 向内存中输出内容
		ByteArrayInputStream bis = new ByteArrayInputStream(str.getBytes());
		// 准备从内存中读取内容
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		
		int temp = 0;
		while((temp = bis.read()) != -1) {
			char c = (char) temp; // 读取的数字变成字符
			bos.write(Character.toLowerCase(c)); // 将字符变成小写
		}
		
		// 所有数据全部都在ByteArrayOutputStream中
		String newStr = bos.toString(); // 取出内容
		try {
			bis.close();
			bos.close();
		}catch(IOException e) {
			e.printStackTrace();
		}
		System.out.println(newStr);
	}

}

管道流

管道流用于实现两个线程间的通信,这两个线程为管道输出流(PipedOutputStream)和管道输入流(PipedInputStream)。要进行管道输出,就必须把输出流连到输入流上。使用 PipedOutputStream 类以下方法可以实现连接管道功能。

public void connect(PipedInputStream pis) throws IOException;

代码示例:使用管道流实现线程连接

import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;

class Send implements Runnable{ // 线程类,管道发送线程
	private PipedOutputStream pos = null; // 管道输出流
	
	public Send() {
		this.pos = new PipedOutputStream(); // 实例化输出流
	}
	
	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(); // 实例化输入流
	}
	
	public void run() {
		byte b[] = new byte[1024]; // 接收内容
		int len = 0;
		try {
			len = this.pis.read(b); // 读取内容
		}catch(IOException e) {
			e.printStackTrace();
		}
		try {
			this.pis.close(); // 关闭
		}catch(IOException e){
			e.printStackTrace();
		}
		System.out.println("接收的内容:" + new String(b,0,len));
	}
	
	public PipedInputStream getPis() {
		return this.pis;
	}
	
}

public class PipedTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Send s = new Send();
		Receive r = new Receive();
		try {
			s.getPos().connect(r.getPis()); // 连接管道
		}catch(Exception e){
			e.printStackTrace();
		}
		new Thread(s).start(); // 启动线程
		new Thread(r).start(); // 启动线程
	}

}

打印流

打印流是输出信息最方便的一个类,主要包括字节打印流(PrintStream)和字符打印流(PrintWriter)。打印流提供了非常方便的打印功能,通过打印流可以打印任何的数据类型,例如小数、整数、字符串等。

PrintStream 类是 OutputStream 的子类,PrintStream 类的常用方法:

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

printf()方法的使用类似于C语言。

与OutputStream 类相比,PrintStream 类能更加方便地输出数据。

代码示例

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;

public class PrintTest {

	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		// 声明打印流对象
		PrintStream ps = new PrintStream(new FileOutputStream(new File("E:" + File.separator) + "test.txt"));
		
		// 写入文件内容
		ps.print("hello ");
		ps.println("world!!!");
		ps.print("1 + 1 = " + 2);
		
		// 关闭
		ps.close();
		
	}

}

BufferedReader 类

BufferedReader 类能够从缓冲区中读取内容,所有的字节数据都将放进缓冲区中。常用方法如下:

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

因为在 BufferedReader 类中定义的构造方法只能接收字符输入流的实例,所以必须使用字符输入流和字节输入流的转换 InputStreamReader 类将字节输入流 System.in 变为字符流。因为每一个汉字要占两字节,所以需要将 System.in 这个字节输入流变为字符输入流。当将 System.in 变为字符流放入到 BufferedReader后,可以通过方法 readLine()等待用户输入信息。

代码示例:输入两个数字,并让两个数字相加

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class BufferedTest {

	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		int i = 0;
		int j = 0;
		
		// 创建一个输入流,用于接收键盘输入的数据
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		String str = null;
		System.out.println("请输入第一个数字:");
		
		// 接收数据
		str = br.readLine();
		// 将字符串变为整数
		i = Integer.parseInt(str);
		System.out.println("请输入第二个数字:");
		str = br.readLine();
		j = Integer.parseInt(str);
		
		// 输出结果
		System.out.println(i + " + " + j + " = " + (i+j));
		
		// 关闭
		br.close();
	}

}

数据操作流

在 Java 的 I/O 包中,提供了两个与平台无关的数据操作流,它们分别为数据输出流(DataOutputStream)和数据输入流(DataInputStream)。数据输出流会按照一定的格式输出数据,再通过数据输入流按照一定的格式将数据读入,这样可以方便对数据进行处理。

常见的订单数据就适合用数据操作流来实现。

商品名价格/元数量/个
帽子98.33
衬衣30.32
裤子50.51

DataOutputStream 类

public class DataOutputStream extends FilterOutputStream implements DataOutput;

DataOutputStream 类继承 FilterOutputStream 类(FilterOutputStream 和 OutputStream 的子类),同时实现了 DataOutput 接口,在 DataOutput 接口定义了一系列写入各种数据的方法。

DataOutput 是数据的输出接口,其中定义了各种数据的输出操作方法,例如在 DataOutputStream 类中的各种 writeXxx() 方法就是此接口定义的。但是在数据输出时一般会直接使用DataOutputStream。

DataOutputStream 类常用方法如下:

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

代码示例:将订单数据写入到 order.txt 中

import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;

public class DataOutputTest {

	public static void main(String[] args) throws Exception{ //抛出所有异常
		// TODO Auto-generated method stub
		File file = new File("E:" + File.separator + "order.txt");
		if(!file.exists()) {
			file.createNewFile();
		}
		DataOutputStream dos = new DataOutputStream(new FileOutputStream(file));
		
		String itemNames[] = {"帽子","衬衣","裤子"};
		Float price[] = {98.3f,30.3f,50.5f};
		int nums[] = {3,2,1};
		
		for(int i = 0;i < itemNames.length; i++) {
			dos.writeChars(itemNames[i]);
			// 写入分隔符
			dos.writeChar('\t');
			dos.writeFloat(price[i]);
			dos.writeChar('\t');
			dos.writeInt(nums[i]);
			// 写入换行符
			dos.writeChar('\n');
		}
		
		// 关闭
		dos.close();
	}

}

这个时候运行,发现出现了乱码问题,如下面这张图

image-20220416144510051

可以看到编码格式为ANSI,为了防止乱码,最好设置字符编码为UTF-8,在写入的时候调用writeUTF()方法。(暂且想不到其它更好的方法)

import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;

public class DataOutputTest {

	public static void main(String[] args) throws Exception{ //抛出所有异常
		// TODO Auto-generated method stub
		File file = new File("E:" + File.separator + "order.txt");
		DataOutputStream dos = new DataOutputStream(new FileOutputStream(file));
	
		String names[] = {"衬衣","手套","围巾"}; // 商品名称
		Float prices[] = {98.3f,30.3f,50.5f}; // 商品价格
		int nums[] = {3,2,1};	// 商品数量
		
		for(int i = 0;i < names.length; i++) {
			dos.writeUTF(names[i]);
			// 写入分隔符
			dos.writeChar('\t');
			dos.writeUTF(prices[i].toString());
			dos.writeChar('\t');
			dos.writeUTF(nums[i] + "");
			// 写入换行符
			dos.writeChar('\n');
		}
		
		// 关闭
		dos.close();
	}

}

image-20220416152524587

DataInputStream 类

DataInputStream 类是 InputStream 的子类,能够读取并使用 DataOutputStream 输出的数据。

public class DataInputStream extends FilterInputStream implements DataInput

常用方法如下:

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

代码示例

import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
public class DataInputTest{
    public static void main(String args[]) throws Exception{
        File file = new File("E:" + File.seperator + "order.txt");
        DataInputStream dis = new DataInputStream(new FileInputStream(file));
        
        String name = null; // 接收名称
        float price = null; // 接收价格
        int num = 0; // 接收数量
        char temp[] = null; // 接收商品名称
        int len = 0; // 保存读取数据的个数
        char c = 0; // '\u0000'
        try{
            while(true){
                temp = new char[200]; // 开辟空间
                len = 0;
                while((c = dis.readChar()) != '\t'){
                    // 接收内容
                    temp[len] = c;
                    len++; // 读取长度加1
                }
                name = new String(temp,0,len); // 将字符数组变为String
                price = dis.readFloat(); // 读取价格
                dis.readChar(); // 读取\t
                num = dis.readInt(); // 读取int
                dis.readChar(); //读取\n
                System.out.printf("名称:%s; 价格:%5.2f; 数量:%d\n",name,price,num);
            }
        }catch(Exception e){}
        dis.close();
    }
}

压缩流

Java 提供了专门的压缩,可以将文件或文件夹压缩成 ZIP、JAR、GZIP 等文件形式。这里只写下压缩成ZIP的笔记。

ZIP 是一种较为常见的压缩形式,在 Java 中要实现 ZIP 压缩需要导入 java.util.zip 包,然后使用此包中的 ZipFile、ZipOutputStream、ZipIntputStream 和 ZipEntry 几个类完成操作。

在每个压缩文件中都会存在多个子文件,每个子文件在 Java 中都使用 ZipEntry 来表示。ZipEntry 常用方法如下:

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

ZipOutputStream 类

ZipOutputStream 类用于完成一个文件或文件夹的压缩。常用操作方法如下:

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

代码示例1:将E盘中的test.txt 文件压缩成文件 www.zip

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class ZipOutputTest {

	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		File file = new File("E:" + File.separator + "test.txt"); // 定义要压缩的文件
		File zipFile = new File("E:" + File.separator + "www.zip"); // 定义压缩文件的名称
		InputStream input = new FileInputStream(file); //文件输入流
		
		// 压缩输出流
		ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(zipFile));
		// 设置ZipEntry 对象
		zipOut.putNextEntry(new ZipEntry(file.getName()));
		// 设置注释
		zipOut.setComment("www.www.cn");
		
		int temp = 0;
		while((temp = input.read()) != -1) { // 读取内容
			zipOut.write(temp); // 压缩输出
		}
		
		// 关闭输入/输出流
		input.close();
		zipOut.close();
		
	}

}

代码运行后得到压缩文件,可以看下面这张图。

image-20220416162829011

代码示例2:压缩E盘中名为test的文件夹

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class ZipOutputTest2 {

	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		// 要压缩的文件夹
		File file = new File("E:" + File.separator + "test");
		// 压缩文件
		File zipFile = new File("E:" + File.separator + "www2.zip");
		
		// 声明文件输入流
		InputStream input = null;
		// 定义压缩流
		ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(zipFile));
		// 注释
		zipOut.setComment("www.www.cn");
		
		int temp = 0;
		if(file.isDirectory()) { // 判断是否是文件夹
			File lists[] = file.listFiles(); // 列出全部文件
			for(int i=0; i < lists.length; i++) {
				input = new FileInputStream(lists[i]); // 定义文件的输入流
				// 设置ZipEntry 对象
				zipOut.putNextEntry(new ZipEntry(file.getName() + File.separator + lists[i].getName()));
				
				// 读取内容
				while((temp = input.read())!=-1) {
					zipOut.write(temp);
				}
				
				// 关闭输入流
				input.close();
			}
		}
		
		// 关闭输出流
		zipOut.close();
	}

}

代码运行后的结果可以看下图。

image-20220416163827209

ZipFile 类

每个压缩文件都可以用File 类或ZipFile 类来表示。可以使用 ZipFile 根据压缩后的文件名找到每个压缩文件中的 ZipEntry 并对其执行解压缩操作。ZipFile 类常用方法如下:

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

代码示例:实例化 ZipFile 对象

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import java.util.zip.ZipFile;
import java.io.FileOutputStream;
public class ZipFileTest{
    public static void main(String args[]) throws Exception{ //抛出所有异常
        File file = new File("E:" + File.seperator + "www.zip");
        ZipFile zipFile = new ZipFile(file); // 实例化ZipFile 对象
        System.out.println("压缩文件的名称" + zipFile.getName()); // 得到压缩对象的名称
    }
}

ZipInputStream 类

ZipInputStream 类是 InputStream 类的子类,通过此类可以方便地读取ZIP 格式的压缩文件,常用方法如下:

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

通过使用 ZipInputStream 类中的 getNextEntry() 方法可以依次取得每个 ZipEntry,这样可将此类与 ZipFile 结合从而对压缩的文件夹执行解压缩的操作。

代码示例:获取www2.zip 中的所有 ZipEntry

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

public class ZipInputTest {

	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		// 定义压缩文件的名称
		File zipFile = new File("E:" + File.separator + "www2.zip");
		// 定义压缩输入流
		ZipInputStream input = new ZipInputStream(new FileInputStream(zipFile));
		
		// 获取所有的ZipEntry
		ZipEntry entry = null;
		while((entry = input.getNextEntry())!=null) {
			System.out.println("压缩实体名称:" + entry.getName());
		}
	}

}

执行结果(与前面的图片对照)

压缩实体名称:test\missfont.log
压缩实体名称:test\test.py
压缩实体名称:test\text.aux
压缩实体名称:test\text.dvi
压缩实体名称:test\text.fdb_latexmk
压缩实体名称:test\text.fls
压缩实体名称:test\text.log
压缩实体名称:test\text.pdf
压缩实体名称:test\text.synctex.gz
压缩实体名称:test\text.tex

字符编码

在计算机世界,任何文字都是以指定的编码方式存在的,在Java 中最常见的有 ISO8859-1、GBK/GB2312、Unicode、UTF编码。

ISO8859-1属于单字节编码,最多只能表示 0~255个字符,主要是英文上应用。

GBK/GB2312是中文的国际编码,是双字节编码,专门用来表示汉字。如果在此编码中出现英文,则使用ISO8859-1编码。GBK可以表示简体和繁体中文;而GB2312只能表示简体中文。GBK兼容GB2312

Unicode是最标准的一种编码,使用十六进制表示编码,它的问题在于不兼容ISO8859-1编码。

UTF用于弥补Unicode编码的不足。由于Unicode不支持ISO8859-1编码,且自身是十六进制编码容易占有更多的空间,再加上英文字母也需要使用两个字节来编码,使得Unicode不便于传输和存储,因此产生了UTF编码。UTF编码可以用来表示所有的语言字符,也兼容ISO8859-1编码。缺点是字符长度不等。

我们开发中文网页一般都用UTF编码

关于乱码问题

在程序中如果处理不好字符的编码,那么就有可能出现乱码问题。如果现在本机的默认编码是GBK,但在程序中使用了ISO8859-1编码,则会出现字符的乱码问题。

如果要避免产生乱码,则程序的编码与本地的默认编码要保持一致。

代码示例1:通过System类来得到本机编码

public class CharSetTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		// 获取当前系统编码
		System.out.println("系统默认编码:" + System.getProperty("file.encoding"));
	}

}

运行结果

系统默认编码:GBK

String 类中的 getBytes(String charset)方法可以设置文件的编码。

代码示例2:通过getBytes方法产生乱码

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class CharSetTest2 {

	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		File file = new File("E:" + File.separator + "test.txt");
		OutputStream out = new FileOutputStream(file);
		
		// 实例化输出流
		// 转码保存
		byte b[] = "甄嬛传永远滴神".getBytes("ISO8859-1");
		// 写入
		out.write(b);
		// 关闭输出流
		out.close();
	}

}

运行结果

image-20220417115019660

对象序列化

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

Serializable 接口

如果我们想使自己创建的一个类的对象进行序列化,那么必须让这个类继承Serializable接口。

public interface Serializable{};

这个接口里面是没有定义任何方法的,它只是一个标识接口,实现了这个接口,就标示这该类可以被序列化。

代码示例:定义一个可序列化的类

import java.io.Serializable;

public class AdminUser implements Serializable{
	private String username;
	private String password;
	private int age;
	
	public AdminUser() {}
	
	public AdminUser(String username,String password,int age) {
		this.username = username;
		this.password = password;
		this.age = age;
	}
	
	public void setUsername(String username) {
		this.username = username;
	}
	
	public void setPassword(String password) {
		this.password = password;
	}
	
	public void setAge(int age) {
		this.age = age;
	}
	
	public String getUsername() {
		return this.username;
	}
	
	public String getPassword() {
		return this.password;
	}
	
	public int getAge() {
		return this.age;
	}
	
	@Override
	public String toString() {
		return "用户名:" + this.username + "; 密码:" + this.password
				+ "; 年龄:" + this.age;
	}
}

仅仅这样实现了接口还不够,真正的序列化要借助于对象输入/输出流。对象输出流进行序列化操作,将类的对象转化为二进制数据进行保存。对象输入流进行反序列化操作,将保存的二进制数据转化为类的对象。

ObjectOutputStream 类

对象输出流 ObjectOutputStream 类的常用方法如下:

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

代码示例:将上面的AdminUser类的对象保存到文件中

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class ObjectOutputTest {

	public static void main(String[] args) throws IOException{
		// TODO Auto-generated method stub
		File file = new File("E:" + File.separator + "test.txt");
		// 对象输出流
		ObjectOutputStream objectOut = new ObjectOutputStream(new FileOutputStream(file));
		
		// 保存AdminUser对象
		objectOut.writeObject(new AdminUser("admin", "123456", 18));
		// 关闭输出流
		objectOut.close();
	}

}

运行结果

image-20220417121352931

ObjectInputStream 类

对象输入流ObjectInputStream 类的主要操作方法如下:

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

代码示例:反序列化保存在文件中的AdminUser对象

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class ObjectInputTest {

	public static void main(String[] args) throws IOException, ClassNotFoundException{
		// TODO Auto-generated method stub
		File file = new File("E:" + File.separator + "test.txt");
		
		// 实例化对象输入流
		ObjectInputStream objectInput = new ObjectInputStream(new FileInputStream(file));
		// 读取对象
		AdminUser adminUser = (AdminUser) objectInput.readObject();
		// 关闭输入流
		objectInput.close();
		// 输出对象
		System.out.println(adminUser.toString());
	}

}

代码执行结果

用户名:admin; 密码:123456; 年龄:18

根据代码执行结果,我们可以发现,对象输出流把我们定义的AdminUser类的所有信息都序列化保存到文件里面去了。

但是现在我们有一个新的需求,假如说甲方爸爸希望我们只将部分信息序列化保存到文件,该怎么完成这个需求?——如果我们想根据自己的需要选择序列化的属性,就可以使用另一个序列化接口:Externalizable或者关键字transient

Externalizable 接口

此接口是Serializable接口的子接口。

public interface Externalizable extends Serializable{
    public void writeExternal(ObjectOutput out) throws IOException;
    public void readExternal(ObjectInput in) throws IOException,ClassNotFoundException;
}
  • write(ObjectOutput out):在此方法中指定要保存的属性信息,它在对象序列化时调用。
  • readExternal(ObjectInput in):在此方法中读取保存的属性信息,它在对象反序列话时调用。

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

代码示例1:定义一个Department类实现此接口

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

public class Department implements Externalizable{
	private long departmentId;
	private String departmentName;
	
	// 无参构造
	public Department() {}
	
	// 有参构造
	public Department(long departmentId,String departmentName) {
		this.departmentId = departmentId;
		this.departmentName = departmentName;
	}
	
	// 覆盖此方法,根据需要读取内容,反序列化时使用它
	@Override
	public void readExternal(ObjectInput in) throws IOException,ClassNotFoundException{
		this.departmentName = (String) in.readObject(); // 读取departmentName
	}
	
	// 覆盖此方法,根据需要可以保存属性或具体内容,序列化时使用它
	@Override
	public void writeExternal(ObjectOutput out) throws IOException{
		out.writeObject(this.departmentName);
	}
	
	public void setDepartmentId(long departmentId) {
		this.departmentId = departmentId;
	}
	
	public void setDepartmentName(String departmentName) {
		this.departmentName = departmentName;
	}
	
	public long getDepartmentId() {
		return this.departmentId;
	}
	
	public String getDepartmentName() {
		return this.departmentName;
	}
	
	@Override
	public String toString() {
		return "部门ID:" + this.departmentId + "部门名称:" + this.departmentName;
	}
}

上面的代码设定上我们只序列列部门名称DepartmentName

代码示例2:序列化Deparment类的对象

import java.io.File;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;

public class ExternalTest {

	public static void main(String[] args) throws Exception{
		// TODO Auto-generated method stub
		File file = new File("E:" + File.separator + "test.txt");
		ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));
		Department department = new Department(1,"研发部");
		// 保存对象
		department.writeExternal(out);
		// 关闭输出流
		out.close();
	}

}

代码执行结果

image-20220417140023508

代码示例3:反序列化Department 类的对象

import java.io.File;
import java.io.FileInputStream;
import java.io.ObjectInputStream;

public class ExternalTest2 {

	public static void main(String[] args) throws Exception{
		// TODO Auto-generated method stub
		File file = new File("E:" + File.separator + "test.txt");
		ObjectInputStream input = new ObjectInputStream(new FileInputStream(file));
		Department department = new Department();
		// 反序列读取对象
		department.readExternal(input);
		System.out.println(department.toString());
		
		//关闭输入流
		input.close();
	}

}

代码执行结果

部门ID:0部门名称:研发部

这里部门ID为0是因为只反序列化读取了部门名称,调用toString()方法的时候,就使用了long变量的默认值0。

关键字 transient

上面实现Externalizable接口的方式比较麻烦。我们可以用关键字 transient来声明不希望被序列化的属性。例如不希望AdminUser类的age属性被序列化,可以这样写:

private transient int age; // 此属性将不被序列化

其它的和实现Serializable接口相同。

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值