IO 操作
程序运行期间,可能需要从外部的存储媒介或其他程序中读入需要的数据,这就需要使用输入流对象。输入流的指向称作它的源,程序从指向源的输入流中读取源中的数据。另一方面,程序在处理数据后,可能需要将处理的结果写入到永久的存储媒介中或传递给其他的应用程序,这就需要使用输出流对象。输出源的指向称作它的目的地,程序通过向输出流中写入数据把数据传送到目的地。
1. File 类
File 类的对象主要用来获取文件本身的一些信息,如文件所在的目录,长度,读写权限等,不涉及对文件的读写操作。
File(String filename); -- filename 是文件名字
File(String directoryPath, String filename); -- directoryPath 是文件的路径
File(File f, String filename); -- f 是指定一个目录的文件
1.1 文件的属性
1. public String getName():获取文件的名字
2. public boolean canRead():判断文件是否可读的
3. public boolean canWrite():判断文件是否可被写入
4. public boolean exists():判断文件是否存在
5. public long length():获取文件的长度,单位是字节
6. public String getAbsolutePath():获取文件的绝对路径
7. public String getParent():获取文件的父目录
8. public boolean isFile():判断文件是否是一个普通文件,而不是目录
9. public boolean isDirectory():判断一个文件是否是一个目录
10. public boolean isHidden():判断文件是否是隐藏文件
11. public long lastModified():获取文件最后修改的时间,时间是从 1979-01-01:00:00:00 到最后修改时间的毫秒数
创建文件 File.createNewFile();
1.2 目录
-
创建目录
File 对象调用方法:public boolean mkdir() 创建一个目录,如果创建成功返回 true,否则返回 false (如果该目录已经存在将返回 false)
public class Test { public static void main(String[] args) { File file1 = new File("D:/bbb"); boolean mkdir = file1.mkdir(); System.out.println(mkdir); } } 运行结果: true
-
列出目录中的文件
如果 File 对象是一个目录,该对象可以调用下面方法列出该目录下的文件和子目录。
- public String[] list():用字符串形式返回目录下的全部文件
- public File[] listFiles():用 File 对象形式返回目录下的全部文件
public class Test { public static void main(String[] args) { File file1 = new File("D:/aaa"); for (String s : file1.list()) { System.out.println("文件夹:" + s); } for (File s : file1.listFiles()) { System.out.println("文件夹:" + s.getName()); } } } 运行结果: 文件夹:a 文件夹:b 文件夹:c 文件夹:a 文件夹:b 文件夹:c
-
文件的创建与删除
在 new File(D:/aa.txt) 创建一个文件对象后,如果括号中指定的 aa.txt 文件,就会调用方法 public boolean createNewFile();,然后再指定文件夹创建文件。
删除文件:文件对象.delete();
public class Test { public static void main(String[] args) { File file1 = new File("D:/bbb.txt"); if (!file1.exists()){ try { file1.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } boolean delete = file1.delete(); System.out.println(delete); } } 运行结果: true
-
运行可执行文件
执行一个可执行文件,需要用到 java.lang 包中的 Runtime 类。
- 声明 Runtime 类的对象 ec
- 调用该类的 getRuntime() 方法
- ec 调用 exce(String command) 方法执行
public class Test { public static void main(String[] args) { Runtime ec = Runtime.getRuntime(); File file = new File("E:/java/IntelliJ IDEA 2018.3.5/bin", "idea.exe"); try { ec.exec(file.getAbsolutePath()); } catch (IOException e) { e.printStackTrace(); } } }
2. 字节流与字符流
2.1 InputStream类 和 OutputStream 类
InputStream 类提供 read 方法以字节为单位顺序地读取源中的数据,只要不关闭流,每次调用 read 方法就顺序地读取源中的其余内容,直到源的末尾或输入流被关闭。
InputStream 类常用方法:
- int read():输入流调用该方法从源中读取单个字节的数据,该方法返回字节值(0~255之间的一个整数),如果未读出字节就返回 -1
- int read(byte b[]):输入流调用该方法从源中试图读取 b.length 个字节到 b 中,返回实际读取的字节数目,如果到达文件的末尾,则返回 -1
- int read(byte b[], int off, int len):输入流调用该方法从源中试图读取 len 个字节到 b 中,并返回实际读取的字节数目。 如果到达文件的末尾,则返回 -1,参数 off 指定从字节数组的某个位置开始存放读取的数据
- void close():输入流调用该方法关闭输入流
- long skip(long numBytes):输入流调用该方法跳过 numBytes 个字节,并返回实际跳过的字节数目。
OutputStream 流以字节为单位顺序地写文件,只要不关闭流,每次调用 write 方法就顺序地向目的地写入内容,直到流被关闭。
OutputStream 类常用方法:
- void write(int n):输出流调用该方法向输出流写入单个字节。
- void write(byte b[]):输出流调用该方法向输出流写入一个字节数组
- void write(byte b[], int off, int len):从给定字节数组中起始与偏移量 off 处取 len 个字节写入到输出流。
- void close():关闭输出流
2.2 Reader类 与 Writer 类
Reader 类提供的 read 方法以字符为单位顺序地读取源中的数据,只要不关闭源,每次调用 read 方法就顺序地读取源中的区域内容,知道源的末尾或输入流被关闭
Reader 常用的方法:
- int read():输入流调用该方法从源中读取一个字符,该方法返回一个整数(0~65536之间的一个整数,Unicode 字符值),如果未读出字符就返回 -1
- int read(char b[]):输入流调用该方法从源中读取 b.length 个字符到字符数组 b 中,返回实际读取的字符数目。如果到达文件的末尾,则返回 -1
- int read(char b[], int off, int len):输入流调用该方法从源中读取 len 个字符并存到字符数组 b 中,返回实际读取的字符数目,如果到达文件的末尾,返回 -1,其中 off 参数指定 read 方法在字符数组 b 中的什么地方存放数据
- long skip(long numBytes):输入流调用该方法跳过 numBytes 个字符,并返回实际跳过的字符数目
Writer 流以字符为单位顺序地写文件,只要不关闭流,每次调用 write 方法就顺序地向目的地写入内容,知道流被关闭
Writer 常用的方法:
- void write(int n):向输入流写入一个字符
- void write(byte b[]):向输入流写入一个字符数组
- void write(byte b[], int off, int length):从给定字符数组中起始于偏移量 off 处取 len 个字符写到输出流
- void close():关闭输出流
3. 文件字节流
3.1 文件字节输入流
使用 FileInputStream 可以以字节为单位去读文件。
构造方法:
- FileInputStream(String name):使用给定的文件名 name 创建一个 FileInputStream 对象,name是输入流的源
- FileInputStream(File file):使用 File 对象创建 FileInputStream 对象,file 为输入流的源
public class Test {
public static void main(String[] args) throws IOException {
int n = -1;
byte[] a = new byte[100];
File file = new File("D:/test.txt");
FileInputStream in = new FileInputStream(file);
while ((n = in.read(a, 0, 100)) != -1) {
String string = new String(a, 0, n);
System.out.println(string);
}
in.close();
}
}
运行结果:
读出指定文件 test.txt 中的内容显示到控制台
3.2 文件字节输出流
使用 FileOutputStream 类来创建指向该文件的文件字节输出流。
构造方法:
- FileOutputStream(String name):使用给定的文件名 name 作为目的地创建一个 FileOutputStream 对象
- FileOutputStream(File file):使用 File 对象作为目的地创建 FileOutputStream 对象
FileOutputStream 流的目的地是文件,所以文件输出流调用 write(byte b[])方法把字节写入到文件。
注意:如果 FileOutputStream 流要写入的文件不存在,该流将首先创建要写的文件,然后再向文件写入内容;如果要写的文件存在,则刷新文件中的内容,然后再顺序地向文件写入内容。
- FileOutputStream(String name,boolean append)
- FileOutputStream(File file,boolean append)
- 如果append取值为true,不会刷新文件,追加再文件内容的末尾,为false,会刷新文件以后的内容
public class Test {
public static void main(String[] args) throws IOException {
byte[] a = "123456".getBytes();
File file = new File("D:/test.txt");
FileOutputStream out = new FileOutputStream(file,true);
out.write(a); //将 a 数组的内容追加的指定的文件后面
out.write(a,1,2); //将数组 a 中从 下表为1 ,2个长度的字节追加到指定文件后面
out.close();
}
}
4. 文件字符流
一个汉字在文件中是占用2个字节,如果使用字节流就会出现乱码的现象。
字符输入流和输出流的 read 和 writer 方法使用字符数组读写数据,即以字符为单位处理数据。
FileReader 和 FileWriter 是 Reader 和 Writer 的子类,构造方法:
- FileReader(String filename)
- FileReader(File filename)
- FileWriter(String filename)
- FileWriter(File filename)
- FileWriter(String filename, boolean append)
- FileWriter(File filename boolean append)
public class Test {
public static void main(String[] args) throws IOException {
String content = "我是学生";
File file = new File("D:/test.txt");
char[] a = content.toCharArray();
FileWriter out = new FileWriter(file, true);
out.write(a, 0, a.length);
out.close();
FileReader in = new FileReader(file);
StringBuffer stringBuffer = new StringBuffer();
char[] b = new char[10];
int n = -1;
while ((n = in.read(b, 0, 10)) != -1) {
String s = new String(b, 0, n);
stringBuffer.append(s);
System.out.println(n);
}
in.close();
System.out.println(stringBuffer);
}
}
注意:对于 Writer 流,writer 方法将数据首先写入到缓冲区,每当缓冲区溢出时,缓冲区的内容被自动写入到目的地,如果关闭流,缓冲区的内容会立刻被写入目的地。
5. 缓冲流
BufferedReader 和 BufferedWriter 的源和目的地必须是字符输入流和字符输出流。
构造方法:
- BufferedReader(Reader in):能够读取文本行, readLine()
- BufferedWriter(Writer out)
public class Test {
public static void main(String[] args) throws IOException {
File file = new File("D:/test.txt");
FileReader in = new FileReader(file);
BufferedReader bufferedReader = new BufferedReader(in);
String str = null;
while ((str = bufferedReader.readLine()) != null) {
System.out.println(str);
}
bufferedReader.close();
in.close();
}
}
public class Test {
public static void main(String[] args) throws IOException {
String str[] = {"我在上课","我在吃饭"};
File file = new File("D:/test.txt");
FileWriter out = new FileWriter(file);
BufferedWriter bufferedWriter = new BufferedWriter(out);
for (String s : str) {
bufferedWriter.write(s);
bufferedWriter.newLine(); //写入一个换行符
}
bufferedWriter.close();
out.close();
}
}
可以把 BufferedReader 和 BufferedWriter 称作上层流,把他们指向的字符流称作底层流。Java 采用缓存技术将上层流和底层流连接。底层字符输出流先将数据读入缓存, BufferedReader 流再从缓存读取数据;BufferedWriter 流将数据写入缓存,底层字符输出流会不断地将缓存中的数据写入到目的地。当 BufferedWriter 流调用 flush() 刷新缓存或调用 close() 方法关闭时,即使缓存没有溢满,底层流也会立刻将缓存的内容写入目的地。
6. 随机流
RandomAccessFile 类创建的流称作随机流,当准备对一个文件进行读写操作时,可以创建一个指向该文件的随机流即可,这样既可以从这个流中读取文件的数据,也可以通过这个流写入数据到文件。
构造方法:
- RandomAccessFile(String name, String mode):name 用来确定一个文件名,给出创建的流的源,目的地。mode 为 r (只读)或 rw (可读写),决定创建的流对文件的访问权力。
- RandomAccessFile(File file, String String mode):同上。
public class Test {
public static void main(String[] args) throws IOException {
File file = new File("D:/test.txt");
int data[] = {1,2,3,4,5,6,7,8,9,10};
RandomAccessFile inAndOut = new RandomAccessFile(file,"rw");
for (int i = 0; i < data.length; i++) {
inAndOut.writeInt(data[i]);
}
for (long i = data.length-1; i >= 0; i--) {
inAndOut.seek(i*4); //定位流的读写位置,参数是确定读写位置距离文件开头的字节个数。
System.out.println(inAndOut.readInt());
System.out.println(inAndOut.getFilePointer()); //获取流的当前读写位置
}
inAndOut.close();
}
}
RandomAccessFile 常用方法
方法 | 描述 |
---|---|
close() | 关闭文件 |
getFilePointer() | 获取当前读写的位置 |
length() | 获取文件的长度 |
read() | 从文件中读取一个字节的数据 |
readBoolean() | 从文件中读取一个布尔值,0 代表 false,其他代表true |
readByte() | 从文件中读取一个字节 |
readChar() | 从文件中读取一个字符,2个字节 |
readDouble() | 从文件中读取一个双精度浮点值(8 个字节) |
readFloat() | 从文件中读取一个单精度浮点值(4 个字节) |
readFully(byte b[]) | 读 b.length 字节放入数组 b,完全填满该数组 |
readInt() | 从文件中读取一个 int 值,4个字节 |
readLine() | 从文件中读取一个文本行 |
readLong() | 从文件中读取一个长型值,8 个字节 |
readShort() | 从文件中读取一个短型值,2 个字节 |
readUnsignedByte() | 从文件中读取一个无符号字节,1 个字节 |
readUnsignedShort() | 从文件中读取一个无符号短型值,2 个字节 |
readUTF() | 从文件中读取一个 UTF 字符串 |
seek(long position) | 定位读写位置 |
setLength(long newlength) | 设置文件的长度 |
skipBytes(int n) | 在文件中跳过给定数量的字节 |
write(byte b[]) | 写 b.length 个字节到文件 |
writeBoolean(boolean v) | 把一个布尔值作为单字节值写入文件 |
writeByte(int v) | 向文件写入一个字节 |
writeBytes(String s) | 向文件写入一个字符串 |
writeChar(char c) | 向文件写入一个字符 |
writeChars(String s) | 向文件写入一个作为字符数据的字符串 |
writeDouble(double v) | 向文件写入一个双精度浮点值 |
writeFloat(float v) | 向文件写入一个单精度浮点值 |
writeInt(int v) | 向文件写入一个 int 值 |
writeLong(long v) | 向文件写入一个长型 int 值 |
writeShort(int v) | 向文件写入一个短型 int 值 |
writeUTF(String s) | 写入一个 UTF 字符串 |
注意:如果 readLine() 方法在读取含有非 ASCII 字符的文件时,含有汉字的文件会出现乱码的情况,需要把 readLine() 读取的字符串用“iso-8859-1”重新编码存放到byte数组中,然后再用当前机器的默认编码将该数组转化为字符串。
public class Test {
public static void main(String[] args) throws IOException {
File file = new File("D:/test.txt");
RandomAccessFile inAndOut = new RandomAccessFile(file, "rw");
long length = inAndOut.length();
long position = 0;
inAndOut.seek(position);
while (position < length) {
String s = inAndOut.readLine();
byte[] a = s.getBytes("iso-8859-1");
s = new String(a,"GB2312"); //GB2312 是机器的编码
position = inAndOut.getFilePointer();
System.out.println(s);
}
}
}
7. 数组流
7.1 字节数组流
ByteArrayInputStream构造方法:
- ByteArrayInputStream(byte[] buf):字节数组流的源是 buf 指定是数组的全部字节单元,
- ByteArrayInputStream(byte[] buf,int offset,int length):同上,从 offset 处按顺序取 length 个字节单元
字节数组输入流方法:
- public int read():顺序地从源中读出一个字节,该方法返回读出的字节值
- public int read(byte[] b, int off, int len):顺序地从源中读出参数 len 指定的字节数,并将读出的字节存到参数 b 指定的数组中,参数 off 指定数组 b 存放读出字节的起始位置,该方法返回实际读出的字节个数。
如果未读出字节 read 方法返回 -1
ByteArrayOutputStream构造方法:
- ByteArrayOutputStream():字节数组输出流指向一个默认大小为 32 字节的缓冲区,如果输出流向缓冲区写入的字节个数大于缓冲区时,缓冲区的容量会自动增加。
- ByteArrayOutputStream(int size):字节数组输出流指向的缓冲区的初始大小由参数 size 指定,如果输出流向缓冲区写入的字节个数大于缓冲区时,缓冲区的容量会自动增加。
字节数组输出流方法:
- public void write(int b):顺序地向缓冲区写入一个字节
- public void write(byte[] b, int off, int len):将参数 b 中指定的 len 个字节顺序地写入缓冲区,参数 off 指定从 b 中写出的字节的起始位置
- public byte[] toByteArray():返回输出流写入到缓冲区的全部字节
7.2 字符数组流
字符数组分别使用字符数组作为流的源和目标,字符数组流:CharArrayReader ,CharArrayWriter 类。
public class Test {
public static void main(String[] args) throws IOException {
//字节数组流
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] a = "我是学生".getBytes();
out.write(a);
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
byte[] b = new byte[out.toByteArray().length];
in.read(b);
System.out.println(new String(b));
//字符数组流
CharArrayWriter outChar = new CharArrayWriter();
char[] content = "我是老师".toCharArray();
outChar.write(content);
CharArrayReader inChar = new CharArrayReader(outChar.toCharArray());
char[] backChar = new char[outChar.toCharArray().length];
inChar.read(backChar);
System.out.println(new String(backChar));
}
}
8. 数据流
DataInputStream 和 DataOutputStream 类创建的对象称为数据的输入流和数据输出流,用该类对象读取一个数值时,不必再关心这个数值应当是多少个字节。
构造方法:
- DataInputStream(InputStream in):创建的数据输入流指向一个由参数 in 指定的底层输入流
- DataOutputStream(OutputStream out):创建的数据输出流指向一个由参数 out 指定的底层输出流
常用方法:
方法 | 描述 |
---|---|
close() | 关闭流 |
readBoolean() | 读一个布尔值 |
readByte() | 读一个字节 |
readChar() | 读一个字符 |
readDouble() | 读一个双精度浮点值 |
readFloat() | 读一个单精度浮点值 |
readInt() | 读一个 int 值 |
readLong() | 读一个长型值 |
readShort() | 读一个短型值 |
readUnsignedByte() | 读一个无符号字节 |
readUnsignedShort() | 读一个无符号短型值 |
readUTF() | 读一个 UTF 字符串 |
skipBytes(int n) | 跳过给定数量的字节 |
writeBoolean(boolean v) | 写入一个布尔值 |
writeBytes(String s) | 写入一个字符串 |
writeChars(String s) | 写入字符串 |
writeDouble(double v) | 写入一个双精度浮点值 |
writeFloat(float v) | 写入一个单精度浮点值 |
writeInt(int v) | 写入一个 int 值 |
writeLong(long v) | 写入一个长型值 |
writeShort(int v) | 写入一个短型值 |
writeUTF(String s) | 写入一个 UTF 字符串 |
public class Test {
public static void main(String[] args){
File file = new File("D:/test.txt");
try {
FileOutputStream out = new FileOutputStream(file);
DataOutputStream outData = new DataOutputStream(out);
outData.writeInt(10);
outData.writeLong(123456);
outData.writeFloat(3.1415926f);
outData.writeBoolean(true);
outData.writeChar(65);
outData.writeChars("I am a stduent");
outData.writeChars("\n");
} catch (IOException e) {
e.printStackTrace();
}
try {
FileInputStream in = new FileInputStream(file);
DataInputStream inData = new DataInputStream(in);
System.out.println(inData.readInt());
System.out.println(inData.readLong());
System.out.println(inData.readFloat());
System.out.println(inData.readBoolean());
System.out.println(inData.readChar());
char c;
while ((c = inData.readChar())!='\n'){
System.out.print(c);
}
}catch (IOException e){
e.printStackTrace();
}
}
}
9. 对象流
ObjectInputStream 和 ObjectOutputStream 类分别是 InputStream 和 OutputStream 类的子类。
ObjectInputStream 和 ObjectOutputStream 类创建的对象称为对象输入流和对象输出流。
构造方法:
- ObjectInputStream(InputStream in):指向是一个输入流对象,当准备从文件中读入一个对象到程序中时,首先用 InputStream 的子类创建一个输入流。
- ObjectOutputStream(OutputStream out):指向是一个输出流对象,当准备将一个对象写入到文件时,首先使用 OutputStream 的子类创建一个输出流。
当使用对象流写出或读入的时候,要保证对象是序列化的。为了保证能把对象写入到文件,并能再把对象正确读回到程序中。
一个类实现 Serializable 接口,这个类创建的对象就是序列化对象。
注意:使用对象流把一个对象写入到文件时不仅要保证该对象是序列化的,而且该对象的成员对象也必须是序列化的。
Serializable 接口中的方法对程序是不可见的,因此实现该接口的类不需要实现额外的方法,当把一个序列化的对象写入到对象输出流时, JVM 就会实现 Serializable 接口中的方法,将一定格式的文本(对象的序列化信息)写入目的地。当 ObjectInputStream 对象流从文件读取对象时,就会从文件中读回对象的序列化信息,并根据对象的序列化信息创建一个对象。
import java.io.Serializable;
public class Student implements Serializable {
String name;
int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class Test {
public static void main(String[] args) {
Student student = new Student();
student.setName("小李");
student.setAge(20);
File file = new File("D:/test.txt");
try {
//写入
FileOutputStream out = new FileOutputStream(file);
ObjectOutputStream objectOut = new ObjectOutputStream(out);
objectOut.writeObject(student);
objectOut.close();
//读
FileInputStream in = new FileInputStream(file);
ObjectInputStream objectIn = new ObjectInputStream(in);
Student student1 = (Student) objectIn.readObject();
System.out.println(student1.getName());
System.out.println(student1.getAge());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
10. 文件锁
经常出现几个程序处理同一个文件的情景,同时更新或读取文件会很混乱,所以 Java 1.4版本后提供了文件锁的功能。
包名:java.nio.channels
方法:
- FileLock
- FileChannel
public class Test {
public static void main(String[] args) {
Student student = new Student();
student.setName("小李");
student.setAge(20);
File file = new File("D:/test.txt");
Scanner scanner = new Scanner(System.in);
try {
//读写属性必须时 rw
RandomAccessFile inAndOut = new RandomAccessFile(file,"rw");
//获得一个连接到底层文件的 FileChannel对象(信道)
FileChannel channel = inAndOut.getChannel();
//信道调用tryLock()或lock()方法获得一个 FileLock(文件锁)对象,这个过程称为文件加锁
//文件锁对象产生后将禁止任何程序对文件进行操作或再进行加锁。
FileLock lock = channel.tryLock(); //加锁
System.out.println("请输入要读取的行数:");
while (scanner.hasNextLine()){
int i = scanner.nextInt();
//对一个文件加锁之后,想要读或写文件必须让 FileLock 对象调用 release() 释放文件锁。
lock.release(); //解锁
for (int j = 0; j < i; j++) {
String s = inAndOut.readLine();
System.out.println(s);
}
lock = channel.tryLock(); //加锁
System.out.println("请输入要读取的行数:");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
学习参考资料:Java 程序设计精编教程(第3版)