1、文件编码
我们经常使用的txt文本文件本质上将文本char按照GBK、utf-8等多种编码方式序列化为byte字节存储起来的,当我们要读取某个txt文件的内容将其转换为字符串时,也需要使用该字节序列所使用的编码方式进行解码,否则会出现乱码。例如我们在中文Windows系统下创建一个txt文件,默认采用ANSI(GBK)编码方式,当我们在编译器中以utf-8的方式打开该文件就会出现中文乱码。这是由于GBK对于中文一个汉字的编码是两个字节,而utf8一个汉字是三个字节。两者对于英文的编码一样,所以英文不会乱码。 java是用utf-16be编码方式,中文、数字和英文都是两个字节。
String s="中文ABC";
// GBK输出:d6 d0 ce c4 41 42 43,前四个代表两个汉字,后面三个为英文
byte[] bytes2=s.getBytes("gbk");
for (byte b:bytes2){ // 循环以十六进制打印每个字节
System.out.print(Integer.toHexString(b & 0xff)+' ');
}
System.out.println();
// utf-8输出:e4 b8 ad e6 96 87 41 42 43,前六个字节为两个汉字,后面三个英文
byte[] bytes3=s.getBytes("utf8");
for (byte b:bytes3){
System.out.print(Integer.toHexString(b & 0xff)+' ');
}
getBytes()方法默认采用项目的默认编码方式,可以对IDEA设置项目的默认编码
2、文件目录
Java通过File类来实现对文件的操作,包括文件的初始化、文件的创建删除、获取文件信息等操作如下:
// 按文件相对路径初始化一个文件目录对象
File dir=new File("src/com/file/output");
if (!dir.exists()) // 如果目录不存在
dir.mkdir(); // 则创建文件目录
/*else
dir.delete(); // 删除文件
*/
File f1=new File(dir,"1.txt"); // 在目录dir下创建文件
if (!f1.isFile()) { // 如果f1不是一个文件
try {
f1.createNewFile(); // 则创建一个具体文件
} catch (IOException e) {
e.printStackTrace();
}
}
String[] fileNames=dir.list(); // 获取目录下的子文件名字符串
for(String file:fileNames)
System.out.println(file);
System.out.println(f1); // 调用File的toString方法,输出文件绝对路径
System.out.println(f1.getName()); // 输出文件名
System.out.println(f1.getParent()); // 输出文件根目录路径
File parent=f1.getParentFile(); // 获取根目录对象
通过递归遍历一个文件目录下所有文件:
public static void traverseFile(File file){
if (file.isDirectory()){ // 如果文件是一个目录
File[] files=file.listFiles(); // 则获取目录下的所有子文件,进行递归调用
for (File f:files)
traverseFile(f);
}else // 否则打印文件,输出文件路径
System.out.println(file);
}
3、读写文件
Java通过类RandomAccessFile打开文件有两种模式:“rw”(读写)、“r”(只读)。打开文件后会有一个文件指针位于文件开头pointer=0。
写方法write()一次写入一个字节8位数据,并将指针后移一个字节,pointer+1。Java中一个字符占用2个字节,因此调用write()只写入低8位,但是由于字符'A'的高8位为0,因此只写入低8位不会出错。但若要写入一个4字节的int,则需要分四次写入,或者可以调用函数writeInt()一次写入一个Int。类似地writeBytes()写入字符串等。
读方法read()一次读取一个字节。
文件读写结束之后要关闭文件流。
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Arrays;
public class RAccessFile {
public static void main(String[] args) throws IOException {
File file=new File("src/com/file/output/1.txt");
RandomAccessFile raf=new RandomAccessFile(file,"rw");
raf.write('A');
System.out.println(raf.getFilePointer()); // 写入一个字节后指针变为1
int i=0x77777777;
raf.write(i>>>24);
raf.write(i>>>16);
raf.write(i>>>8); // 右移8位,写入i的高8位
raf.write(i); // 自动写入低8位
raf.writeInt(i); // 调用方法写入Int
String s="汉字";
byte[] bytes=s.getBytes();
raf.write(bytes); //写入字节数组
raf.seek(0); // 将指针移动到文件头0的位置
byte[] br=new byte[(int)raf.length()]; // 新建和文件长度相等的字节数组
raf.read(br); // 将文件读到数组中
System.out.println(Arrays.toString(br));
raf.close(); // 关闭文件
}
}
4、输入输出流
Java定义了两个抽象类InputStream和OutputStream用于实现从流中读取/输出数据,子类FileInputStream用于实现从文件流中读取数据。相应地子类FileOutputStream 用于向文件流中输出数据,其构造方法(filename,boolean)第一个参数为文件位置,第二个可选布尔型参数,如果为true,代表以追加的方式写入文件,否则将覆盖或新建一个文件。
输入流的基本方法int b=in.read()默认读取一个字节填充到b的低8位。in.read(byte[] buf , int start, int size)可以从输入流读取字节数组存放到buf中,存放位置从start开始,长度为size,后两个参数可选。
对应地输出流方法out.write(int b)将int的低8位写入输出流。out.write(byte[] buf , int start, int size),字节数组buf从start位置开始写size长度的数据到输出流。
如下为从文件读取输入流数据并显示、输出流向文件写入数据
public static void main(String[] args) throws IOException {
FileInputStream in1=new FileInputStream("src/com/file/output/2.txt");
// 以字节为单位进行读取
int b;
while ((b=in1.read())!=-1){ // 文件的结尾标识符EOF=-1
System.out.print(Integer.toHexString(b)+" ");
}
FileInputStream in=new FileInputStream("src/com/file/output/2.txt");
// 以1024个字节的buf缓冲块为单位进行读取
byte[] buf=new byte[1024];
// 从in读取数据到buf,从位置0开始最多读取buf.length长度的数据,返回实际读取到的字节数
int nums;
while((nums=in.read(buf,0,buf.length))!=-1){//返回-1代表没有读取到字节,到达文件尾
for (int i = 0; i < nums; i++) {
System.out.print(Integer.toHexString(buf[i] & 0xff)+" ");
}
}
FileOutputStream out=new FileOutputStream("src/com/file/output/3.txt",true);
out.write('A'); // 写入字符
byte[] bytes="汉字".getBytes(); // 写入字符缓存数组
out.write(bytes);
in.close();
out.close();
}
为了便于常用数据类型的读写,Java提供了装饰者类用于对输入输出流装饰。其中FilterInputStream为抽象类,其实现子类DataInputStream提供了对Int、Long、Char等多种类型数据的直接写入的API;装饰器的使用类似与管道,例如我们将DataInputStream套接到FileInputStream后面,这样从FileInputStream获取到的文件输入流就会进入DataInputStream,接着我们就可以利用DataInputStream对象完成对多种类型数据的操作。
值得注意的是writeChars()是以Java默认编码utf-16be的方式写入字符串,如果希望以utf-8写入则需使用writeUTF()
FileOutputStream out=new FileOutputStream("src/com/file/output/3.txt",true);
DataOutputStream dos=new DataOutputStream(out); //将FileOutputStream对象作为参数传入DataOutputStream
dos.writeChars("字符串");
dos.writeUTF("utf8字符串");
子类BufferedInputStream提供了通过缓存流来读写文件的方法,与之前的以字节为单位进行读写相比,先将字节先放入缓存中,然后再成批量的读写,速度上有明显的提升。例如通过buffer来拷贝一个文件如下:
FileInputStream in=new FileInputStream("src/com/file/output/1.txt");
BufferedInputStream bis=new BufferedInputStream(in);
FileOutputStream out=new FileOutputStream("src/com/file/output/3.txt");
BufferedOutputStream bos=new BufferedOutputStream(out);
int c;
while ((c=bis.read())!=-1){
bos.write(c); // 将数据写入缓冲区
bos.flush(); // 刷新缓冲区,将缓冲区数据写入文件
}
bos.close();
bis.close(); // 关闭文件流,先打开的后关闭
使用Scanner类的nextXx()可以方便地读入指定类型的数据。需要注意的是在输入完age之后我们会敲一个换行符,不会被nextInt()读取而是留在输入流中,这时如果直接读取name会读到这个换行符,因此需要先用一个nextLine()吸收掉这个换行符。也可以使用next()来代替nextLine(),它不会读取字符流中的回车符和tab,或者空格键。
public static void main(String [] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入你的年龄:");
int age = sc.nextInt();
sc.nextLine(); //吸收输入流中的换行符
System.out.println("请输入你的姓名:");
String name = sc.nextLine();
System.out.println("请输入你的工资:");
float salary = sc.nextFloat();
System.out.println("你的信息如下:");
System.out.println("姓名:"+name+"\n"+"年龄:"+age+"\n"+"工资:"+salary);
}
5、字符流
Java在字节流的基础上封装了字符流类用于实现以字符为单位读取文件,通过InputStreamReader/OutputStreamWriter完成在字节流byte与字符流char之间的相互转换,转换时要注意选择合适的编码方式,否则会出现乱码。如下分别是以单个字符和字符数组为单位进行字符读取
FileInputStream in=new FileInputStream("src/com/file/output/1.txt");
// 新建utf-8解码的字符流对象
InputStreamReader reader=new InputStreamReader(in,"utf8");
int c;
while ((c=reader.read())!=-1){ // 以字符为单位读取和显示
System.out.print((char)c);
}
// 以数组为单位进行读取,将字符读取到缓冲数组中再转换为字符串显示
char[] buffer=new char[1024];
int nums;
while ((nums=reader.read(buffer,0,buffer.length))!=-1){
String s=new String(buffer,0,nums);
System.out.println(s);
}
为了便于文件的读写,Java提供了文件流读写类FileReader和FileWriter用于实现直接读写文件,但该类无法选择编码方式,只能按照项目的默认编码方式进行读写。
FileReader fr=new FileReader("src/com/file/output/1.txt");
// 以追加的方式写入文件
FileWriter fw=new FileWriter("src/com/file/output/2.txt",true);
char[] buffer=new char[1024];
int nums;
while ((nums=fr.read(buffer,0,buffer.length))!=-1){
fw.write(buffer,0,nums);
fw.flush();
}
fw.close();
fr.close();
字符流过滤器可以更方便地对文件流进行操作,例如实现按行读写操作。在字符流InputStreamReader的基础上创建BufferedReader类,而字符流又是在字节流FileInputStream的基础上创建的,因此可以嵌套构造字符流过滤器。注意BufferedWriter不能识别换行符,需要手动通过newLine()换行。也可以使用PrintWriter来实现文件的写入,它可以直接从文件名创建、输入一行以及自动刷新等操作,十分方便。
// 嵌套创建字符流过滤器
BufferedReader br=new BufferedReader(
new InputStreamReader(
new FileInputStream("src/com/file/output/1.txt")
)
);
BufferedWriter bw=new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream("src/com/file/output/2.txt",true)
)
);
PrintWriter pw=new PrintWriter("src/com/file/output/3.txt");
String line;
while ((line=br.readLine())!=null){ // 按行读取文件内容
bw.write(line);
bw.newLine(); // bw写入时手动添加换行
bw.flush();
pw.println(line); // pw可以写入一行
pw.flush();
}
6、对象序列化
Java对象的序列化就是通过序列化流ObjectOutputStream将对象转化为byte序列,反之称为反序列化ObjectInputStream。
对象必须实现了序列化接口Serializable才能进行序列化,否则会报错。如果父类实现了序列化接口,则其子类不需要再实现。在子类对象进行反序列化操作时,如果其父类没有实现接口,父类的构造函数将会被调用。
例如实现一个Student类并将其对象序列化为obj.dat文件以及从该文件中读取对象
public class Student implements Serializable { // Student类实现接口
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +"name='" + name + '\'' + ", age=" + age +'}';
}
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectOutputStream oos=new ObjectOutputStream(
new FileOutputStream("src/com/file/output/obj.dat")
);
Student stu=new Student("小明",23);
oos.writeObject(stu); // 将对象写入序列化流
oos.flush();
oos.close();
ObjectInputStream ois=new ObjectInputStream(
new FileInputStream("src/com/file/output/obj.dat")
);
Student s2=(Student)ois.readObject(); // 从反序列化流中取出对象
System.out.println(s2);
}