Fork me on GitHub

第27章 java I/O输入输出流

java I/O输入输出流

1.编码问题


import java.io.UnsupportedEncodingException;

/**
 * java涉及的编码
 */
public class EncodeDemo {
    public static void main(String[] args) throws UnsupportedEncodingException {
        String s = "百度ABC";
        //字符串转化为byte,使用getBytes方法
        //转换字节序列用的是项目默认的编码utf-8
        byte[] byte1 = s.getBytes();
        for (byte b : byte1) {
            //把字节以16进制的方式显示
            //utf-8编码中文占用3个字节,英文占用1个字节
            System.out.print(Integer.toHexString(b & 0xff)+" ");//e7 99 be e5 ba a6 41 42 43
        }

        System.out.println();
        //显式的重新设置编码
        byte[] byte2 = s.getBytes("gbk");
        for (byte b : byte2){
            //gbk编码中文占用2个字节,英文占用1个字节
            System.out.print(Integer.toHexString(b & 0xff)+" ");//b0 d9 b6 c8 41 42 43
        }


        //java是双字节编码:utf-16be
        System.out.println();
        //显式的重新设置编码
        byte[] byte3 = s.getBytes("utf-16be");
        for (byte b : byte3){
            //utf-16be编码中文占用2个字节,英文占用2个字节
            System.out.print(Integer.toHexString(b & 0xff)+" ");//b0 d9 b6 c8 41 42 43
        }

        /**
         * 当你的字节序列是用某种编码的,这个时候想把字节序列变成字符串
         * 也需要用这种编码方式,否则会出现乱码
         */
        String str1 = new String(byte3);
        System.out.println(str1);//v~^� A B C
        //String可以指定编码格式
        String str2 = new String(byte3,"utf-16be");
        System.out.println(str2);//百度ABC

        /**
         * 文本文件 就是字节序列
         * 可以是任意编码的字节序列
         * 如果我们在中文机器上直接创建文本文件,那么该文本文件只认识ansi编码
         */
    }
}

2.File类的使用

java.io.File类用于表示文件(目录)
File类只用于表示文件(目录)的信息(名称,大小等),但不不能用于文件内容的访问

2.1.File类常用API介绍

import java.io.File;
import java.io.IOException;

/**
 * 测试File类的方法
 */
public class FileDemo {
    public static void main(String[] args) throws IOException {
        //了解构造函数的情况
        //创建一个File实例,构造方法
        File  file = new File("/home/cenyu/test");
        File file1 = new File("/home/cenyu/note");
        File file2 = new File("/home/cenyu","note");
        //exists()检查文件是否存在
        System.out.println(file.exists());
        //mkdir()创建指定目录
        if (!file.exists()) {
            file.mkdir();
        }else {
            //delete删除路径目录
            file.delete();
        }


        //文件
        if (!file1.exists()){
            //创建一个文件
            file1.createNewFile();
        }else {
            //也可删除文件
            file1.delete();
        }
        //是否是一个目录
        System.out.println(file.isDirectory());
        //是否是一个文件
        System.out.println(file.isFile());


        //常用的File对象的API
        System.out.println(file);//file.toString()的内容,这里显示的是目录名
        //显示抽象文件路径名
        System.out.println(file.getAbsolutePath());
        //获取文件名字
        System.out.println(file.getName());
        //返回父目录路径
        System.out.println(file.getParent());

    }
}

2.1.File类方法

遍历

import java.io.File;

/**
 * 列出File类的一些常用操作
 * 过滤,遍历等
 */
public class FileUtils {
    /**
     * 列出指定目录下(包括其子目录)的所有文件
     */
    public static void listDirectory(File dir) throws IllegalAccessException {
        if (!dir.exists()){
            throw new IllegalAccessException("目录:"+dir+"不存在");
        }
        if (!dir.isDirectory()){
            throw  new IllegalAccessException(dir+"不是目录");
        }
        //此处只能遍历指定目录下的一层目录
    /*    String[] filnames = dir.list();//返回的是字符串数组,直接是子目录的名称,没有子目录下的内容
        for (String string : filnames){
            System.out.println(string);
        }
    */

    //如果需要遍历目录下的内容,就需要构造成File对象做递归操作,File提供了直接返回File对象的API
        //此处遍历指定目录下的所有层级的文件
        File[] files = dir.listFiles();//返回的是直接子目录(文件)的抽象
        if (files!=null && files.length>0){
            for (File file : files){
                if (file.isDirectory()){
                    //递归
                    listDirectory(file);
                }else {
                    System.out.println(file);
                }
            }
        }

    }

    
    //测试
    public static void main(String[] args) throws IllegalAccessException {
        FileUtils.listDirectory(new File("/home/cenyu/"));
    }
}

3.RandomAccessFile的使用

RandomAccessFile java提供的对文件内容的访问,既可以读文件,也可以写文件

RandomAccessFile支持随机访问文件,可以访问文件的任意位置
java文件的一些知识
1.java文件模型:

在硬盘上的文件是byte byte byte存储的,是数据的集合

2.打开文件:

有两种模式"rw"(读写),“人”(只读),在创建RandomAccessFile实例的时候,需要指定使用什么方法来打开文件
RandomAccessFile raf = new RandomAccessFile(file, "rw")
文件中存在文件指针,在打开文件时指针在开头,pointer=0;在读和写的过程中,这个指针会乡下移动

3.写方法

raf.write(int)--->只写一个字节(后8位),这个字节是最后一个字节,所以如果要写好几个字节的字符,需要分别写进去,可以使用移位的方法,同时指针指向下一个位置,准备再次写入

4.读方法

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

5.文件写完以后一定要关闭

如果不关闭,会产生不可预料的问题

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Arrays;

/**
 * RandomAccessFile类对文件的读写操作
 */
public class RafDemo {

    public static void main(String[] args) throws IOException {
        //创建一个目录,如果目录不存在,就创建这个目录
        File demo = new File("demo");//如果不指定位置,就默认是当前目录下
        if (!demo.exists()){
            demo.mkdir();
        }

        //创在demo下的一个文件,如果文件不存在,就创建这个文件
        File file = new File(demo,"raf.dat");
        if (!file.exists()){
            file.createNewFile();
        }

        //
        RandomAccessFile raf = new RandomAccessFile(file,"rw");
        //指针位置
        System.out.println(raf.getFilePointer());//0

        raf.write('A');//只写了一个字节
        System.out.println(raf.getFilePointer());//1,已经写了一个字节
        raf.write('B');
        System.out.println(raf.getFilePointer());//2,已经写了两个字节

        int i = 0x7fffffff;
        //用write方法每次只能写一个字节,如果把i写进去就得写4次
        raf.write(i>>>24);//高8位
        raf.write(i>>>14);
        raf.write(i>>>8);
        raf.write(i);//低8位
        System.out.println(raf.getFilePointer());//6


        //可以直接使用writInt方法,一次性把找个int写进去
        raf.writeInt(i);//其实底层调用的还是上面的移位方法

        //写入一个字符
        String s = "中";
        byte[] gbk = s.getBytes("gbk");
        raf.write(gbk);
        System.out.println(raf.getFilePointer());//12
        System.out.println(raf.length());//12


        //读文件,必须把指针移到头部
        raf.seek(0);//设置文件指针位置
        //一次性读取,把文件中的内容都读到字节数组中
        byte[] buf = new byte[(int)raf.length()];
        raf.read(buf);//不加buf参数的话,一次只能读取一个
        System.out.println(Arrays.toString(buf));//[65, 66, 127, -1, -1, -1, 127, -1, -1, -1, -42, -48]

        String s1 = new String(buf);
        //此处乱码的原因是我们的s使用的是gdk编码格式,跟其他内容的格式是不一样。
        System.out.println(s1);//AB��������
    }
}

4.字节流的使用

IO流作为内容的传输作用,分为输入流和输出流,输入流就是程序获取了信息,输出流就是程序向外传送了信息
传输方式一般有字节流和字符流
1.字节流
1.有两个抽象的父类,分别是:
InputStream:抽象了应用程序读取(获得)数据的方法
OutputStream:抽象了应用程序写出(传出)数据的方法
2.数据读取到结尾的时候就是EOF=End,也是读到-1.这两个就代表读到了结尾
3.输入流基本方法:
int b = in.read();读取一个字节(byte类型),无符号填充到int低八位,-1是EOF
in.read(byte[] buf)读取数据填充到字节数组buf
in.read(byte[] buf, int start, int size)读取数据到字节数组buf,从buf的start位置开始存放size长度的数据
4.输出流基本方法
out.read(int b)写出一个byte到流,写的是b的低8位,而int是4个字节的
out.read(byte[] buf)将buf字节数组都写入到流
out.read(byte[] buf, int start, int size)字节数组buf从start位置开始写size长度的字节到流
5.FileInputStream--->集成InputStream,具体实现了文件上的读取数据

注意:读取的时候单字节读取不适合读取大文件,大文件可以使用批量读取,效率更高
使用输入流读取数据实例:

import java.io.FileInputStream;

import java.io.IOException;

/**
 * 读取文件内容的两种不同方法
 */
public class IOUtil {
    /**
     * 读取指定文件内容,按照16进制输出到控制台
     * 并且每输出10个byte换行
     * @param fileName
     */
    //单字节读取
    public static void printHex(String fileName) throws IOException {
        //把文件作为字节流操作
        FileInputStream in = new FileInputStream(fileName);
        int b;
        int i =1;
        while ((b=in.read())!=-1){
            if (b<0xf){
                //如果是一位,单位数前面补0
                System.out.print("0");
            }
            System.out.print(Integer.toHexString(b)+"  ");
            if (i++%10==0){
                System.out.println();
            }
        }
        in.close();
    }

    //批量读取
    public static void printHexByByteArray(String fileName) throws IOException {
        FileInputStream in = new FileInputStream(fileName);
        byte[] buf = new byte[20*1024];
        /**
         * 从in中批量读取字节,放入到buf这个字节数组中,
         * 从第0个位置开始放,最多放buf.length个
         * 返回的是多的字节的个数
         */
/*        int bytes = in.read(buf, 0, buf.length);//一次性读完,此要求字节数组足够大
        int j=1;
        for (int i = 0; i < bytes; i++) {
            if (buf[i]<=0xf){
                System.out.print("0");
            }
            System.out.print(Integer.toHexString(buf[i])+"  ");
            if (j++%10==0){
                System.out.println();
            }
        }
        in.close();
*/
//上面的方法需要字节数组足够大,如果文件草果字节数组比较麻烦,所以一般更推荐使用下面的方法
        int bytes = 0;
        int j = 1;

        while ((bytes = in.read(buf, 0, buf.length))!=-1){
            for (int i = 0; i < bytes; i++) {
                if (buf[i]<=0xf){
                System.out.print("0");
                }
                //byte类型8位,int类型32位,为了避免数据转换错误,通过&0xff将高24位清零
                System.out.print(Integer.toHexString(buf[i] & 0xff)+"  ");
                if (j++%10==0){
                    System.out.println();
                }
            }
        }
    }

    public static void main(String[] args) {
        try {
            IOUtil.printHex("/home/cenyu/aa");
        } catch (IOException e) {
            e.printStackTrace();
        }


        try {
            IOUtil.printHexByByteArray("/home/cenyu/aa");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

6.FileOutStream--->继承OutputStream 实现了向文件中写入byte数据的方法

mport java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * 测试FileOutputStream类写入文件
 */
public class FileOutDemo {
    public static void main(String[] args) throws IOException {
        //如果该文件不存在,则直接创建该文件。如果文件存在,要看后面有没有true参数,
        //如果没有true参数,则删除文件后再重新创建,如果有true参数,则在文件后追加内容
        FileOutputStream out = new FileOutputStream("demo/out.dat",true);
        out.write('A');//写出了'A'的低八位
        out.write('B');//写出了'B'的低八位
        int a =10;//write只能写八位,那么写一个int整数,需要写4此,每次8位
        out.write(a>>>24);
        out.write(a>>>16);
        out.write(a>>>8);
        out.write(a);
        //写字符串
        byte[] gdk = "中国".getBytes("gbk");
        out.write(gdk);
        out.close();


        //测试copyFile
        FileOutDemo.copyFile(new File("/home/cenyu/aa"), new File("/home/cenyu/bb"));
    }


    //copy源文件
    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){
            out.write(buf, 0, b);
            out.flush();
        }
        in.close();
        out.close();
    }
}

7.DataOutputStream/DataInputStream类是对“流”功能的扩展,可以更加方便读取int,long,字符等类型数据
DataOutputStream:
writeInt()/writeDouble()/writeUTF()

写入代码示例:

public class DosDemo {
    public static void main(String[] args) throws IOException {
        String file = "demo/dos.dat";
        DataOutputStream dos = new DataOutputStream(new FileOutputStream(file));
        dos.writeInt(10);
        dos.writeInt(-10);
        dos.writeLong(10L);
        dos.writeDouble(10.5);
        //采用utf-8编码写出
        dos.writeUTF("中国");
        //采用utf-16编码写出
        dos.writeChars("中国");
        dos.close();
        IOUtil.printHex(file);
    }
}

读取代码示例:

public class DisDemo {
    public static void main(String[] args) throws IOException {
        String file = "demo/dos.dat";
        DataInputStream dis = new DataInputStream(new FileInputStream(file));
        int i = dis.readInt();
        System.out.println(i);
        i = dis.readInt();
        System.out.println(i);//10
        long l = dis.readLong();
        System.out.println(l);//-10
        double d = dis.readDouble();//10
        System.out.println(d);//10.5
        String  s = dis.readUTF();
        System.out.println(s);//中国

        dis.close();
    }
}

8.BufferedInputStream / BufferedOutputStream
这两个流类为IO提供了缓冲区的操作,一般打开文件进行写入或者读取操作,都会加上缓冲,这种流模式提高了IO的性能
从应用程序中把输入放入文件,就相当于将一缸水倒入另一个缸中:
FileOutputStream--->write()方法相当于一滴一滴把水“转移”过去
DataOutputStream--->writeXXX()方法会更方便一些,相当于一瓢一瓢把水“转移”过去
BufferedOutputStream--->write()方法更方便,相当于一瓢一瓢把水先放入桶中,再从桶中倒入到另一个缸里。

    //进行文件的拷贝,利用带缓冲的字节流
    public static void copyFileByBuffer(File srcFile, File destFile) throws IOException {
        if (!srcFile.exists()){
            throw new IllegalArgumentException("文件:"+srcFile+"不存在");
        }
        if (!srcFile.isFile()){
            throw new IllegalArgumentException(srcFile+"不是文件");
        }
        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();

    }

5.字符流的使用

1.编码的问题上面讲过了
2.认识文本和文本文件

java中的文本(Char)是一个16位无符号整数,是字符unicode(双字节)编码。就是说java中的文本就是char,而这里的char表示unicode中的所有能表示的单个字符,注意必须是单个的。而在java的语法中,定义一个char的时候必须用单引号来引起来,不能用双引号
文件是byte byte byte ……的数字序列
文本文件是文本(char)序列按照某种编码方法(utf-8, utf-16be, gbk)序列化为byte的存储结果。

3.字符流也分为输出流(Write)抽象类和输入流(Read)抽象类,都是操作文本文件,文本文件是把文本(char)按照一定的编码序列化成byte

字符的处理,一次处理一个字符
字符的底层依然是基本的字节序列

1.字符流的基本实现:
InputStreamReader 完成byte流解析为char流,按照编码解析
OutputStreamReader 提供char流到byte流,按照编码处理
此类主要对文件内的文本进行操作


/**
 * 字符流的读(InputStreamReader)和写操作(OutputStreamReader)
 */
public class IsrAndOswDemo {
    public static void main(String[] args) throws IOException {
        FileInputStream in = new FileInputStream("demo/dos.dat");
        InputStreamReader isr = new InputStreamReader(in, "utf-8");//utf-8是当前调用文件的默认编码,如果以后遇到不是这个编码的,要在这里修改

        //单个读取
        int c;
        while ((c=isr.read())!=-1){
            //char类型是一个16位的无符号整数,代表一个单个的字符
            System.out.print((char) c);//强制转换为char来打印,结果就是字符
        }


        //按照char[]数组大小批量读取
        char[] buffer = new char[8*1024];
        int c1;
        //批量读取,放入buffer这个字符数组,从第0个位置开始放置,最多放buffer.length个字符
        while ((c1 = isr.read(buffer,0,buffer.length))!=-1){
            String s = new String(buffer,0,c1);
            System.out.print(s);
        }

        //写操作
        FileOutputStream out = new FileOutputStream("demo/osw.dat");
        OutputStreamWriter osw = new OutputStreamWriter(out, "utf-8");//调用文件的编码方式
        osw.write("aaa11");
        osw.flush();

        isr.close();
        osw.close();
    }
}

字符流的第二种操作方式FileReader和FileWriter
这一类重要是对文件进行操作

/**
 * 字符流的第二种操作方式FileReader和FileWriter
 */
public class FrAndFwDemo {
    public static void main(String[] args) throws IOException {
        //实例化,不可指定编码格式
        FileReader fr = new FileReader("demo/dos.dat");
        //有true是对文件追加内容,没有true,则直接覆盖
        FileWriter fw = new FileWriter("demo/osw.dat",true);
        char[] buffer = new char[2056];
        int c;
        while ((c = fr.read(buffer, 0, buffer.length))!=-1) {
            fw.write(buffer, 0, c);
            fw.flush();
        }
        fr.close();
        fw.close();
    }
}

字符流的过滤器
BufferReader --->readLine 一次读一行
BufferWriter/PrintWriter --->写一行

/**
 * 字符流的过滤器
 */
public class BrAndBwPwDemo {
    public static void main(String[] args) throws IOException {
        //对文件进行读写操作
        BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("demo/dos.dat")));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("demo/out.dat")));

        String line;
        while ((line=br.readLine())!=null){
            System.out.println(line);//一次读一行,并不能识别换行符
            bw.write(line);//一次写一行,但是不会自动换行
            bw.newLine();//换行操作
            bw.flush();
        }
        br.close();
        bw.close();
        
    }
}

6.对象的序列化和反序列化

对象序列化:就是将Object转换成byte序列,反之叫对象的反序列化
序列化流(ObjectOutputStream),是过滤流---witeObject
反序列化流(ObjectInputStream)--->read Object

对象要进行序列化有一个接口(Serializable)
对象必须实现序列化接口,才能进行序列化,否则将出现异常,这个接口,没有任何方法,只是一个标准
序列号化和反序列号的操作
创建一个Student类,并实现Serializable接口

transient关键字
transient关键字限定这个元素不会进行JVM的序列号操作。但是并不是不能再对这个元素进行序列化,而是可以通过自己来做序列化

public class Student implements Serializable{
    private String stuno;
    private String stuname;
    private transient int stuage;//transient关键字限定这个元素不会进行JVM的序列号操作

    public Student(String stuno, String stuname, int stuage) {
        super();
        this.stuno = stuno;
        this.stuname = stuname;
        this.stuage = stuage;
    }

    public void setStuno(String stuno) {
        this.stuno = stuno;
    }

    public String getStuno() {
        return stuno;
    }

    public void setStuname(String stuname) {
        this.stuname = stuname;
    }

    public String getStuname() {
        return stuname;
    }

    public void setStuage(int stuage) {
        this.stuage = stuage;
    }

    public int getStuage() {
        return stuage;
    }

    @Override
    public String toString() {
        return "Student[stuno="+stuno+", stuname="+stuname+",stuage="+stuage+"]";
    }
}


序列化和反序列化操作


public class ObjectSeriaDemo1 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        String file = "demo/obj11.dat";
        //1.对象序列化
        /*
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
        Student stu = new Student("10001","张三", 20);
        oos.writeObject(stu);
        oos.flush();
        oos.close();
        */

        //2.反序列化操作
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
        Student stu = (Student)ois.readObject();
        System.out.println(stu);
        ois.close();
    }
}
posted @ 2016-12-09 15:58  洋葱源码  阅读(358)  评论(0编辑  收藏  举报