一。编码问题
- utf-8编码中,一个中文占3个字节,一个英文占1个字节;gbk编码中,一个中文占2个字节,一个英文占1个字节。
- Java是双字节编码,为utf-16be编码,是说一个字符(无论中文还是英文,都占用2个字节)。因此如果这么问:Java字符串中一个字符可以放一个中文吗?是可以的!
- 如果一直某个字节序列的编码方式,当我们想将它还原成字符串时,应明确指定其编码格式,否则会出现乱码。
- 文本文件就是字节序列,可以是任意编码的字节序列。如果在中文机器上,直接创建文本文件,该文本文件只认识ANSI编码
public static void main(String[] args) throws UnsupportedEncodingException {
// TODO Auto-generated method stub /*
* 在utf-8编码中中文占3个字节,而bgk编码中中文占2个字节
*/
String s = "慕课ABC";
byte[] byte1 = s.getBytes();
for(byte b : byte1) //byte 8bits, int 32 bits. xx vs xxxxxxxx
System.out.print(Integer.toHexString(b & 0xff) + " "); //e6 85 95 e8 af be 41 42 43 (code: utf-8)
System.out.println(); byte[] byte2 = s.getBytes("gbk");
for(byte b : byte2)
System.out.print(Integer.toHexString(b & 0xff) + " "); //c4 bd bf ce 41 42 43 /*
* java是双字节编码,utf-16be编码。意思是Java里的字符串的一个字符占用2个字节
* 面试官会问:Java一个字符中可不可以放汉字呢?如果是gbk编码,是可以的。
*/
System.out.println();
byte[] byte3 = s.getBytes("utf-16be");
for(byte b : byte3)
System.out.print(Integer.toHexString(b & 0xff) + " "); //61 55(慕) 8b fe(课) 0 41(A) 0 42(B) 0 43(C) System.out.println();
String s1 = new String(byte3);
System.out.println(s1); //乱码
String s2 = new String(byte3, "utf-16be");
System.out.println(s2); //慕课ABC }
二。File类的使用
- java.io.File类用于表示文件(目录);
- File类值用于表示文件(目录 )的信息(名称、大小等),不能用于文件内容的访问。
- 静态方法:File.seperator可以直接当成分隔符使用,无论在什么系统下都可以使用,避免\\ or /的困扰。
/**
* 递归遍历一个目标及所有子目录下的文件,打印其文件名
* @param dir
*/
public void listDirectory(File dir) {
if(!dir.exists())
throw new IllegalArgumentException("目录" + dir + "不存在");
if(!dir.isDirectory())
throw new IllegalArgumentException(dir + "不是一个目录");
//String[] fileNames = dir.list();
File[] files = dir.listFiles(); //如果要遍历子目录下的内容,就要构造File对象做递归操作
if(files != null && files.length > 0) {
for(File file : files) {
if(file.isDirectory())
listDirectory(file);
else System.out.println(file.getName());
}
} }
三。RandomAccessFile的使用
- Java提供的对文件内容的访问,既可以读文件,也可以写文件。
- 支持随机访问文件,可以访问文件的任意位置。
- Java文件模型:
- 在硬盘上的文件是byte byte byte存储的,是数据的集合。
- 打开文件
- 有两种模式“rw”和“r”
- RandomAccessFile raf = new RandomAccessFile(file, "rw");
- 文件指针,打开文件是指针在开头 pointer = 0;
- 写文件
- raf.write(int) —— 只写一个字节(后8位),同时指针指向下一个位置,准备再次写入
- 读方法
- int b = raf.read() —— 读一个字节
- 文件读写完成后一定要关闭
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
File demo = new File("demo");
if(!demo.exists())
demo.mkdir();
File file = new File(demo, "raf.dat");
if(!file.exists())
file.createNewFile(); RandomAccessFile raf = new RandomAccessFile(file, "rw");
//指针的位置,输出为0,随机读取文件好处:文件下载时,文件很大,分成程序同时下载,灭个下载
//然后在拼接在一起,迅雷每个线程下载文件的一部分,需要知道拼接的位置在哪里,所以需要随机读取
System.out.println(raf.getFilePointer()); raf.write('A'); //只写了一个字节(后8位)
System.out.println(raf.getFilePointer()); int i = 0x7fffffff;
//用write方法,每次只能写一个字节,所以得写4次
raf.write(i >>> 24);
raf.write(i >>> 16);
raf.write(i >>> 8);
raf.write(i);
System.out.println(raf.getFilePointer()); //可以直接写一个int
raf.writeInt(i);
String s = "中";
byte[] utf = s.getBytes("gbk");
raf.write(utf);
System.out.println(raf.getFilePointer()); //读文件,必须把指针移到头部
raf.seek(0);
byte[] buf = new byte[(int)raf.length()];
raf.read(buf);
System.out.println(Arrays.toString(buf));
System.out.println(new String(buf, "utf-8"));
for(byte b : buf) {
System.out.print(Integer.toHexString(b & 0xff) + " ");
} }
四。字节流的使用
- IO流(输入流、输出流; 字节流、字符流)
- InputStream
- 抽象了应用程序读取数据的方式;
- int b = in.read(); //读取一个字节,无符号填充到int的低八位。-1是EOF
- in.read(byte[] buf) //读取数据填充到字符数组buf中
- OutputStream
- 抽象了应用程序写出数据的方式;
- out.write(int b) //只写出一个byte到流,b的低八位
- out.write(byte[] buf) //将buf字节数组都写入到流
- EOF = End 读到-1就读到结尾了
- FileInputStream —— 具体实现了在文件上读取数据
byte类型为8位,int类型为32位,为了避免数据转换错误,通过&0xff 将高位24位清零!
- 批量读取与单独读取有什么区别?用批量读取会节省很多时间。
//批量读取,适合大文件
public static void printHexByByteArray(String fileName) throws IOException {
FileInputStream in = new FileInputStream(fileName);
byte[] buf = new byte[20 * 1024];
int bytes = 0;
int j = 1;
while((bytes = in.read(buf, 0, buf.length)) != -1) {
for(int i=0; i<bytes; i++) {
System.out.print(Integer.toHexString(buf[i] & 0xff) + " ");
if(j++ % 10 == 0)
System.out.println();
}
}
} //单字节读取
/**
* 读取指定文件内容,按照16进制输出到控制台
* 每输出10个byte就换行
* @param fileName
* @throws IOException
*/
public static void printHex(String fileName) throws IOException {
//把文件作为字节流进行读操作
FileInputStream in = new FileInputStream(fileName);
int b;
int i = 0;
while((b = in.read()) != -1) {
if(b <= 0xf)
System.out.print("0");
System.out.print(Integer.toHexString(b) + " ");
if(i++ % 10 == 0)
System.out.println();
}
in.close();
}
- DataInputDtream 和 DataOutputStream
- 对流功能的扩展,可以更加方便的读取int,long,字符等类型数据,如writeInt()/writeDouble()/writeUTF()
- BufferedInoutStream 和 BufferedOutputStream
- 这两个类为IO提供了带缓冲区的操作,一般打开文件进行写入或读取操作时,都会加上缓冲,这种流模式提高了IO的性能
- 从应用程序中把输入放入文件,相当于将一桶水倒入另一个桶中:
- FileOutputStream的write()方法相当于一滴一滴地把水转移过去;
- DataOutputStream的writexxx()方法相当于用一个非常小的容器转移;
- BufferedOutputStream的write()方法相当于用一个更大的容器做缓存,提高性能。
/**
* 通过缓冲区的方法拷贝文件
* @param srcFile
* @param dstFile
* @throws IOException
*/
public static void copyFileByBuffer(File srcFile, File dstFile) throws IOException {
if(!srcFile.exists())
throw new IllegalArgumentException("File: " + srcFile + " not exist.");
if(!srcFile.isFile())
throw new IllegalArgumentException(srcFile + "not a file.");
BufferedInputStream bis = new BufferedInputStream(
new FileInputStream(srcFile));
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream(dstFile));
int c;
while((c = bis.read()) != -1) {
bos.write(c);
bos.flush();
}
bis.close();
bos.close();
}
五。字符流的使用
- 文本和文本文件
- Java中的文本(char)是16位无符号整数,是字符的Unicode编码(双字节编码);
- 文件是byte byte byte的数据序列;
- 文本文件是文本(char)序列按照某种编码方案(utf-8, utf-16be,gbk)序列化为byte的存储。
- 字符流(Reader,Writer)—— 操作的是文本文件
- 字符的处理,一次处理一个字符;
- 字符的底层仍然是基本的字节序列。
- 字符流的基本实现:
- InputStreamReader 完成byte流解析为char流,按照编码解析
- OutputStreamReader 提供char流解析为byte流,按照编码处理
- 文件读写流:
- FileReader
- FileWriter
- 字符流的过滤
- BufferedReader —— 可以readLine,一次读一行
- BufferedWriter/Printer —— 写一行
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(
new InputStreamReader(
new FileInputStream("demo/out.data")));
BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream("demo/out1.data")));
PrintWriter pw = new PrintWriter("demo/out2.data");
String line;
while((line = br.readLine()) != null) {
System.out.println(line);
pw.println(line);
pw.flush();
// bw.write(line);
// bw.newLine();
// bw.flush();
}
pw.close();
bw.close();
br.close(); }
六。序列化与反序列化
- 对象的序列化和反序列化
- 对象序列化是指将Object对象转化成byte序列,反之叫做对象的反序列化;
- 序列化流(ObjectOutputStream)是过滤流——writeObject();
- 反序列化流(ObjectInputStream)—— readObject();
- 序列化接口(Serializable)
- 对象必须实现序列化接口才能实现序列化,否则将出现异常,这个接口没有任何方法,只是一个标准。
- 对象的序列化和反序列化将那些实现了Serialization接口的对象转换成一个字节序列,并能够在以后将这个字节完全恢复为原来的对象;
- 这样做的一个好处是:能够自动弥补不同操作系统之间的差异。可以在运行Windows操作系统的计算机上创建一个对象,将其序列化,通过网络将它发送给一台运行Unix系统的计算机,然后在那里能够准确的重新组装,而不必担心数据在不同机器上的表示会不同,也不必关心字节的顺序或者其他任何细节;
- 将序列化的概念加入java中主要是为了支持两种特性:
- 一是java的远程方法调用(Remote Method Invocation,RMI),它使存活在其他计算机上的对象使用起来就像存活于本机上一样,当向远程对象大宋消息时,需要通过对象序列化来传输参数和返回值;
- 二是对于java Bean来说,序列化也是必须的。使用一个Bean时,一般情况是在设计阶段对他的状态信息进行配置。这种状态信息必须保存下来,并在程序启东市进行后期恢复。
public static void main(String[] args) throws IOException, IOException {
String file = "demo/obj.txt";
//1. 对象序列化
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream(file));
StudentDemo st = new StudentDemo("aaa", "12", 15);
oos.writeObject(st);
oos.flush();
oos.close(); //2.反序列化
ObjectInputStream pis = new ObjectInputStream(
new FileInputStream(file));
try {
StudentDemo stu = (StudentDemo) pis.readObject();
System.out.println(stu);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
pis.close();
}
-
transient 修饰后,不会进行虚拟机默认的序列化;也可以自己完成这个元素的序列化
- 为什么要使用transient关键字呢?
- 分析ArrayList的序列化与反序列化:实质是一个数组,但是这个数组并不一定放满了,因此我们不需要讲后面没有使用的地方进行序列化,可以根据自己的需要定制序列化,只是序列化数组中的有效元素,提高性能。
- 序列化中子父类构造函数调用问题
- 一个类实现了序列化接口,其子类都可以进行序列化
- 对子类对象进行反序列化操作时,如果其父类没有实现序列化接口,那么其父类的构造函数会被调用;反之不会。