Java-IO

最近写代码遇到读取文件和加载文件的问题,搞了半天还是不咋懂,感觉还是自己Java的IO基础太薄弱了,现在重新回顾下,整理笔记如下:

第一节

所谓输入输出流就是把数据从文件中读取到程序中的某个对象中去,然后经过我们的程序处理后再写入到某个文件中去的过程。当然数据的来源和去处并不一定是文件,可能是网络,可能是屏幕等等。但过程就是这么个过程。那就产生了两个问题:
1. 怎么把数据读取到程序中来,读取进来后放在哪?
2. 怎么把存放在程序中的数据写入到目标位置去?

带着这两个问题就进入今天的主题:IO(in & out)。

概念扫盲

  • 字节流:以字节为传输的基本单位
  • 字符流:以字符为传输的基本单位
  • 节点流:数据流的处理方式(不处理)
  • 处理流:数据流的处理方式

注意: 输入流并不是真正的数据内容,而是一种将文件中的数据(或其他方式输入的数据)读取到内存中某个对象中的工具。其本质上是工具,而不是数据本身。输出流同理。

核心类

字节流:
InputStream–FileInputStream
OutputStream–FileOutputStream
字符流:
Reader–FileReader
Writer–FileWriter

完整继承图:

一个典型的输入流例子:

class Test{
    public static void main(String[] args) {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream("d:/test/from.txt");
            byte[] buffer = new byte[100];
            //read()第二个参数为偏移量,指前面空出多少位
            //read()第三个参数是每次读取的数据长度
            //最大值为数组的长度减去偏移量,否则越界
            fis.read(buffer,5,10);
            for(int i = 0;i<buffer.length;i++){
                System.out.println(buffer[i]);
            }
        }catch(Exception e) {
            System.out.println(e);
        }
    }
}

看完这个例子应该就明白了,数据存在数组中,由工具类读取。工具类首先生成一个绑定特定文件的对象,然后调用读方法,将数据读取到内存中的数组对象中去。
注意:以上例子有个问题,如果文件只有4个字节,而buffer有100个字节,这将导致buffer其余96个字节全部为0,这是一种空间上的浪费。如果能提前知道文件大小就好了(这个下面再讲)。

写入数据和上面的过程差不多,核心代码如下:

FileOutputStream fos = new FileOutputStream("d:/test/to.txt");
fos.write(buffer,0,fileLenght);
理解read和write的参数
  • offset是针对buffer而言的。
    对read来讲,offset = 5 等于将buffer的前5位写为0,然后从第六位(数组下表的第五位)开始写真正的数据。
    对write来讲,offset = 5 等于将buffer的前5位不写进文件,从第六位(数组下标的第五位)开始对文件注入数据。
  • 第三个参数length
    对read来讲,该参数代表要从文件中读取多少个字节到buffer中去,该长度不能超过buffer的长度。而buffer中未写入数据的部分元素将自动为0。但是该长度必须大于等于文件长度,否则文件的读取就不完整,而超出的部分自动为0. 但是这个0不是从文件中读取到的,而是从文件中读取不到,那么在内存中编译器自动为其分配0.
    对write来讲,该参数代表一共要写出多少字节。
  • read返回值
    该返回值是buffer中非空数据的长度。如果read有offset,数据为0000123000。。。
    则read返回值为3.

另外要注意,写入数据的时候不管目标文件现在存的是什么都将被写入操作覆盖

tips:
- 使用这种方法读取文件最好不要设置offset
- 使用这种字节流读取文件中如果含中文会产生乱码

第二节

对于上面的写法,有一个bug就是必须得事先知道要读取的文件有多长,否则不知道buffer设置多大。现在改用这种写法:

class Test{
    public static void main(String[] args) {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream("d:/test/from.txt");
            fos = new FileOutputStream("d:/test/to.txt");
            byte[] buffer = new byte[1024];//每次读取1024byte
            while(true){
                int a = fis.read(buffer,0,1024);
                if (a == -1) {
                    break;
                }
                fos.write(buffer,0,a);
            }
        }catch(Exception e) {
            System.out.println(e);
        }finally{
            try{
                fis.close();
                fos.close();
            } catch (Exception e){
                System.out.println(e);
            }

        }
    }
}

注意:write第三个参数一定要写a,否则最后一次读取会出问题:
如果最后一次只读取到了500个字节,则read方法将会改写buffer的前500个元素,后524个元素还是上一次读取的数据,并没有被覆盖,此时若还写入1024个数据,则会将上一次已经写入过的后524个数据重写写一遍,这不是我们想要看到的。
所以读和写的配合原则一定要是读多少,写多少。

上面介绍的都是字节流的读取方法,字符流的读取方法几乎一样,在此不赘述

到此,基本的读取文件和写入文件的过程就比较明了了。

第三节

处理流

处理流使用了设计模式中的装饰者模式,就是对现有的东西进行加工。作用和继承是一样的,但是装饰者模式能节省大量代码,具体请看另一篇文章。

下面介绍一种处理流(BufferedReader):
常用方法:public String readLine() throws IOException
实例:

class Test2{
    public static void main(String[] args) {
        FileReader fr = null;
        FileWriter fw = null;
        BufferedReader br = null;
        BufferedWriter bw = null;
        try {
            fr = new FileReader("d:/test/from.txt");
            br = new BufferedReader(fr);
            fw = new FileWriter("d:/test/to.txt");
            bw = new BufferedWriter(fw);
            String line = null;
            while(true){
                line = br.readLine();
                if (line == null) {
                    break;  
                }
                System.out.println(line);
                //flush一下,否则最后一次无法写入数据
                bw.flush();
                bw.write(line,0,line.length());
            }
        } catch(Exception e){
            System.out.println(e);
        } finally{
             try{
                //fr.close();
                //fw.close();
                br.close();
                bw.close();
             } catch(Exception e){
                System.out.println(e);
             }
         }
    }
}

对于flush()方法的解释:
- IO就好比将A桶的水通过输入流抽取到程序中来,加入糖,变成糖水,再通过输出流输出到B桶中去的过程。我们上面说了,输入输出是有基本单位的,不可能一个分子一个分子的取,最有可能的是一勺一勺的取。这样的情况下,输入是没问题的,输出就有问题了:当最后剩下的一点不够一勺时,write方法不会执行,因为缓冲区(勺子)还没满,这个时候就需要使用flush()方法强行将这剩下的一点写入到B桶中去。

关于上面最后的关闭方法说一点:
1. 先关外部,再关内部–没问题
2. 先关内部,再关外部–抛出异常
3. 只关外部–没问题
4. 只关内部–没问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值