ITPub博客

首页 > 应用开发 > Java > JAVA中级教程 - IO输入输出

JAVA中级教程 - IO输入输出

Java 作者:喝水会长肉 时间:2021-12-11 18:10:48 0 删除 编辑

 2.1 文件对象

文件和文件夹都是用File代表

  • 创建一个文件对象

使用绝对路径或者相对路径创建File对象


package file
;

import java .io .File ;
public class TestFile {
    public static void main ( String [ ] args ) {
        // 绝对路径
       File f1 = new File ( "d:/LOLFolder" ) ;
       System .out . println ( "f1的绝对路径:" + f1 . getAbsolutePath ( ) ) ;
       
        // 相对路径,相对于工作目录,如果在eclipse中,就是项目目录
       File f2 = new File ( "LOL.exe" ) ;
       System .out . println ( "f2的绝对路径:" + f2 . getAbsolutePath ( ) ) ;
 
        // 把f1作为父目录创建文件对象
       File f3 = new File (f1 , "LOL.exe" ) ;
       System .out . println ( "f3的绝对路径:" + f3 . getAbsolutePath ( ) ) ;
    }
}



  • 文件常用方法1

注意1: 需要在D:\LOLFolder确实存在一个LOL.exe,才可以看到对应的文件长度、修改时间等信息

注意2:  renameTo方法用于对物理文件名称进行修改,但是并不会修改File对象的name属性。


package file
;

 
import java .io .File ;
import java .util .Date ;
 
public class TestFile {
 
    public static void main ( String [ ] args ) {
 
       File f = new File ( "d:/LOLFolder/LOL.exe" ) ;
       System .out . println ( "当前文件是:" +f ) ;
        //文件是否存在
       System .out . println ( "判断是否存在:" +f . exists ( ) ) ;
       
        //是否是文件夹
       System .out . println ( "判断是否是文件夹:" +f . isDirectory ( ) ) ;
         
        //是否是文件(非文件夹)
       System .out . println ( "判断是否是文件:" +f . isFile ( ) ) ;
         
        //文件长度
       System .out . println ( "获取文件的长度:" +f . length ( ) ) ;
         
        //文件最后修改时间
       long time = f . lastModified ( ) ;
       Date d = new Date (time ) ;
       System .out . println ( "获取文件的最后修改时间:" +d ) ;
        //设置文件修改时间为1970.1.1 08:00:00
       f . setLastModified ( 0 ) ;
         
        //文件重命名
       File f2 = new File ( "d:/LOLFolder/DOTA.exe" ) ;
       f . renameTo (f2 ) ;
       System .out . println ( "把LOL.exe改名成了DOTA.exe" ) ;
       
       System .out . println ( "注意: 需要在D:\\LOLFolder确实存在一个LOL.exe,\r\n才可以看到对应的文件长度、修改时间等信息" ) ;
    }
}


  • 文件常用方法2


package file
;

 
import java .io .File ;
import java .io .IOException ;
 
public class TestFile {
 
    public static void main (String [ ] args ) throws IOException {
 
       File f = new File ( "d:/LOLFolder/skin/garen.ski" ) ;
 
        // 以字符串数组的形式,返回当前文件夹下的所有文件(不包含子文件及子文件夹)
       f . list ( ) ;
 
        // 以文件数组的形式,返回当前文件夹下的所有文件(不包含子文件及子文件夹)
       File [ ]fs = f . listFiles ( ) ;
 
        // 以字符串形式返回获取所在文件夹
       f . getParent ( ) ;
 
        // 以文件形式返回获取所在文件夹
       f . getParentFile ( ) ;
        // 创建文件夹,如果父文件夹skin不存在,创建就无效
       f . mkdir ( ) ;
 
        // 创建文件夹,如果父文件夹skin不存在,就会创建父文件夹
       f . mkdirs ( ) ;
 
        // 创建一个空文件,如果父文件夹skin不存在,就会抛出异常
       f . createNewFile ( ) ;
        // 所以创建一个空文件之前,通常都会创建父目录
       f . getParentFile ( ) . mkdirs ( ) ;
 
        // 列出所有的盘符c: d: e: 等等
       f . listRoots ( ) ;
 
        // 刪除文件
       f . delete ( ) ;
 
        // JVM结束的时候,刪除文件,常用于临时文件的删除
       f . deleteOnExit ( ) ;
 
    }
}


  • 练习-遍历文件夹

一般说来操作系统都会安装在C盘,所以会有一个 C:\WINDOWS目录。

遍历这个目录下所有的文件(不用遍历子目录)

找出这些文件里,最大的和最小(非0)的那个文件,打印出他们的文件名

注: 最小的文件不能是0长度


package file
;

 
import java .io .File ;
 
public class TestFile {
 
    public static void main ( String [ ] args ) {
       File f = new File ( "c:\\windows" ) ;
       File [ ] fs = f . listFiles ( ) ;
        if ( null ==fs )
            return ;
       long minSize = Integer . MAX_VALUE ;
       long maxSize = 0 ;
       File minFile = null ;
       File maxFile = null ;
        for (File file : fs ) {
            if (file . isDirectory ( ) )
                continue ;
            if (file . length ( ) >maxSize ) {
               maxSize = file . length ( ) ;
               maxFile = file ;
            }
            if (file . length ( ) != 0 && file . length ( ) <minSize ) {
               minSize = file . length ( ) ;
               minFile = file ;
            }
        }
       System .out . printf ( "最大的文件是%s,其大小是%,d字节%n" ,maxFile . getAbsoluteFile ( ) ,maxFile . length ( ) ) ;
       System .out . printf ( "最小的文件是%s,其大小是%,d字节%n" ,minFile . getAbsoluteFile ( ) ,minFile . length ( ) ) ;
 
    }
}


  • 练习-遍历子文件夹

使用递归来进行文件夹的遍历


package file
;

 
! [image .png ] (https : / /p1 -juejin .byteimg .com /tos -cn -i -k3u1fbpfcp / 0fb606b0616d4de291553986e8058a93 ~tplv -k3u1fbpfcp -watermark .image ? )
import java .io .File ;
 
public class TestFile {
     
    static long minSize = Integer . MAX_VALUE ;
    static long maxSize = 0 ;
    static File minFile = null ;
    static File maxFile = null ;
     
    //使用递归来遍历一个文件夹的子文件
    public static void listFiles ( File file ) {
        if (file . isFile ( ) ) {
            if (file . length ( ) >maxSize ) {
               maxSize = file . length ( ) ;
               maxFile = file ;
            }
            if (file . length ( ) != 0 && file . length ( ) <minSize ) {
               minSize = file . length ( ) ;
               minFile = file ;
            }
            return ;
        }
         
        if (file . isDirectory ( ) ) {
           File [ ] fs = file . listFiles ( ) ;
            if ( null !=fs )
            for (File f : fs ) {
                listFiles (f ) ;
            }
        }
    }
 
    public static void main ( String [ ] args ) {
       File f = new File ( "c:\\windows" ) ;
        listFiles (f ) ;
       System .out . printf ( "最大的文件是%s,其大小是%,d字节%n" ,maxFile . getAbsoluteFile ( ) ,maxFile . length ( ) ) ;
       System .out . printf ( "最小的文件是%s,其大小是%,d字节%n" ,minFile . getAbsoluteFile ( ) ,minFile . length ( ) ) ;
 
    }
}


2.2 什么是流

什么是流(Stream),流就是一系列的数据

  • 什么是流

当不同的介质之间有数据交互的时候,JAVA就使用流来实现。 数据源可以是文件,还可以是数据库,网络甚至是其他的程序。

比如读取文件的数据到程序中,站在程序的角度来看,就叫做输入流: 输入流: InputStream 输出流:OutputStream

  • 文件输入流

如下代码,就建立了一个文件输入流,这个流可以用来把数据从硬盘的文件,读取到JVM(内存)。

目前代码只是建立了流,还没有开始读取,真正的读取在下个章节讲解。


package stream
;

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

public class TestStream {
    public static void main ( String [ ] args ) {
        try {
           File f = new File ( "d:/lol.txt" ) ;
            // 创建基于文件的输入流
           FileInputStream fis = new FileInputStream (f ) ;
            // 通过这个输入流,就可以把数据从硬盘,读取到Java的虚拟机中来,也就是读取到内存中
        } catch (IOException e ) {
            // TODO Auto-generated catch block
           e . printStackTrace ( ) ;
        }
    }
}


  • 练习-流


public 
class 
TestStream 
{

    public static void main ( String [ ] args ) {
        try {
           File f = new File ( "d:/lol.txt" ) ;
           FileOutputStream fos = new FileOutputStream (f ) ;
        } catch (IOException e ) {
           e . printStackTrace ( ) ;
        }
    }
}

2.3 字节流

InputStream字节输入流,OutputStream字节输出流,用于以字节的形式读取和写入数据。

  • ASCII码 概念

所有的数据存放在计算机中都是以数字的形式存放的。 所以 字母就需要转换为数字才能够存放。 比如A就对应的数字65,a对应的数字97. 不同的字母和符号对应不同的数字,就是一张码表。ASCII是这样的一种码表。 只 包含简单的英文字母,符号,数字等等。  不包含中文,德文,俄语等复杂的。

  • 以字节流的形式读取文件内容

InputStream是字节输入流,同时也是抽象类,只提供方法声明,不提供方法的具体实现。 FileInputStream 是InputStream子类,以FileInputStream 为例进行文件读取


package stream
;

 
import java .io .File ;
import java .io .FileInputStream ;
import java .io .IOException ;
 
public class TestStream {
    public static void main ( String [ ] args ) {
        try {
            //准备文件lol.txt其中的内容是AB,对应的ASCII分别是65 66
           File f = new File ( "d:/lol.txt" ) ;
            //创建基于文件的输入流
           FileInputStream fis = new FileInputStream (f ) ;
            //创建字节数组,其长度就是文件的长度
           byte [ ] all = new byte [ (int ) f . length ( ) ] ;
            //以字节流的形式读取文件所有内容
           fis . read (all ) ;
            for (byte b : all ) {
                //打印出来是65 66
               System .out . println (b ) ;
            }
           
            //每次使用完流,都应该进行关闭
           fis . close ( ) ;
             
        } catch (IOException e ) {
            // TODO Auto-generated catch block
           e . printStackTrace ( ) ;
        }
    }
}


  • 以字节流的形式向文件写入数据

OutputStream是字节输出流,同时也是抽象类,只提供方法声明,不提供方法的具体实现。FileOutputStream 是OutputStream子类,以FileOutputStream 为例向文件写出数据。

注: 如果文件d:/lol2.txt不存在,写出操作会自动创建该文件。 但是如果是文件 d:/xyz/lol2.txt,而目录xyz又不存在,会抛出异常


package stream
;


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

public class TestStream {
    public static void main ( String [ ] args ) {
        try {
            // 准备文件lol2.txt其中的内容是空的
           File f = new File ( "d:/lol2.txt" ) ;
            // 准备长度是2的字节数组,用88,89初始化
            //其对应的字符分别是X,Y
           byte data [ ] = { 88 , 89 } ;

            // 创建基于文件的输出流
           FileOutputStream fos = new FileOutputStream (f ) ;
            // 把数据写入到输出流
           fos . write (data ) ;
            // 关闭输出流
           fos . close ( ) ;
        } catch (IOException e ) {
            // TODO Auto-generated catch block
           e . printStackTrace ( ) ;
        }
    }
}


  • 练习-写入数据到文件

以字节流的形式向文件写入数据 中的例子,当lol2.txt不存在的时候,是会自动创建lol2.txt文件的。但是,如果是写入数据到d:/xyz/lol2.txt,而目录xyz又不存在的话,就会抛出异常。 那么怎么自动创建xyz目录? 如果是多层目录 d:/xyz/abc/def/lol2.txt 呢?

 
// 准备文件lol2.txt其中的内容是空的

           File f = new File ( "d:/xyz/abcd/lol.txt" ) ;

            //获取文件所在的目录
           File dir = f . getParentFile ( ) ;
            if ( !dir . exists ( ) ) {
                //dir.mkdir(); //使用mkdir会抛出异常,因为该目录的父目录也不存在
               dir . mkdirs ( ) ; //使用mkdirs则会把不存在的目录都创建好
            }


  • 练习-拆分文件

找到一个大于100k的文件,按照100k为单位,拆分成多个子文件,并且以编号作为文件名结束。 比如文件 eclipse.exe,大小是309k。 拆分之后,成为 eclipse.exe-0 eclipse.exe-1 eclipse.exe-2 eclipse.exe-3


package com
.company
;


import java .io .File ;
import java .io .FileInputStream ;
import java .io .FileOutputStream ;
import java .io .IOException ;
import java .util .Arrays ;

public class TestStream {
    /**
    * 拆分的思路,先把源文件的所有内容读取到内存中,然后从内存中挨个分到子文件里
    * @param srcFile 要拆分的源文件
    * @param eachSize 按照这个大小,拆分
    */

    private static void splitFile ( File srcFile ,int eachSize ) {
        if (srcFile . length ( ) == 0 )
            throw new RuntimeException ( "文件长度为0,不可拆分" ) ;
       byte [ ] fileContent = new byte [ (int ) srcFile . length ( ) ] ;
        try {
            //创建基于文件的输入流
           FileInputStream fis = new FileInputStream (srcFile ) ;
           fis . read (fileContent ) ;
           fis . close ( ) ;
        } catch (IOException e ) {
           e . printStackTrace ( ) ;
        }

        // 计算需要被划分成多少份子文件
       int fileNumber ;
        if ( 0 == fileContent .length % eachSize )
           fileNumber = (int ) (fileContent .length / eachSize ) ;
        else
           fileNumber = (int ) (fileContent .length / eachSize ) + 1 ;

        for (int i = 0 ;i < fileNumber ;i ++ ) {
           String eachFileName = srcFile . getName ( ) + "-" + i ;
           File eachFile = new File (srcFile . getParent ( ) ,eachFileName ) ;
           byte [ ] eachContent ;

            // 从源文件的内容里,复制部分数据到子文件
            // 除开最后一个文件,其他文件大小都是100k
            // 最后一个文件的大小是剩余的
            if (i != fileNumber - 1 ) {
               eachContent = Arrays . copyOfRange (fileContent ,eachSize *i ,eachSize ) ;
            }
            else {
               eachContent = Arrays . copyOfRange (fileContent , eachSize * i , fileContent .length ) ;
            }

            try {
                // 写出去
               FileOutputStream fos = new FileOutputStream (eachFile ) ;
               fos . write (eachContent ) ;
               fos . close ( ) ;
               System .out . printf ( "输出子文件%s,其大小是 %d字节%n" , eachFile . getAbsoluteFile ( ) , eachFile . length ( ) ) ;
            }
            catch (IOException e ) {
               e . printStackTrace ( ) ;
            }
        }
    }

    public static void main ( String [ ] args ) {
       int eachSize = 100 * 1024 ; // 100k
       File srcFile = new File ( "d:/eclipse.exe" ) ;
        splitFile (srcFile , eachSize ) ;
    }
}


  • 练习-合并文件

把上述拆分出来的文件,合并成一个原文件。

以是否能正常运行,验证合并是否正确


package com
.company
;


import java .io . * ;
import java .util .Arrays ;

public class TestStream {
    /**
    * 合并的思路,就是从eclipse.exe-0开始,读取到一个文件,就开始写出到 eclipse.exe中,直到没有文件可以读
    * @param folder
    *            需要合并的文件所处于的目录
    * @param fileName
    *            需要合并的文件的名称
    * @throws FileNotFoundException
    */

    private static void mergeFile ( String folder , String fileName ) {
        try {
            // 合并的目标文件
           File destFile = new File (folder , fileName ) ;
           FileOutputStream fos = new FileOutputStream (destFile ) ;
           int index = 0 ;
            while ( true ) {
                //子文件
               File eachFile = new File (folder , fileName + "-" + index ++ ) ;
                //如果子文件不存在了就结束
                if ( !eachFile . exists ( ) )
                    break ;

                //读取子文件的内容
               FileInputStream fis = new FileInputStream (eachFile ) ;
               byte [ ] eachContent = new byte [ (int ) eachFile . length ( ) ] ;
               fis . read (eachContent ) ;
               fis . close ( ) ;

                //把子文件的内容写出去
               fos . write (eachContent ) ;
               fos . flush ( ) ;
               System .out . printf ( "把子文件 %s写出到目标文件中%n" ,eachFile ) ;
            }
           fos . close ( ) ;
           System .out . printf ( "最后目标文件的大小:%,d字节" , destFile . length ( ) ) ;
        } catch (FileNotFoundException e ) {
            // TODO Auto-generated catch block
           e . printStackTrace ( ) ;
        } catch (IOException e ) {
            // TODO Auto-generated catch block
           e . printStackTrace ( ) ;
        }
    }
   
    public static void main ( String [ ] args ) {
        mergeFile ( "d:/" , "eclipse.exe" ) ;
    }
}


2.4 关闭流的方式

所有的流,无论是输入流还是输出流,使用完毕之后,都应该关闭。 如果不关闭,会产生对资源占用的浪费。 当量比较大的时候,会影响到业务的正常开展。

  • 在try中关闭

在try的作用域里关闭文件输入流,在前面的示例中都是使用这种方式,这样做有一个弊端; 如果文件不存在,或者读取的时候出现问题而抛出异常,那么就不会执行这一行关闭流的代码,存在巨大的资源占用隐患。  不推荐使用

 
try 
{

           File f = new File ( "d:/lol.txt" ) ;
           FileInputStream fis = new FileInputStream (f ) ;
           byte [ ] all = new byte [ (int ) f . length ( ) ] ;
           fis . read (all ) ;
            for (byte b : all ) {
               System .out . println (b ) ;
            }
            // 在try 里关闭流
           fis . close ( ) ;
        } catch (IOException e ) {
           e . printStackTrace ( ) ;
        }


  • 在finally中关闭

这是标准的关闭流的方式

  1. 首先把流的引用声明在try的外面,如果声明在try里面,其作用域无法抵达finally.

  2. 在finally关闭之前,要先判断该引用是否为空

  3. 关闭的时候,需要再一次进行try catch处理

这是标准的严谨的关闭流的方式,但是看上去很繁琐,所以写不重要的或者测试代码的时候,都会采用上面的 有隐患try的方式,因为不麻烦~

 
public 
static 
void 
main
(
String
[
] args
) 
{

       File f = new File ( "d:/lol.txt" ) ;
       FileInputStream fis = null ;
        try {
           fis = new FileInputStream (f ) ;
           byte [ ] all = new byte [ (int ) f . length ( ) ] ;
           fis . read (all ) ;
            for (byte b : all ) {
               System .out . println (b ) ;
            }

        } catch (IOException e ) {
           e . printStackTrace ( ) ;
        } finally {
            // 在finally 里关闭流
            if ( null != fis )
                try {
                   fis . close ( ) ;
                } catch (IOException e ) {
                    // TODO Auto-generated catch block
                   e . printStackTrace ( ) ;
                }
        }
    }


  • 使用try()的方式

把流定义在try()里,try,catch或者finally结束的时候,会自动关闭 这种编写代码的方式叫做  try-with-resources, 这是从JDK7开始支持的技术

所有的流,都实现了一个接口叫做  AutoCloseable,任何类实现了这个接口,都可以在try()中进行实例化。 并且在try, catch, finally结束的时候自动关闭,回收相关资源。


package stream
;

 
import java .io .File ;
import java .io .FileInputStream ;
import java .io .IOException ;
 
public class TestStream {
    public static void main ( String [ ] args ) {
       File f = new File ( "d:/lol.txt" ) ;
 
        //把流定义在try()里,try,catch或者finally结束的时候,会自动关闭
        try (FileInputStream fis = new FileInputStream (f ) ) {
           byte [ ] all = new byte [ (int ) f . length ( ) ] ;
           fis . read (all ) ;
            for (byte b : all ) {
               System .out . println (b ) ;
            }
        } catch (IOException e ) {
           e . printStackTrace ( ) ;
        }
    }
}


2.5 字符流

Reader字符输入流,Writer字符输出流,专门用于字符的形式读取和写入数据。

  • 使用字符流读取文件

FileReader 是Reader子类,以FileReader 为例进行文件读取。

ackage stream
;


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

public class TestStream {

    public static void main ( String [ ] args ) {
        // 准备文件lol.txt其中的内容是AB
       File f = new File ( "d:/lol.txt" ) ;
        // 创建基于文件的Reader
        try (FileReader fr = new FileReader (f ) ) {
            // 创建字符数组,其长度就是文件的长度
           char [ ] all = new char [ (int ) f . length ( ) ] ;
            // 以字符流的形式读取文件所有内容
           fr . read (all ) ;
            for (char b : all ) {
                // 打印出来是A B
               System .out . println (b ) ;
            }
        } catch (IOException e ) {
            // TODO Auto-generated catch block
           e . printStackTrace ( ) ;
        }

    }
}


  • 使用字符流把字符串写入到文件

FileWriter 是Writer的子类,以FileWriter 为例把字符串写入到文件


package stream
;

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

public class TestStream {
    public static void main ( String [ ] args ) {
        // 准备文件lol2.txt
       File f = new File ( "d:/lol2.txt" ) ;
        // 创建基于文件的Writer
        try (FileWriter fr = new FileWriter (f ) ) {
            // 以字符流的形式把数据写入到文件中
           String data = "abcdefg1234567890" ;
           char [ ] cs = data . toCharArray ( ) ;
           fr . write (cs ) ;
 
        } catch (IOException e ) {
            // TODO Auto-generated catch block
           e . printStackTrace ( ) ;
        }
    }
}


  • 练习-文件加密

准备一个 文本文件(非二进制),其中包含 ASCII码的字符和中文字符,设计一个方法。

public static void encodeFile(File encodingFile, File encodedFile);

在这个方法中把encodingFile的内容进行加密,然后保存到encodedFile文件中。 加密算法:  数字: 如果不是9的数字,在原来的基础上加1,比如5变成6, 3变成4 如果是9的数字,变成0  字母字符: 如果是非z字符,向右移动一个,比如d变成e, G变成H 如果是z,z->a, Z-A。 字符需要保留大小写  非字母字符 比如',&^ 保留不变,中文也保留不变  建议: 使用以前学习的练习题中的某个Java文件,比如循环练习,就有很多的字符和数字


package com
.company
;


import java .io . * ;

public class TestStream {
    public static void encodeFile ( File encodingFile , File encodedFile ) {
        //encoding file  encoded file
       char [ ]all = new char [ (int )encodingFile . length ( ) ] ;
        try (FileReader fr = new FileReader (encodingFile ) ) {
            // 以字符流的形式读取文件所有内容
           fr . read (all ) ;
        } catch (FileNotFoundException e ) {
           e . printStackTrace ( ) ;
        } catch (IOException e ) {
           e . printStackTrace ( ) ;
        }
//java学习交流:737251827  进入可领取学习资源及对十年开发经验大佬提问,免费解答!
        try (FileWriter fw = new FileWriter (encodedFile ) ) {
            for (int index = 0 ;index < all .length ;index ++ ) {
                if (Character . isLetter (all [index ] ) | Character . isDigit (all [index ] ) )
                   all [index ] = encodeChar (all [index ] ) ;
            }
           fw . write (all ) ;
        } catch (FileNotFoundException e ) {
           e . printStackTrace ( ) ;
        } catch (IOException e ) {
           e . printStackTrace ( ) ;
        }
    }

    public static char encodeChar ( char sourceChar ) {
       char result = '\u0000' ;
       int asciiNum = (int ) sourceChar ;
        // 当转换后的编码大于127时,可以判断不是ascii码,直接返回
        if (asciiNum > 127 )
            return sourceChar ;
        if (asciiNum >= 65 && asciiNum <= 90 || asciiNum >= 97 && asciiNum <= 122 )
        {
            if (asciiNum >= 65 && asciiNum < 90
                    || asciiNum >= 97 && asciiNum < 122 )
                return (char ) (asciiNum + 1 ) ;
            else
                return (char ) (asciiNum - 25 ) ;
        }
        return result ;
    }

    public static void main ( String [ ] args ) {
        try {
           File encondingFile = new File ( "d:/lol.txt" ) ;
           File encondedFile = new File ( "d:/lol.txt" ) ;
            encodeFile (encondingFile ,encondedFile ) ;
        } catch (Exception e ) {
           e . printStackTrace ( ) ;
        }
    }
}

2.6 中文问题

  • 编码概念

计算机存放数据只能存放数字,所有的字符都会被转换为不同的数字。就像一个棋盘一样,不同的字,处于不同的位置,而不同的位置,有不同的数字编号。 有的棋盘很小,只能放数字和英文;有的大一点,还能放中文;有的“足够”大,能够放下世界人民所使用的所有文字和符号。

如图所示,英文字符  A 能够放在所有的棋盘里,而且位置都差不多 中文字符, 中文字符  中 能够放在后两种棋盘里,并且位置不一样,而且在小的那个棋盘里,就放不下中文

  • 常见编码

工作后经常接触的编码方式有如下几种:  ISO-8859-1 ASCII 数字和西欧字母  GBK GB2312 BIG5 中文  UNICODE (统一码,万国码)

其中 ISO-8859-1 包含 ASCII GB2312 是简体中文,BIG5是繁体中文,GBK同时包含简体和繁体以及日文。 UNICODE 包括了所有的文字,无论中文,英文,藏文,法文,世界所有的文字都包含其中

UNICODE和UTF

根据前面的学习,我们了解到不同的编码方式对应不同的 棋盘,而UNICODE因为要存放所有的数据,那么它的棋盘是最大的。不仅如此,棋盘里每个数字都是很长的(4个字节),因为不仅要表示字母,还要表示汉字等。

如果完全按照UNICODE的方式来存储数据,就会有很大的浪费。 比如在ISO-8859-1中, a 字符对应的数字是0x61 而UNICODE中对应的数字是 0x00000061,倘若一篇文章大部分都是英文字母,那么按照UNICODE的方式进行数据保存就会消耗很多空间

在这种情况下,就出现了UNICODE的各种 减肥子编码, 比如UTF-8对数字和字母就使用一个字节,而对汉字就使用3个字节,从而达到了 减肥还能保证健康的效果。

UTF-8,UTF-16和UTF-32 针对不同类型的数据有不同的 减肥效果,一般说来UTF-8是比较常用的方式。UTF-8,UTF-16和UTF-32 彼此的区别在此不作赘述,有兴趣的可以参考  unicode-百度百科

  • Java采用的是Unicode

写在.java源代码中的汉字,在执行之后,都会变成JVM中的字符。 而这些中文字符采用的编码方式, 都是使用UNICODE. "中"字对应的UNICODE是 4E2D,所以在内存中,实际保存的数据就是十六进制的0x4E2D, 也就是十进制的20013。


package stream
;


public class TestStream {
    public static void main ( String [ ] args ) {
       String str = "中" ;
    }
}
  • 一个汉字使用不同编码方式的表现

以字符  中 为例,查看其在不同编码方式下的值是多少,也即在不同的 棋盘上的位置


package stream
;


import java .io .UnsupportedEncodingException ;

public class TestStream {

    public static void main ( String [ ] args ) {
       String str = "中" ;
        showCode (str ) ;
    }

    private static void showCode ( String str ) {
       String [ ] encodes = { "BIG5" , "GBK" , "GB2312" , "UTF-8" , "UTF-16" , "UTF-32" } ;
        for (String encode : encodes ) {
            showCode (str , encode ) ;
        }

    }

    private static void showCode ( String str , String encode ) {
        try {
           System .out . printf ( "字符: \"%s\" 的在编码方式%s下的十六进制值是%n" , str , encode ) ;
           byte [ ] bs = str . getBytes (encode ) ;

            for (byte b : bs ) {
               int i = b & 0xff ;
               System .out . print (Integer . toHexString (i ) + "\t" ) ;
            }
           System .out . println ( ) ;
           System .out . println ( ) ;
        } catch (UnsupportedEncodingException e ) {
           System .out . printf ( "UnsupportedEncodingException: %s编码方式无法解析字符%s\n" , encode , str ) ;
        }
    }
}
  • 文件的编码方式-记事本

接下来讲,字符在文件中的保存 字符保存在文件中肯定也是以数字形式保存的,即对应在不同的 棋盘上的不同的数字 用 记事本打开任意文本文件,并且 另存为,就能够在编码这里看到一个下拉。  ANSI 这个 不是ASCII的意思,而是采用 本地编码的意思。如果你是中文的操作系统,就会使GBK,如果是英文的就会是ISO-8859-1

Unicode UNICODE原生的编码方式  Unicode big endian 另一个 UNICODE编码方式  UTF-8 最常见的UTF-8编码方式,数字和字母用一个字节, 汉字用3个字节。


  • 文件的编码方式-eclipse

eclipse也有类似的编码方式,右键任意文本文件,点击最下面的"property" 就可以看到 Text file encoding 也有ISO-8859-1,GBK,UTF-8等等选项。 其他的US-ASCII,UTF-16,UTF-16BE,UTF-16LE不常用。

  • 为了能够正确的读取中文内容

  1. 必须了解文本是以哪种编码方式保存字符的

  2. 使用字节流读取了文本后,再使用对应的 编码方式去识别这些数字,得到正确的字符

如本例,一个文件中的内容是字符 中,编码方式是GBK,那么读出来的数据一定是D6D0。 再使用GBK编码方式识别D6D0,就能正确的得到字符 中

注: 在GBK的棋盘上找到的 中字后,JVM会自动找到 中在UNICODE这个棋盘上对应的数字,并且以 UNICODE上的数字保存在内存中。


package stream
;

 
import java .io .File ;
import java .io .FileInputStream ;
import java .io .IOException ;
 
public class TestStream {
 
    public static void main ( String [ ] args ) {
       File f = new File ( "E:\\project\\j2se\\src\\test.txt" ) ;
        try (FileInputStream fis = new FileInputStream (f ) ; ) {
           byte [ ] all = new byte [ (int ) f . length ( ) ] ;
           fis . read (all ) ;
 
            //文件中读出来的数据是
           System .out . println ( "文件中读出来的数据是:" ) ;
            for (byte b : all )
            {
               int i = b & 0x000000ff ;   //只取16进制的后两位
               System .out . println (Integer . toHexString (i ) ) ;
            }
           System .out . println ( "把这个数字,放在GBK的棋盘上去:" ) ;
           String str = new String (all , "GBK" ) ;
           System .out . println (str ) ;
        } catch (IOException e ) {
            // TODO Auto-generated catch block
           e . printStackTrace ( ) ;
        }
    }
}



  • 用FileReader 字符流正确读取中文

FileReader得到的是字符,所以一定是已经把字节 根据某种编码识别成了字符了 而FileReader使用的编码方式是Charset.defaultCharset()的返回值,如果是中文的操作系统,就是GBK FileReader是不能手动设置编码方式的,为了使用其他的编码方式,只能使用InputStreamReader来代替,像这样:

new InputStreamReader(new FileInputStream(f),Charset.forName("UTF-8"));


在本例中,用记事本另存为UTF-8格式,然后用UTF-8就能识别对应的中文了。

解释: 为什么中字前面有一个? 如果是使用记事本另存为UTF-8的格式,那么在第一个字节有一个 标示符,叫做BOM用来标志这个文件是用UTF-8来编码的。


package stream
;


import java .io .File ;
import java .io .FileInputStream ;
import java .io .FileNotFoundException ;
import java .io .FileReader ;
import java .io .IOException ;
import java .io .InputStreamReader ;
import java .io .UnsupportedEncodingException ;
import java .nio .charset .Charset ;

public class TestStream {

    public static void main (String [ ] args ) throws UnsupportedEncodingException , FileNotFoundException {
       File f = new File ( "E:\\project\\j2se\\src\\test.txt" ) ;
       System .out . println ( "默认编码方式:" +Charset . defaultCharset ( ) ) ;
        //FileReader得到的是字符,所以一定是已经把字节根据某种编码识别成了字符了
        //而FileReader使用的编码方式是Charset.defaultCharset()的返回值,如果是中文的操作系统,就是GBK
        try (FileReader fr = new FileReader (f ) ) {
           char [ ] cs = new char [ (int ) f . length ( ) ] ;
           fr . read (cs ) ;
           System .out . printf ( "FileReader会使用默认的编码方式%s,识别出来的字符是:%n" ,Charset . defaultCharset ( ) ) ;
           System .out . println ( new String (cs ) ) ;
        } catch (IOException e ) {
            // TODO Auto-generated catch block
           e . printStackTrace ( ) ;
        }
        //FileReader是不能手动设置编码方式的,为了使用其他的编码方式,只能使用InputStreamReader来代替
        //并且使用new InputStreamReader(new FileInputStream(f),Charset.forName("UTF-8")); 这样的形式
        try (InputStreamReader isr = new InputStreamReader ( new FileInputStream (f ) ,Charset . forName ( "UTF-8" ) ) ) {
           char [ ] cs = new char [ (int ) f . length ( ) ] ;
           isr . read (cs ) ;
           System .out . printf ( "InputStreamReader 指定编码方式UTF-8,识别出来的字符是:%n" ) ;
           System .out . println ( new String (cs ) ) ;
        } catch (IOException e ) {
            // TODO Auto-generated catch block
           e . printStackTrace ( ) ;
        }
       
    }
}



  • 练习-数字对应的中文

找出 E5 B1 8C 这3个十六进制对应UTF-8编码的汉字


package stream
;


import java .io .UnsupportedEncodingException ;

public class TestStream {
    public static void main (String [ ] args ) throws UnsupportedEncodingException {
//      找出 E5 B1 8C 这3个十六进制对应UTF-8编码的汉字
       byte [ ] bs = new byte [ 3 ] ;
       bs [ 0 ] = (byte ) 0xE5 ;
       bs [ 1 ] = (byte ) 0xB1 ;
       bs [ 2 ] = (byte ) 0x8C ;
       
       String str   = new String (bs , "UTF-8" ) ;
       System .out . println ( "E5 B1 8C 对应的字符是:" +str ) ;
    }
}


  • 练习-移除BOM

如果用记事本根据UTF-8编码保存汉字就会在最前面生成一段标示符,这个标示符用于表示该文件是使用UTF-8编码的。找出这段标示符对应的十六进制,并且开发一个方法,自动去除这段标示符


package com
.company
;


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

public class utf8Test {
    public static void main ( String [ ] args ) {
       File f = new File ( "d:\\lol.txt" ) ;
        try (FileInputStream fis = new FileInputStream (f ) ) {
           byte [ ] all = new byte [ (int ) f . length ( ) ] ;
           fis . read (all ) ;
           System .out . println ( "首先确认按照UTF-8识别出来有?" ) ;
           String str = new String (all , "UTF-8" ) ;
           System .out . println (str ) ;

           System .out . println ( "根据前面的所学,知道'中'字对应的UTF-8编码是:e4 b8 ad" ) ;
           System .out . println ( "打印出文件里所有的数据的16进制是:" ) ;
            for (byte b : all ) {
               int i = b & 0xff ;
               System .out . println (Integer . toHexString (i ) + " " ) ;
            }
           System .out . println ( ) ;

           System .out . println ( "通过观察法得出 UTF-8的 BOM 是 ef bb bf" ) ;
           byte [ ] bom = new byte [ 3 ] ;
           bom [ 0 ] = (byte ) 0xef ;
           bom [ 1 ] = (byte ) 0xbb ;
           bom [ 2 ] = (byte ) 0xbf ;

           byte [ ] fileContentWithoutBOM = removeBom (all ,bom ) ;
           System .out . println ( "去掉了BOM之后的数据的16进制是:" ) ;
            for (byte b : fileContentWithoutBOM ) {
               int i = b & 0xff ;
               System .out . print (Integer . toHexString (i ) + " " ) ;
            }
           System .out . println ( ) ;
           System .out . println ( "对应的字符串就没有问号了:" ) ;
           String strWithoutBOM = new String (fileContentWithoutBOM , "UTF-8" ) ;
           System .out . println (strWithoutBOM ) ;
        } catch (IOException e ) {
            // TODO Auto-generated catch block
           e . printStackTrace ( ) ;
        }
    }

    private static byte [ ] removeBom ( byte [ ] all , byte [ ] bom ) {
        return Arrays . copyOfRange (all , bom .length , all .length ) ;
    }

}


2.7 缓存流

以介质是硬盘为例, 字节流和字符流的弊端:在每一次读写的时候,都会访问硬盘。 如果读写的频率比较高的时候,其性能表现不佳。

为了解决以上弊端,采用缓存流:缓存流在读取的时候, 会一次性读较多的数据到缓存中,以后每一次的读取,都是在缓存中访问,直到缓存中的数据读取完毕,再到硬盘中读取。

缓存流在写入数据的时候,会先把数据写入到缓存区,直到缓存区 达到一定的量,才把这些数据, 一起写入到硬盘中去。按照这种操作模式,就不会像字节流,字符流那样 每写一个字节都访问硬盘,从而减少了IO操作

  • 使用缓存流读取数据

缓存字符输入流 BufferedReader 可以一次读取一行数据


package com
.company
;


import java .io . * ;
import java .util .Arrays ;

public class utf8Test {
    public static void main ( String [ ] args ) {
       File f = new File ( "d:/lol.txt" ) ;
        // 创建文件字符流
        // 缓存流必须建立在一个存在的流的基础上
        try (
               FileReader fr = new FileReader (f ) ;
               BufferedReader br = new BufferedReader (fr ) ;
                ) {
            while ( true ) {
                // 一次读一行
               String line = br . readLine ( ) ;
                if ( null == line )
                    break ;
               System .out . println (line ) ;
            }
        } catch (IOException e ) {
            // TODO Auto-generated catch block
           e . printStackTrace ( ) ;
        }
    }
}
  • 使用缓存流写出数据

PrintWriter 缓存字符输出流, 可以一次写出一行数据


package stream
;

 
import java .io .File ;
import java .io .FileWriter ;
import java .io .IOException ;
import java .io .PrintWriter ;
 
public class TestStream {
    public static void main ( String [ ] args ) {
        // 向文件lol2.txt中写入三行语句
       File f = new File ( "d:/lol2.txt" ) ;
         
        try (
                // 创建文件字符流
               FileWriter fw = new FileWriter (f ) ;
                // 缓存流必须建立在一个存在的流的基础上              
               PrintWriter pw = new PrintWriter (fw ) ;              
        ) {
           pw . println ( "garen kill teemo" ) ;
           pw . println ( "teemo revive after 1 minutes" ) ;
           pw . println ( "teemo try to garen, but killed again" ) ;
        } catch (IOException e ) {
            // TODO Auto-generated catch block
           e . printStackTrace ( ) ;
        }
    }
}
  • flush

有的时候,需要 立即把数据写入到硬盘,而不是等缓存满了才写出去。 这时候就需要用到flush


package stream
;

   
import java .io .File ;
import java .io .FileWriter ;
import java .io .IOException ;
import java .io .PrintWriter ;
public class TestStream {
    public static void main ( String [ ] args ) {
        //向文件lol2.txt中写入三行语句
       File f = new File ( "d:/lol2.txt" ) ;
        //创建文件字符流
        //缓存流必须建立在一个存在的流的基础上
        try (FileWriter fr = new FileWriter (f ) ;PrintWriter pw = new PrintWriter (fr ) ; ) {
           pw . println ( "garen kill teemo" ) ;
            //强制把缓存中的数据写入硬盘,无论缓存是否已满
               pw . flush ( ) ;          
           pw . println ( "teemo revive after 1 minutes" ) ;
               pw . flush ( ) ;
           pw . println ( "teemo try to garen, but killed again" ) ;
               pw . flush ( ) ;
        } catch (IOException e ) {
            // TODO Auto-generated catch block
           e . printStackTrace ( ) ;
        }
    }
}
  • 练习-移除注释

设计一个方法,用于移除Java文件中的注释

public void removeComments(File javaFile)

比如,移出以//开头的注释行

File f 
= 
new 
File
(
"d:/LOLFolder/LOL.exe"
)
;

System .out . println ( "当前文件是:" +f ) ;
//文件是否存在
System .out . println ( "判断是否存在:" +f . exists ( ) ) ;
//是否是文件夹
System .out . println ( "判断是否是文件夹:" +f . isDirectory ( ) ) ;



package com
.company
;


import java .io . * ;
import java .util .Arrays ;

public class utf8Test {
    public static void main ( String [ ] args ) {
       File f = new File ( "d:/lol.txt" ) ;
        removeComments (f ) ;
    }
    private static void removeComments ( File javaFile ) {
        try (FileReader fr = new FileReader (javaFile ) ;BufferedReader br = new BufferedReader (fr ) ) {
            while ( true ) {
               String line = br . readLine ( ) ;
                if ( null == line )
                    break ;
                if (line . startsWith ( "//" ) )
                    continue ;
                else
                   System .out . println (line ) ;
            }
        } catch (IOException e ) {
           e . printStackTrace ( ) ;
        }
    }
}



2.8 数据流

DataInputStream 数据输入流;DataOutputStream 数据输出流。

  • 直接进行字符串的读写

使用数据流的writeUTF()和readUTF() 可以进行数据的 格式化顺序读写 如本例,通过DataOutputStream 向文件顺序写出 布尔值,整数和字符串。 然后再通过DataInputStream 顺序读入这些数据。

注: 要用DataInputStream 读取一个文件,这个文件必须是由DataOutputStream 写出的,否则会出现EOFException,因为DataOutputStream 在写出的时候会做一些特殊标记,只有DataInputStream 才能成功的读取。


package stream
;

     
import java .io .DataInputStream ;
import java .io .aDataOutputStream ;
import java .io .File ;
import java .io .FileInputStream ;
import java .io .FileOutputStream ;
import java .io .IOException ;
     
public class TestStream {
     
    public static void main ( String [ ] args ) {
        write ( ) ;
        read ( ) ;
    }

    private static void read ( ) {
       File f = new File ( "d:/lol.txt" ) ;
        try (
               FileInputStream fis   = new FileInputStream (f ) ;
               DataInputStream dis = new DataInputStream (fis ) ;
        ) {
           boolean b = dis . readBoolean ( ) ;
           int i = dis . readInt ( ) ;
           String str = dis . readUTF ( ) ;
           
           System .out . println ( "读取到布尔值:" +b ) ;
           System .out . println ( "读取到整数:" +i ) ;
           System .out . println ( "读取到字符串:" +str ) ;

        } catch (IOException e ) {
           e . printStackTrace ( ) ;
        }
       
    }

    private static void write ( ) {
       File f = new File ( "d:/lol.txt" ) ;
        try (
               FileOutputStream fos   = new FileOutputStream (f ) ;
               DataOutputStream dos = new DataOutputStream (fos ) ;
        ) {
           dos . writeBoolean ( true ) ;
           dos . writeInt ( 300 ) ;
           dos . writeUTF ( "123 this is gareen" ) ;
        } catch (IOException e ) {
           e . printStackTrace ( ) ;
        }
       
    }
}


2.9 对象流

对象流指的是可以直接 把一个对象以流的形式传输给其他的介质,比如硬盘。

一个对象以流的形式进行传输,叫做序列化。 该对象所对应的类,必须是实现Serializable接口。

  • 序列化一个对象

创建一个Hero对象,设置其名称为garen。 把该对象序列化到一个文件garen.lol。 然后再通过序列化把该文件转换为一个Hero对象

**注:**把一个对象序列化有一个前提是:这个对象的类,必须实现了Serializable接口

Hero.java


package charactor
;


import java .io .Serializable ;

public class Hero implements Serializable {
    //表示这个类当前的版本,如果有了变化,比如新设计了属性,就应该修改这个版本号
    private static final long serialVersionUID = 1L ;
    public String name ;
    public float hp ;

}

TestStream.java


import java
.io
.File
;

import java .io .FileInputStream ;
import java .io .FileOutputStream ;
import java .io .IOException ;
import java .io .ObjectInputStream ;
import java .io .ObjectOutputStream ;
 
import charactor .Hero ;
   
public class TestStream {
   
    public static void main ( String [ ] args ) {
        //创建一个Hero garen
        //要把Hero对象直接保存在文件上,务必让Hero类实现Serializable接口
       Hero h = new Hero ( ) ;
       h .name = "garen" ;
       h .hp = 616 ;
         
        //准备一个文件用于保存该对象
       File f = new File ( "d:/garen.lol" ) ;
//java学习交流:737251827  进入可领取学习资源及对十年开发经验大佬提问,免费解答!
        try (
            //创建对象输出流
           FileOutputStream fos = new FileOutputStream (f ) ;
           ObjectOutputStream oos = new ObjectOutputStream (fos ) ;
            //创建对象输入流              
           FileInputStream fis = new FileInputStream (f ) ;
           ObjectInputStream ois = new ObjectInputStream (fis ) ;
        ) {
           oos . writeObject (h ) ;
           Hero h2 = (Hero ) ois . readObject ( ) ;
           System .out . println (h2 .name ) ;
           System .out . println (h2 .hp ) ;
             
        } catch (IOException e ) {
            // TODO Auto-generated catch block
           e . printStackTrace ( ) ;
        } catch (ClassNotFoundException e ) {
            // TODO Auto-generated catch block
           e . printStackTrace ( ) ;
        }
           
    }
}


2.10 System.in

System.out 是常用的在控制台输出数据的;System.in 可以从控制台输入数据。

  • System.in


package stream
;


import java .io .IOException ;
import java .io .InputStream ;

public class TestStream {

    public static void main ( String [ ] args ) {
        // 控制台输入
        try (InputStream is = System .in ; ) {
            while ( true ) {
                // 敲入a,然后敲回车可以看到
                // 97 13 10
                // 97是a的ASCII码
                // 13 10分别对应回车换行
               int i = is . read ( ) ;
               System .out . println (i ) ;
            }
        } catch (IOException e ) {
           e . printStackTrace ( ) ;
        }
    }
}
  • Scanner读取字符串


package stream
; 

import java .util .Scanner ;
   
public class TestStream {
    public static void main ( String [ ] args ) {
           Scanner s = new Scanner (System .in ) ;
           
            while ( true ) {
               String line = s . nextLine ( ) ;
               System .out . println (line ) ;
            }
       
    }
}


Scanner从控制台读取整数

    
    package stream
    ;
    

    import java .util .Scanner ;
    public class TestStream {
        public static void main ( String [ ] args ) {
           Scanner s = new Scanner (System .in ) ;
           int a = s . nextInt ( ) ;
           System .out . println ( "第一个整数:" +a ) ;
           int b = s . nextInt ( ) ;
           System .out . println ( "第二个整数:" +b ) ;
        }
    }
    • 2.11 综合练习

    • 复制文件

    复制文件是常见的IO操作,设计如下方法,实现复制源文件srcFile到目标文件destFile

    
    public 
    static 
    void 
    copyFile
    (
    String srcFile
    , String destFile
    )
    {
    
    }
    • 复制文件夹

    复制文件夹,实现如下方法,把源文件夹下所有的文件 复制到目标文件夹下(包括子文件夹)

    
    public 
    static 
    void 
    copyFolder
    (
    
      String srcFolder ,
     String destFolder
    ) {

    }


    • 查找文件内容

    
    public 
    static 
    void 
    search
    (
    
     File folder , String search
    ) ;

    假设你的项目目录是 e:/project,遍历这个目录下所有的java文件(包括子文件夹),找出文件内容包括  Magic的那些文件,并打印出来。

    2.12 流关系图

    • 流关系图

    这个图把本章节学到的流关系做了个简单整理

    1. 流分为字节流和字符流

    2. 字节流下面常用的又有数据流和对象流

    3. 字符流下面常用的又有缓存流




    来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70010294/viewspace-2847203/ ,如需转载,请注明出处,否则将追究法律责任。

    请登录后发表评论 登录
    全部评论

    注册时间:2021-11-19

    • 博文量
      199
    • 访问量
      86401