不管是什么开发语言,都要提供对硬盘数据的处理功能,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。
在整个 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());
}
}
}
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操作是有相应步骤的,以文件操作为例,主要操作流程包括:
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));
}
}
流的类型分为字节流和字符流,除此之外,还存在一组“字节流-字符流”的转换类,用于两者之间的转换。
代码示例:操作指定文件
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 类能够从缓冲区中读取内容,所有的字节数据都将放进缓冲区中。常用方法如下:
方法 | 类型 | 描述 |
---|---|---|
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 |
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 类是 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 类用于完成一个文件或文件夹的压缩。常用操作方法如下:
方法 | 类型 | 描述 |
---|---|---|
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();
}
}
代码运行后的结果可以看下图。
每个压缩文件都可以用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 类是 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();
}
}
运行结果
对象序列化就是把一个对象变为二进制数据流的一种方法,通过对象序列化可以方便地实现对象的传输或存储。
如果我们想使自己创建的一个类的对象进行序列化,那么必须让这个类继承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 类的常用方法如下:
方法 | 类型 | 描述 |
---|---|---|
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 类的主要操作方法如下:
方法 | 类型 | 描述 |
---|---|---|
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
。
此接口是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。
上面实现Externalizable
接口的方式比较麻烦。我们可以用关键字 transient
来声明不希望被序列化的属性。例如不希望AdminUser类的age属性被序列化,可以这样写:
private transient int age; // 此属性将不被序列化
其它的和实现Serializable
接口相同。