java复习笔记7--java基础之I/O流2

字符流

前面针对字节流和字节缓存流做了一个比较全面的探索。字节流以字节(8bit)为单位读取数据,且可以处理所有的数据,包括文本,音频等,这里就要抛出一个问题了,既然字节流这么方便,只是读取数据比较麻烦,那我们完全可以包装字节流进行快速的一些处理,为什么还要衍生出字符流?

字符流的优势

  • 字符流提供了针对字符类型的数据的很好的API操作
  • 字节流读取中文的时候,存在乱码的情况
  • 字符流会使用缓冲区

字符流的API之后详细去看,先看一下中文乱码和缓冲区这两个。首先,字节流读取中文的时候为什么可能出现中文乱码的情况。有以下两种原因:

  • 我们数组转String是有默认的charset(“UTF-8”),由于中文在不同的编码中对应的字节以及长度都是不一样的,如若文件格式和转换所使用的编码不一致,就会出现乱码的情况,比如
@Test
    public void ioDemo1() {
        File file = new File("D:" + File.separator + "a.txt");
        if (file.exists()) {
            log.info(file.getAbsolutePath());
            InputStream inputStream = null;
            try {
                inputStream = new FileInputStream(file);
                inputStream = new FileInputStream("D:\\a.txt");
                new BufferedInputStream(inputStream);
                int n = 0;
                byte[] bytes =new byte[1024];
                StringBuilder sb = new StringBuilder();
                while ((n = inputStream.read(bytes)) != -1) {
                    sb.append(new String(bytes, 0, n));
                }
                System.out.print(sb.toString());
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

我们a.txt里面内容为 ‘’aaa中‘’,数组大小为1024,一次可以全部读取完,但是,由于我们文件使用的是GBK的编码,但是转换String使用的默认的utf-8的编码,导致输出是乱码的aaa�й�

  • 还有一种可能,我们这里将文件改为UTF-8格式的,这样格式一致了,但是我们将数组大小改为4::byte[] bytes =new byte[4];我们运行发现还是乱码:aaa�����。这里是什么原因呢?正如上面说的,字节流读取的时候,是以字节(8bit)为单位读取数据,英文字母和数字都是一个字节,而中文在UTF-8中是占三个字节的,而字节流读取数据的时候,,如果刚好读取到某个中文的第一个字节或者第二个字节,然后数组满了,返回了,这个时候,就会出现乱码

既然我们知道字节流读取会出现乱码的情况,并且知道原因,那怎么解决呢?其实很简单,读取的io流和转换为String的Charset保持一致,然后,根据各个charset针对中文的定义,判断是否读取到中文,如果是,根据规则将后面的剩余部分找出来一起拼凑:

{
                inputStream = new FileInputStream(file);
                inputStream = new FileInputStream("D:\\a.txt");
                new BufferedInputStream(inputStream);
                int n = 0;
                byte[] bytes =new byte[4];
                StringBuilder sb = new StringBuilder();
                while ((n = inputStream.read(bytes)) != -1) {
                    //判断最后一位是否是中文一部分
                    int a = 0;
                    if(n == bytes.length && bytes[bytes.length - 1] < 0){
                        //判断缺几个字节
                        for(int i = 0; i< bytes.length; i++){
                            if(bytes[i] < 0){
                                a++;
                            }
                        }
                    }
                    a = a% 3;
                    if(a > 0){
                        byte[] bytes1 =new byte[3 - a];
                        for(int i = 0; i< (3 - a); i++){
                            bytes1[i] =  (byte)inputStream.read();
                        }
                        byte[] bytes2 = Arrays.copyOfRange(bytes, n -a, n);
                        byte[] bytes3 = ArrayUtils.addAll(bytes2, bytes1);
                        //无需拼接的部分
                        sb.append(new String(bytes, 0, n -a));
                        //拼接的中文
                        sb.append(new String(bytes3));
                    }else{
                        sb.append(new String(bytes, 0, n));
                    }
                }
                System.out.print(sb.toString());
            }

整个实现代码还是比较冗余的,所以字符流应运而生,对于文本和字符数据的处理,建议使用字符流,一来不存在乱码的问题,二来使用缓冲区,效率高,三来也有比较多简洁便捷的API。缓冲区也是我们一直提到的一个知识点,其实缓冲区是固定长度的数据容器,也是内存中的一部分。有一个单独的缓冲区类型来保存每种类型的基本值的数据,除了布尔类型值。比如创建一个8个字节的缓冲区:ByteBuffer bb = ByteBuffer.allocate(8);,缓冲区和数组很类似,有容量,有position(指针),只是它内部有一些自己针对读和写的处理方法。

基础的字符流

和字节流一样,Reader 是所有的输入字符流的父类,它是一个抽象类,是将磁盘(文件)中的数据读入内存中。Writer:是所有的输出字符流的父类,它是一个抽象类,是将内存中的数据按照字符形式写入磁盘(文件)中。
在这里插入图片描述

在这里插入图片描述
字符流的构造方法以及基类中的对于数据的读取基本和字节流一致,具体的构造函数和API可以看一篇文章的介绍,或者自行阅读,比较简单,代码的写法也和字节流相似:

{
        File file = new File("D:" + File.separator + "a.txt");
        if (file.exists()) {
            FileReader fileReader = null;
            try {
               fileReader = new FileReader(file);
                char [] ch=new char[1024];
                int len=0;
                while((len=fileReader.read(ch))!=-1){
                    System.out.print(new String(ch,0,len));
                }
                fileReader.close();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
            catch (IOException e) {
                e.printStackTrace();
            }

        }
    }

字符缓冲流

  • BufferedReader:字符输入缓冲流,从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。BufferedReader 由Reader类扩展而来,也是装饰者模式的典型应用,BufferedReader 的构造函数需要传一个reader进来:public BufferedReader(Reader in) { this(in, defaultCharBufferSize); },后面那个表示的是默认的缓冲区的大小(8192)。他提供了一个readLine的逐行读取文本的方法:String readLine(boolean ignoreLF) throws IOException,对于文本数据和字符数据的处理很方便。

  • BufferedWriter:字符输出缓冲流,原理和BufferedReader类似。 该类提供了 newLine() 方法,与BufferedReader的readLine呼应,它使用平台自己的行分隔符概念(由系统属性 line.separator 定义)。其实也是对于write方法的一次封装。

字符缓冲流对于我们工作中与文本或者字符数据打交道,对于他们的处理有一个很好的帮助,能够大大简化我们的代码量,例如,这里用readLine和newLine方法去实现上一次的文件的copy,代码会很简洁:

@Test
    public void ioDemo3()  {
        File file = new File("D:" + File.separator + "a.txt");
        if (file.exists()) {
            BufferedReader bufferedReader = null;
            BufferedWriter bufferedWriter = null;
            try {
                bufferedReader = new BufferedReader(new FileReader("D:\\a.txt"));
                bufferedWriter = new BufferedWriter(new FileWriter("E:\\a.txt"));
                String str=null;
                while((str=bufferedReader.readLine())!=null){
                    bufferedWriter.write(str);
                    bufferedWriter.newLine();//写入一个行分隔符。行分隔符字符串由系统属性 line.separator 定义,并且不一定是单个新行 ('\n') 符。
                    bufferedWriter.flush();//缓冲输出流一定要flush
                }
                bufferedWriter.close();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
            catch (IOException e) {
                e.printStackTrace();
            }finally {
                try {
                    bufferedWriter.close();
                    bufferedWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
    }

至此,字符流我们基本有了一个了解,对于其用法和常用的API也有了运用,对于长期与磁盘,文件打交道的程序员来说,这些知识虽然很基础,但是也是必不可少的。下一次温习,我们将针对遗留的读取数据的pos,也就是指针和reset,mark这些方法做一些学习和分享。

猜你喜欢

转载自blog.csdn.net/aizhupo1314/article/details/83892971