JavaSE笔记(五)文件传输基础IO流

字数 908阅读 59
文件的编码
gbk编码:中文占用2个字节,英文占用1个字节

utf-8编码:中文占用3个字节,英文占用1个字节

java是双字节编码 utf-16be,中文占用2个字节,英文占用2个字节

当你的字节序列是某种编码时,若想把该字节序列变成字符串,也必须用这种编码方式,不能直接使用String str = new 
String(byteStr);否则系统会使用默认的编码,从而出现乱码。所以要这样写String str = new String(byteStr,"UTF-8");在第二
个参数中写入对应的编码方式,这样就不会出现乱码。

文本文件就是字节序列,可以是任意编码的字节序列,如果我们在中文机器上直接创建文本文件,则该文本文件只认识
ansi编码
File类
Java.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();获取文件格式名。

File的过滤,遍历等操作

列出指定目录下的(包括子目录)的所有文件

File file = new File();
exists() 方法用于判断文件或目录是否存在
isDirectory()方法判断File类的对象是否是目录
throw new IllegalArgumentException();抛出异常

String[] filenames = file.list()  //返回的是字符串数组,列出当前目录下的所有子目录和文件,不包含子目录下的内容
//递归
for(String string : filenames){//将filenames数组中的值,循环赋值给string,直到filenames为空时,结束循环
    System.out.println(string);
}

//如果要遍历目录下的内容就需要构造成File对象做递归操作,File提供了直接返回File对象的API
File[] files = file.listFiles(); //返回的是直接子目录(文件)的抽象

//递归,遍历出该目录下所有文件信息,包括子目录下的文件
for(File f :files){
    if(f.isDirectory){
        //递归遍历该目录下的子目录的信息
        listDirectory(file);
    }
    else{
    System.out.pritln(file);
    }
}
RandomAccessFile
RandomAccessFile java提供的对文件内容的访问,既可以读文件,也可以写文件。支持随机访问文件,可以访问文件的任意位置。

java文件模型
  在硬盘上的文件时 byte byte byte存储的,是数据的集合
打开文件
两种模式:“rw”(读写),“r”(只读)

RandomAccessFile raf = new RandomAccessFile(File,"rw")
文件指针,打开文件时指针在开头 pointer = 0;

写方法:
raf.write(int)--->只写一个字节(后8位),同时指针指向下一个位置,准备再次写入

读方法:
int b = raf.read()--->读一个字节

文件读写完成以后一定要关闭
raf.close()
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文件输入
单字节输入即不用数组。
/**
* 批量读取,对大文件而言效率高,也是我们最常用的读文件的方式
* @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();
}
}
/**
     * 文件拷贝,字节批量读取
     * @param srcFile
     * @param destFile
     * @throws IOException
     * 
     * JAVA的throw和throws區別:
     * http://zhidao.baidu.com/link?url=cOoyefusSFJRvXFuNK3vAVS_mGcE3jgWSy8CiwZk5y-N8Fa-m_cwRrNVEneXKkwMOTYHz8MIIS13gAz91Y4vZ_
     */
    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();
        
    }

DataOutputStream/DataInputStream:对“流”功能的扩展,可以更加方便的读取int、long、字符等类型数据
DataOutputStream类
  writeInt()/writeDouble()/writeUTF()
实例化:DataOutputStream dos = new DataOutputStream(new FileOutputStream("demo/dos,dat"));
//采用utf-8编码写出  dos.writeUTF("先森");
//采用utf-16be编码写出  dos.writeChars("先森");

DataInputStream类:
    readInt()/readLong()/readDouble()/readUTF()
DataInoutStream dis = new DataInputStream(new FileInputStream("demo/dos.dat"));
int i = dis.readInt();
System.out.println(i);

BufferedInputStream&BufferedOutputStream
提供了带缓存区的操作,一般打开文件进行写入或读取操作时,都会加上缓冲,这种流模式提高了IO的性能
从应用程序中把输入放入文件,相当于将一罐水倒入到另外一个罐中:
FileOutputStream-->write()方法相当于一滴一滴地把水“转移”过去
DataOutputStream-->writeXxx()方法会方便一些,相当于一瓢一瓢把水“转移”
BuffereOutputStream-->write()方法更方便,相当于一瓢一瓢先放入 桶中,在从桶中倒入到另外一个罐中

字符流
InputStreamReader和OutputStreamWriter的使用
public static void main(String[] args) throws IOException {
        // 读出文件内容 使用默认编码方式,其他编码方式可加在文件路径后面加上用双引号括起来
        InputStreamReader isr = new InputStreamReader(new FileInputStream(
                "F:\\IO流\\IsrAndOsw\\isr.txt"),"gbk");
        // 写入文件内容
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(
                "F:\\IO流\\IsrAndOsw\\osw.txt"));

        //覆盖写入
        osw.write("你好");
        //不覆盖写入,加入后面
        osw.append("世界");

        // 一次读取一个字符
        /*int c;
        while ((c = isr.read()) != -1) {
            System.out.print((char) c);
        }*/

        char[] buffer = new char[1024];
        // 批量读取,放入buffer这个字符数组
        while ((isr.read(buffer)) != -1) {
            osw.write(buffer);
            osw.flush();
        }
        isr.close();
        osw.close();
    }

FileReader/FileWriter:可以直接写文件名的路径。与InputStreamReader相比坏处:无法指定读取和写出的编码,容易出现乱码。只能读取与项目编码方式相同的文件
FileReader fr = new FileReader("e:\javaio\imooc.txt"); //输入流
FileWriter fw = new FileWriter("e:\javaio\imooc2.txt");//输出流
字符流:字符流分为输出流(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");

public static void main(String[] args) throws IOException{
         //对文件进行读写操作 
        BufferedReader br = new BufferedReader(
                new InputStreamReader(
                        new FileInputStream("e:\\javaio\\imooc.txt")));
        
        PrintWriter pw = new PrintWriter("e:\\javaio\\imooc4.txt");
        
        String line ;
        while((line = br.readLine())!=null){
            System.out.println(line);//一次读一行,并不能识别换行
            
            pw.println(line);
            pw.flush();
        }
        br.close();
        
        pw.close();
    }
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里,对数组的有效对象进行序列化
序列化:

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的反序列化操作

序列化过程中子父类构造函数问题
一、父类实现了serializable接口,子类继承就可序列化。
1、子类在反序列化时,父类实现了序列化接口,则不会递归调用其构造函数。
二、父类未实现serializable接口,子类自行实现可序列化
2、子类在反序列化时,父类没有实现序列化接口,则会递归调用其构造函数。

*** 结论:【反序列化时】,向上递归调用构造函数会从【可序列化的一级父类结束】。即谁实现了可序列化(包括继承实现的),谁的构造函数就不会调用。

推荐阅读更多精彩内容