不管是什么开发语言,都要提供对硬盘数据的处理功能,Java自然也不能例外。什么是I/O呢?I/O(input/output)即输入/输出。Java中的I/O操作是通过输入/输出数据流的形式来完成的,因此它也称作数据流操作。
简单来说,I/O实际上就是一种数据的输入/输出方式,其中输入模式是指允许程序读取外部程序(包括来自磁盘、光盘等存储设备的数据)、用户输入的数据。这些数据源都是文件、网络、压缩包或其他数据。下图即为输入模式:

输出模式与输入模式恰好相反,输出模式是指允许程序记录运行状态,将程序数据输出到磁盘、光盘等存储设备中。
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操作是有相应步骤的,以文件操作为例,主要操作流程包括:
- 使用类 File 打开一个文件
- 通过字节流或字符流的子类指定输出位置
- 进行读/写操作
- 关闭输入/输出
字节输出流
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.3 | 3 | 
| 衬衣 | 30.3 | 2 | 
| 裤子 | 50.5 | 1 | 
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();
	}
}
这个时候运行,发现出现了乱码问题,如下面这张图

可以看到编码格式为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();
	}
}

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();
		
	}
}
代码运行后得到压缩文件,可以看下面这张图。

代码示例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();
	}
}
代码运行后的结果可以看下图。

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编码。缺点是字符长度不等。
关于乱码问题
在程序中如果处理不好字符的编码,那么就有可能出现乱码问题。如果现在本机的默认编码是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();
	}
}
运行结果

对象序列化
对象序列化就是把一个对象变为二进制数据流的一种方法,通过对象序列化可以方便地实现对象的传输或存储。
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();
	}
}
运行结果

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();
	}
}
代码执行结果

代码示例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接口相同。










