一、文件的编码
中文机器上创建的文本文件只能识别ansi编码
如果是由其他地方创建的文本文件 再拷贝出来的则可以识别任意的编码
UTF-8编码 汉字占3个字节 英文占一个
gbk编码 汉字占2个字节 英文占1个
UTF-16be编码是java中的编码 汉字和英文都是占两个字节
.getBytes();将字符串变成byte类型
integer.toHexString();将字节流变成16进制的int类型
用什么编码将字符串变成字节流 就要用同样的编码才能将其变回去
new String(“dd”,”UTF-8”);可以自己选择编码方式 缺省则是默认工程属性中默认的编码
二、File类的使用
ava.IO.File类表示文件或目录,只用于表示文件或目录得信息,不能用于文件的访问。
常用的API:
1.创建File对象:File file=new File(String path);注意:File.seperater();获取系统分隔符,如:”\“.
2.boolean file.exists();是否存在.
3.file.mkdir();或者file.mkdirs();创建目录或多级目录。
4.file.isDirectory()或者file.isFile()判断是否是目录或者是否是文件。
5.file.delete();删除文件或目录。
6.file.createNewFile();创建新文件。
7.file.getName()获取文件名称或目录绝对路径。
8.file.getAbsolutePath()获取绝对路径。
9.file.getParent();获取父级绝对路径。
10.file.getSize();获取文件大小。
11.file.getFormat();获取文件格式名。
public String[] list()返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中的文件和目录。
exists()方法用于判断文件或目录是否存在
file.list() 返回的是 字符串数组 直接子的名称,不包含子目录下的内容
file.listFiles() 返回当前目录下的所有子目录和文件的文件数组名称
如果要遍历子目录下的内容就需要构造成File对象做递归操作
//throws IOException 回避了IO的抛出异常
File[] files = dir.listFiles();//返回的是直接子目录(文件)的抽象
if (file!=null && files.length > 0){
for(File file:files){
if(file.isDrectory()){
listDirectory(file);
}else{
System.out.println(file);
}
}
}
三、RandomAccessFile类的使用
RandomAccessFile java提供的对文件内容的访问,既可以读文件,也可以写文件。支持随机访问文件,可以访问文件的任意位置。
(1)java文件模型 :
在硬盘上的文件是byte byte byte 存储的 是数据的集合
(2)打开文件
有两种模式 rw 读写 r只读
RandomAccessFile raf=new RandomeAccessFile(file,”rw”);
文件指针, pointer=0;
(3)写方法
raf.write(int) —-> 只写一个字节 (后8位) 同时指针指向下一个位置 准备再次写入
(4)读方法
int b=raf.read(); 读一个字节
(5)文件读写完成后一定要关闭 (oracle官方说明)
raf.getFilePointer()获取当前指针位置
raf.length()获取文件长度
raf.seek()把指针指定到某一位置
注意write方法每次只能写入一个字节:
raf.write(‘A’);//此时指针后移
System.out.println(raf.getFilePointer());此时输出为1
这时只写入了一个字节而不是完整的char,只是因为后八位刚好能够表示A
raf.write(‘B’);
若要写入一个整数i则需要写四次
int i=0x7fffffff;
raf.write(i>>>24);//高八位
raf.write(i>>>16);
raf.write(i>>>8);
raf.write(i);//写入最低的八位
System.out.println(raf.getFilePointer());
此时打印输出6
可以直接写入一个int
raf.writeInt(i);
String s=”中”;
byte[] gbk=s.getBytes(“gbk”);
raf.write(gbk);
System.out.println(raf.length(0);
此时打印输出12(中文占俩字节)
读文件,必须把指针移到头部
raf.seek();
//一次性读取:
byte[] buf= new byte[(int)raf.length()];
raf.read(buf);
System.out.println(Arrays.toString(buf));
此时打印输出
[65,66,127,-1,-1,-1,127,-1,-1,-1,-42,-48]
开头的65,66是正确的AB,因为后八位已经能表示AB了
也可按字符串输出
String s1=new String(buf);
System.out.println(s1,”gbk”);
打印输出AB?????
因为“中”的前后都有字节,只有定位到中的两个字节,才能读出他
最后要加上raf.close();
四、字节流
4-1 字节流之文件输入流FileInputStream-1
IO流分为输入流、输出流
还有字节流、字符流
1、字节流:
(1)InputStream:抽象了应用程序读取数据的方式
(2)OutputStream:抽象了应用程序写 出数据的方式
2)EOF = End 读到-1就读到结尾
3)输入流基本方法
int b = in.read();读取一个字节无符号填充到int低八位.-1是EOF
in.read(byte[] buf) 读取数据填充到字节数组buf
in.read(byte[] buf,int start, int size)读取数据到字节数组buf从buf的start位置开始存放size长度分数据
4)输出流基本方法
out.write(int b)写出一个byte到流,b的低8位
out.write(byte[] buf)将buf字节数组都写到流
out.write(byte[] buf, int start,int size) 字节数组buf从start位置开始写size长度的字节到流
1、byte 类型 8 位,int 类型 32 位,为了避免数据转换错误,通过 & 0xff 将高 24 位清零
2、long time = System.currentTimeMillis()
当前时间与协调世界时 1970 年 1 月 1 日午夜之间的时间差(以毫秒为单位测量)
3、is.read() 单字节适合读取 小 文件
is.read(byte[] bytes,int star,int size) 字节数组适合读取 大 文件
读取文件最常用的是批量读取int bytes = fis.read(buf, 0 , buf.length);
FileInputStream文件输入
单字节输入即不用数组。
4-2 字节流之文件输入流FileInputStream-2
/**
* 批量读取,对大文件而言效率高,也是我们最常用的读文件的方式
* @Inparam fileName
* @throws IOException
*/
public static void printHexByByteArray(String fileName)throws IOException{
FileInputStream in = new FileInputStream(fileName);
byte[] buf = new byte[8 * 1024];
/*从in中批量读取字节,放入到buf这个字节数组中,
* 从第0个位置开始放,最多放buf.length个
* 返回的是读到的字节的个数
*/
int bytes = in.read(buf,0,buf.length);//一次性读完,说明字节数组足够大
int j = 1;
for(int i = 0; i < bytes;i++){
System.out.print(Integer.toHexString(buf[i] & 0xff)+” “);
if(j++%10==0){
System.out.println();
}
}
4-3 字节流之文件输出流FileOutputStream (13:24)
FileOutputStream fos = new FileOutputStream(file,true)
文件不存在,则创建,否则在后面追加内容
FileOutputStream fos = new FileOutputStream(file)
文件不存在,则创建,否则,删除后再创建
java中throw和throws的区别
仔细一看就知道了: public Test() throws RepletException { try { System.out.println(“Test this Project!”) } catch (Exception e) { throw new Exception(e.toString()); } }throws是用来声明一个方法可能抛出的所有异常信息throw则是指抛出的一个具体的异常类型搜索。通常在一个方法(类)的声明处通过throws声明方法(类)可能抛出的异常信息,而在方法(类)内部通过throw声明一个具体的异常信息。throws通常不用显示的捕获异常,可由系统自动将所有捕获的异常信息抛给上级方法;throw则需要用户自己捕获相关的异常,而后在对其进行相关包装,最后在将包装后的异常信息抛出
public static void copyFile(File srcFile,File destFile)throws IOException{
if(!srcFile.exists()){
throw new IllegalArgumentException(“文件:”+srcFile+”不存在”);
}
if(!srcFile.isFile()){
throw new IllegalArgumentException(srcFile+”不是文件”);
}
FileInputStream in = new FileInputStream(srcFile);
FileOutputStream out = new FileOutputStream(destFile);
byte[] buf = new byte[8*1024];
int b ;
while((b = in.read(buf,0,buf.length))!=-1){
//講buf數組裡的內容讀入到該輸出流中(out)
out.write(buf,0,b);
out.flush();//最好加上
}
in.close();
out.close();
}
4-4 字节流之数据输入输出流
readInt readLong 方法都是对FileInputStream方法的包装
DataOutputStream/DataInputStream
对“流”功能的扩展,可以更加方便的读取 int,long, 字符等类型数据
DataOutputStream:使用FileOutputStream构造出来,通过包装FileOutput,可以调用FileOutput类的write方法来构造新的更方便的写方法:
new DataOutputStream(new FileOutptStream(file))
wrieteUTF()采用utf-8编码写出字符串
用utf-16be写出字符串,或字符串数组
写完之后一定要关闭流
数据输入输出流:
DataInputStream、DataOutputStream 是对“流”功能的扩展,方便读写
DataOutputStream dos = new DataOutputStream(new FileOutputStream(file));
dos.writeInt(10);
dos.writeLong(10l);字母l
dos.writeDouble(10.5);
//采用utf-8编码写出
dos.writeUTF(“中国”);
//采用utf-16be编码写出
dos.writeChars(“中国”);
4-5 字节缓冲流
BufferedInputStream&BufferedOutputStream
提供了带缓存区的操作,一般打开文件进行写入或读取操作时,都会加上缓冲,这种流模式提高了IO的性能
从应用程序中把输入放入文件,相当于将一罐水倒入到另外一个罐中:
FileOutputStream–>write()方法相当于一滴一滴地把水“转移”过去
DataOutputStream–>writeXxx()方法会方便一些,相当于一瓢一瓢把水“转移”
BuffereOutputStream–>write()方法更方便,相当于一瓢一瓢先放入 桶中,在从桶中倒入到另外一个罐中
批量文件拷贝&利用带缓冲的字节流,实现文件拷贝&单字节,不带缓冲进行文件拷贝
效率:批量文件拷贝最快,其次带缓冲拷贝,最后单字节拷贝
五、字符流
5-1 字节字符转换流
1)编码问题 前已述及 起码有一点要非常清楚的是,一切进入计算机的都会变成字节码
2)认识文本和文本文件
java中的文本(其实就是char)16位的无符号整数,是字符的unicode编码(这是一种双自己编码)
文件是byte byte byte…的数据集合,可以缩句来理解 文件输数据集合。字节流的编码方式和序列化规则不一样就形成了不同的文件:文本文件,音频文件,视频文件等
文本文件是文本(char的编码)序列按照某种编码方案(utf-8,utf-16be,gbk)序列化为byte的数据存储集合
(3)字符流
抽象类 Reader Writer 二者实现了数据的两种相互转换,存储时我们用字节码的形式存储,读入计算机内存处理(包括显示,运算等)是用字符(ABC…)的形式。
字符的处理,一次处理一个字符。其底层仍然是基本的字节序列
字符流的基本实现
InputStreamReader 完成byte流解析为char流,按照编码接卸
OutputStreamWriter 提供char流到byte流,按照编码处理。
为什么上面特别说明了一下文本文件,因为字符流大部分操作的都是文本文件。毕竟文本文编,以编码的方式不容易认读,我们才把字符编码解析为字符。如果Reader一个MP3之类的音频文件,根本就没有什么意义,因为,声音本来就不是用来看的,所以说字符流主要是用于处理文本文件的
4)InputStreamreader 完成byte流解析成char流 按照编码解析
OutputStreamWrite 提供char流到byte流 按照编码处理
FileInputStream in=new FileInputStream(“e:\javaio\imooc.txt”);
InputSreamStreamReader isr=new InputStreamReader(in);//默认项目编码 gbk 可改成 utf-8
int c;
while((c=isr.read())!=-1){
syso -> char(c)
}
//一次读一个字符数组
char[] buffer=new char[8*1024];
int c;
while ((c=isr.read(buffer,0,buffer.length))!=-1){
批量读取放入buffer这个字符数组 从第0个位置开始防止,最多放置buffer.length个
String s=new String(buffer,0,c);
syso -> s;
}
5-2 字符流之文件读写流
.字符流:字符流分为输出流(Reader)和输出流(Writer)。操作的是文本文件。
字符处理,一次处理一个字符
字符处理底层还是基本的字节序列
InputStreamReader:完成byte流解析为char流,按照编码解析
FileInputStream in = new FileInputStream(“e:\javaio\imoocutf8.txt”);
//获取字符输入流
InputStreamReader isr = new InputStreamReader(in,”utf-8”);//默认项目的编码,操作的时候,要写文件本身的编码格式
OutputStreamWriter:提供char流到byte流,按照编码处理
FileOutputStream out = new FileOutputStream(“e:\javaio\imoocutf81.txt”);
//获取字符输出流
OutputStreamWriter osw = new OutputStreamWriter(out,”utf-8”);
FileReader/FileWriter:可以直接写文件名的路径。与InputStreamReader相比坏处:无法指定读取和写出的编码,容易出现乱码。
FileReader fr = new FileReader(“e:\javaio\imooc.txt”); //输入流
FileWriter fw = new FileWriter(“e:\javaio\imooc2.txt”);//输出流
5-3 字符流的过滤器
字符流 之字符流的过滤器 除了具有基本的读写单个字外,更具有加强功能,可以一次性的读写一行。可以参见帮助文档,文档中的介绍更加详细,介绍到字符流的顾虑器,优化了一般的Reader的巨大开销。
Bufferedreader–>readLine()
BufferedWriter/PrintWriter 可以实现一次写一行
FileReader和FileWriter不能增加编码参数,所以当项目和读取文件编码不同时,就会产生乱码。
这种情况下,只能回归InputStreamReader和OutputStreamWriter。
BufferedReader – > readLine -> 读一行 不识别换行符,不会自动换行
BufferedWriter/PrintWriter – > writeLine/println -> 写一行,不会自动换行/自动换行
在文件中换行,可以用newLine();实现
/**其构造需要双层的嵌套
* 看看下面是一个多么恶心的嵌套,FileReader 是对一个 Reader 进行过滤 所以,构造式需要传进来一个Reader,(我们知道Reader是一个抽象类,我们只能使用起实现子类)
* 我们有知道,用的是Reader的实现子类 InputStreamReader,而InputStreamReader它有需要一个InputStream,我们最常用的
* InputStream的实现子类是FileInputStream,因为我们这里也是对文件进行操作
*/
BufferedReader br = new BufferedReader(
new InputStreamReader(
new FileInputStream(“e:\Jworkspace\code.txt”)));
BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(“e:\Jworkspace\code2.txt”)));
String line;
while((line = br.readline())!=null) //一次读一行
{
System.out.println(line); //一次读一行不支持换行, 必须使用println
bw.write(line);
//必须要单独写出黄航
bw.newLine();
bw.flush();
}
br.close();
bw.close
六、对象的序列化和反序列化
6-1 序列化基本操作
IO——对象的序列化和反序列化
一、概念
1、对象序列化,就是将Object转换成byte序列,反之叫对象的反序列化
2、序列化流(ObjectOutputStream),字节的过滤流 —— writeObject()方法
反序列化流(ObjectInputStream)—— readObject()方法
3、序列化接口(Serializable)
对象必须实现序列化接口,才能进行序列化,否则将出现异常。
这个借口,没有任何方法,只是一个【标准】
二、transient关键字
1、transient修饰的元素,不会进行JVM默认的序列化:如int transient age = 10;在序列化和反序列化后,age的值为默认分配的值0
2、可以自己通过重写序列化操作方式,来对transient修饰的元素进行想要的序列化。
*方法:通过从ArrayList中拿到writeObject()和readObject()方法,进行自写完成。
· 先执行s.defaultWriteObject(); 和 s.defaultReadObject()方法
· 再对于无法默认序列化的成员,可以进行.writeObject(obj)和this.obj = s.readObject()完成序列化
3、这样做的目的是提高效率。如ArrayList里,对数组的有效对象进行序列化
、、、被序列化的对象要实现序列化接口:
public class Student implements Serializable{
、、、對象的序列化:
public static void main(String[] args) throws Exception{
//序列化后存到這個文件里
String file = “demo/obj.dat”;
//1.对象的序列化
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream(file));
Student stu = new Student(“10001”, “张三”, 20);
oos.writeObject(stu);
oos.flush();
oos.close();
//反序列化
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream(file));
Student stu = (Student)ois.readObject();
System.out.println(stu);
ois.close()
6-2 transient及ArrayList源码分析
序列化:
transient 关键字:被transient修饰的元素,该元素不会进行jvm默认的序列化,但可以自己完成这个元素的序列化
注意:
(1)在以后的网络编程中,如果有某些元素不需要传输,那就可以用transient修饰,来节省流量;对有效元素序列化,提高性能。
(2)可以使用writeObject自己完成这个元素的序列化。ArrayList就是用了此方法进行了优化操作。ArrayList最核心的容器Object[] elementData使用了transient修饰,但是在writeObject自己实现对elementData数组的序列化。只对数组中有效元素进行序列化。readObject与之类似。
(3)java.io.ObjectOutputStream.defaultWriteObject();
// 把jvm能默认序列化的元素进行序列化操作
java.io.ObjectOutputStream.writeInt(age);// 自己完成序列化
(4)
java.io.ObjectOutputStream.defaultReadObject();// 把jvm能默认反序列化的元素进行反序列化
this.age = java.io.ObjectOutputStream.readInt(); // 自己完成age的反序列化操作
ArrayList源码中序列化和反序列化的方法,可以拿來直接用:
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
s.defaultWriteObject();//把jvm能默认序列化的元素进行序列化操作
s.writeInt(stuage);//自己完成stuage的序列化
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException{
s.defaultReadObject();//把jvm能默认反序列化的元素进行反序列化操作
this.stuage = s.readInt();//自己完成stuage的反序列化操作
}
6-3 序列化中子父类构造函数问题
序列化过程中子父类构造函数问题
一、父类实现了serializable接口,子类继承就可序列化。
1、子类在反序列化时,父类实现了序列化接口,则不会递归调用其构造函数。
二、父类未实现serializable接口,子类自行实现可序列化
2、子类在反序列化时,父类没有实现序列化接口,则会递归调用其构造函数。
* 结论:【反序列化时】,向上递归调用构造函数会从【可序列化的一级父类结束】。即谁实现了可序列化(包括继承实现的),谁的构造函数就不会调用。
总结
当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。
把Java对象转换为字节序列的过程称为对象的序列化。
把字节序列恢复为Java对象的过程称为对象的反序列化。
对象的序列化主要有两种用途:
1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
2) 在网络上传送对象的字节序列。
JDK类库中的序列化API
java.io.ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
java.io.ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
只有实现了Serializable和Externalizable接口的类的对象才能被序列化。Externalizable接口继承自Serializable接口,实现Externalizable接口的类完全由自身来控制序列化的行为,而仅实现Serializable接口的类可以采用默认的序列化方式 。
对象序列化包括如下步骤:
1) 创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流;
2) 通过对象输出流的writeObject()方法写对象。
对象反序列化的步骤如下:
1) 创建一个对象输入流,它可以包装一个其他类型的源输入流,如文件输入流;
2) 通过对象输入流的readObject()方法读取对象。