编码问题
String s = "Java 教程";
byte[] byte1 = s.getBytes(); //使用默认编码。
byte[] byte2 = s.getBytes("utf-8"); //显示指定编码格式为 utf-8
//把字节(转换成int)以16进制方式显示
// GBK编码 中文占2个字节,英文占1个字节
// UTF-8 编码,中文占用3个字节,英文占用1个字节
// Java 是双字节编码 UTF-16be
for( byte b : byte1) {
System.out.print(Integer.toHexString(b & 0xff) + " ");
}
当你的字节序列是某种编码时,在把字节序列变成字符串时,也需要使用该种编码,否则会乱码。
String str = new String(b,"utf-16be");
文本文件就是字节序列,可以是任意编码的字节序列。
如果在中文机器上直接创建的文本文件,那么文本文件只认识ansi编码。
复制的还是按照原来的编码格式
File 类常用API
Java中, java.io.FIle
类用于表示文件(目录),只用于表示文件(目录)的信息(名称、大小),不能用于文件访问。
File file = new File("E:\\java\\javaTest"); // 一个目录
File file2 = new File("E://java//test1.java"); // 一个文件
File file3 = new File("E://java","test2.java") //一个文件
if(!file.exists()) {
file.mkdir(); //创建一个目录
file.createNewFile(); //创建一个文件
} else {
file.delete();
}
if(file.isDirectory()) { }
if(file2.isFile()) { }
//常用的File对象API
file.getAbsolutePath();
file.getName();
file.getParent();
File常用操作:遍历,过滤
//列出指定目录下的目录和文件
String[] filenames = dir.list(); //列出当前目录下的子目录和文件,返回字符串数组。不包含子目录下的内容
// 需要遍历目录下的内容时,就要构造File对象做递归操作。
//File提供了直接返回对象的方法。
File[] files = dir.listFiles(); //返回直接子目录(文件)的对象
if(files != null && files.length > 0) {
for (file : files) {
if(file.isDirectory()) {
//递归
}
else {
//直接返回
}
}
}
RandomAccessFile的使用
RandomAcceseeFile 是Java 提供的对文件内容的访问类。
既可以读取文件,也可以写文件。并且可以随机访问文件,可以访问文件的任意位置
java 文件模型
- 在硬盘上的文件是按 字节 存储的,是数据的集合;
打开文件
- 两种模式: “rw”“r”。
- RandomAccessFile raf = new RandomAccessFile(file, “rw”);
- 文件指针: 打开文件时指针在开头 pointer = 0;
写方法
- raf.write(int) ; //只写一个字节(后8位),同时指针指向下一个字节的位置,准备再次写入
读方法
- int b = raf.read(); //只读一个字节
读写文件后一定要关闭文件,否则可能出现不可预测的错误。
File file = new File("001.dat") {
if(!file.exist()) {
file.createNewFile();
}
RandomAccessFile raf = new RandomAccessFile(file,"rw");
int i = 0x7fffffff;
//用write方法没戏只写一个字节。int4个字节,需要写4次
raf.write(i >>> 24); 高8位
raf.write(i >>> 16);
raf.write(i >>> 8);
//或是用 writeInt 直接写一个int
raf.writeInt(i);
//write 可以直接写一个字节数组
String s = "中";
byte[] gbk = s.getBytes("gbk");
raf.write(gbk); //写入一个字节数组
//读文件,必须把指针移到头部
raf.seek(0);
//一次性读取文件中的所有内容到字节数组中
byte[] buf = new Byte[(int)raf.length()];
raf.read(buf);
IO流(输入流,输出流)
字节流的使用
- InputStream、OutputStream
InputStream 抽象了应用程序读取数据的方式
OutputStream 抽象了应用程序写出数据的方式 - EOF == end of file 读到-1表示读到文件结尾
输入流基本方法:(注意,键盘是输入流。 从键盘读入数据写到文件中)
int b = in.read(); // 读取一个字节无符号填充int低8位。 -1 是 EOF
- `int read(byte[] buf); //读取数据填充到字节数组buf
int read(byte[] buf, int start, int size); //读取数据到字节数组buf,从buf 的start位置开始存放size长度的数据
输出流基本方法
out.write(int b); // 写出一个byte到流中,b的低8位
out.write(byte[] buf); //将buf字节数组写入到流
out.write(byte[] buf, int start, int size); //字节数组buf从start位置开始写size长度的字节到流
FileInputStream 继承InputStream,具体实现了文件上读取数据。
FileInputStream in = new FileInputStream("file");
int b = in.read();
Byte[] buf = new byte[20*1024];
int bytes = in.read(buf,0,buf.length);//从in中批量读取字节,放入到buf这个字节数组中,从第0个位置开始放,最多放buf.length个字节。返回的是读到的字节的个数
FileOutputStream 继承了OutputStream,具体实现了向文件中写出byte数据的方法
//如果文件不存在,直接创建。如果存在,删除后创建
FileOutputStream out = new FileOutputStream(file);
//true 表示 append,在文件后面追加.
//如果文件不存在,直接创建。如果文件存在,直接在文件末尾写入
FileOutputStream out = new FileOutputStream(file,true);
DataInputStream 和 DateOutputStream 数据输入输出流
是对“流”功能的扩展,可以更加方便的读取int,long,字符等类型数据writeInt()/ writeDouble()/ writeChar()
//有OutputStream嵌套生成
FileOutputStream fout = new FileOutputStream("E:\\001.txt");
DataOutputStream dos = new DataOutputStream(fout);
dos.writeInt(10);
dos.writeUTF("中国");// 采用utf-8编码写出,每个汉字3个字节
dos.writeChars("中国"); //采用utf-16be 编码,每个汉字2个字节
BufferedInputStream & BufferedOutputStream 带缓冲区的字节流操作。
一般打开文件进行写入或读取操作时,都会加上缓冲,这种流模式提高了IO的性能。
FileOutoutStream ---> write()方法:类似一滴一滴的把水“转移”过去
DataOutputStream ---> writeXxx方法:较方便,类似一瓢一瓢的把水‘转移’过去
BufferedOutputStream ---> write() 方法: 更方便,类似把水先一瓢一瓢放到桶中,在从桶中导入水缸中。
FileOutoutStream ---> write(buf,start,size)方法: 速度最快
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFile));
int c;
while((c = bis.read()) != -1) {
bos.write(c);
bos.flush(); //一定要刷新缓冲区,将数据写入文件中
}
bis.close();
bos.close();
字符流的使用
认识文本和文本文件
- 文本: Java 文本(char)是16位无符号整数,是字符的unicode编码(双字节编码)
- 文件: 是按照byte存储的数据序列
- 文本文件: 是文本(char)序列按照某种编码方案(utf-8,utf-16be, gbk) 序列化为byte的存储结构
字符流输入输出流(Reader, Writer) 操作文本文件
字符的处理: 一次处理一个字符
字符的底层仍然是基本的字节序列
字符流的基本实现
InputStreamReader
: 完成byte流解析为char流,按照编码解析OuputStreamWriter
: 提供char流发到byte流,按照编码解析
FileInputStream in = new FileInputStram("001.txt");
InputStreamReader isr = new InputStreamReader(in,"gbk"); //默认按照gbk编码
int c = isr.read();
char[] buffer = new char[8 * 1024];
c = isr.read(buffer, 0, buffer.length); //批量读取字符,放入buffer字符数组,从0位置开始放置,最多放置length个,返回读取到的字符个数
String s = new String(buffer,0,c); //把字符存储到字符串中
另外一种实现: FileReader&FileWriter
注意: 没有编码参数,
需要设置编码时,就用InputStreamReader&OutputStreamWriter
FileReader fr = new FileReader("001.txt");
FileWriter fw = new FileWriter("002.txt");
//FileWriter fw = new FileWriter("002.txt","true");//文件后面追加
while((int c = fr.read(buffer,0,buffer,length) != -1) {
fw.write(buffer,0,c);
fw.flush();
}
fr.close();
fw.close();
字符流的过滤器 BufferedReader
BufferedReader 除了基本的读取外,可以一次读取一行
BufferedWriter/ PrintWriter 可以一次写一行
BufferedReader br = new BufferedReader(
new InputStreamReader(
new FileInputStream("001.txt"[,"utf-8"])));
//或是用FileReader()
//BufferedReader br = new BufferedWriter(FileReader("001.txt"));
BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(
new FileInputStream("002.txt"[,"utf-8"]));
String line;
//一次读取一行,不能识别换行,返回是字符串
while(line = br.readLine() != null) {
bw.write(line);
//单独写出换行操作
bw.newLine(); //换行操作
bw.flush();
}
br.close();
bw.close();
// 可以直接用PrintWriter 代替 BufferedWriter
// PrintWriter pw = new PrintWriter("003.txt");
// PrintWriter pw1 = new PrintWriter(outputStream, boolean autoFlush); //自动刷新
// pw.print(line); //输出一行,没有换行
// pw.println(line); // 输出一行,有换行
对象的序列化和反序列化
对象的序列化&反序列化
- 对象的序列化,就是将Object 转换成byte序列
对象的反序列化,就是将byte序列转换成Object
序列化流:ObjectOutputStream, 是过滤流 -> writeObject 方法
- 反序列化流:ObjectInputStream, -> readObject方法
序列化接口 (Serializable)
对象必须实现序列化接口,才能进行序列化,否则会出现异常;
序列化接口没有任何实现方法,只是一个标准。
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("001.txt"));
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("002.txt"));
Student stu = new Student("1001","张三"); // Student 类要实现Serializable 接口,否则出现异常
oos.writeObject(stu);
Student stu2 = (Student)ois.readObject();
oos.flush();
oos.close();
ois.close();
关键字: transient
transient 修饰的元素不会进行JVM默认的序列化
但是可以自己定义给元素的序列化。
例如在ArrayLis中,底层的数组是不一定会被填满的。
所以ArrayList 自己定义数组元素的序列化和反序列化,这样可以舍去空元素,提高性能。
序列化中子类和父类构造函数的调用
父类实现了序列化接口,其子类都能进行序列化。
定义的3个foo类
序列化时递归调用父类构造函数
反序列化操作1
反序列化操作1 结果: 只打印出了对象,没有打印出构造函数调用结果
另外3个类
序列化时会打印3个构造函数。
反序列化时会打印父类的构造函数:
反序列化操作 bar 类
说明 对子类对象进行反序列化操作时,如果其父类没有实现序列化接口,那么其父类的构造函数会被调用。