IO流及字符集字符编码总结

第一章 File类

java.io.File类:文件和文件目录路径的抽象表示形式,与平台无关;file只是事先指定需要进行文件操作的位置。

File 能新建、删除、重命名文件和目录,但 File 不能访问文件内容本身。 如果需要访问文件内容本身,则需要使用输入/输出流。

想要在Java程序中表示一个真实存在的文件或目录,那么必须有一个File对 象,但是Java程序中的一个File对象,可能没有一个真实存在的文件或目录。

File对象可以作为参数传递给流的构造器

1.1 常用构造器

/**
* 以pathname为路径创建File对象,可以是绝对路径或者相对路径,如果 pathname是相对路径,则默认的当前路径在系统属性user.dir中存储
*/
public File(String pathname)

/**
* 以parent为父路径,child为子路径创建File对象
*/
public File(String parent,String child)

/**
* 根据一个父File对象和子文件路径创建File对象
*/
 public File(File parent,String child) 

1.2 路径分隔符

windows中文件路径使用" \ "来分隔

UNIX和URL使用"/"来分隔

Java程序支持跨平台运行,因此路径分隔符要慎用

File使用动态分隔符

如:d:\\atguigu\\info.txt

1.3 常用方法

获取功能
/**
* 获取绝对路径
*/
public File getAbsoluteFile()

/**
* 获取路径 
*/
 public String getPath() 

/**
* 获取名称  
*/
 public String getName() 

/**
* 获取上层文件目录路径。若无,返回null  
*/
public String getParent()

/**
* 获取文件长度(即:字节数)。不能获取目录的长度。  
*/
public long length() 

/**
* 获取最后一次的修改时间,毫秒值
*/
 public long lastModified() 

/**
* 获取指定目录下的所有文件或者文件目录的名称数组 
*/
public String[] list() 

/**
* 获取指定目录下的所有文件或者文件目录的File数组
*/
public File[] listFiles() 
重命名功能
/**
* 把文件重命名为指定的文件路径
*/
public boolean renameTo(File dest)

/**
* 判断是否是文件目录 
*/
public boolean isDirectory()

/**
* 判断是否是文件 
*/
public boolean isFile() 

/**
* 判断是否存在
*/
public boolean exists()

/**
* 判断是否可读
*/
public boolean canRead()

/**
* 判断是否可写
*/
public boolean canWrite()

/**
* 判断是否隐藏
*/
public boolean isHidden()
创建功能
 /**
* 创建文件。若文件存在,则不创建,返回false 
*/
 public boolean createNewFile()
 
 /**
* 创建文件目录。如果此文件目录存在,就不创建了
*/
 public boolean mkdir()
     
 #如果你创建文件或者文件目录没有写盘符路径,那么,默认在项目 路径下
删除功能
 /**
* 删除文件或者文件夹 
* 要删除一个文件目录,请注意该文件目录内不能包含文件或者文件目录
*/
public boolean delete()

示例

//file只是抽象的指定文件或文件夹的位置
File file = new File("C:\\Users\\Administrator\\Desktop\\abc\\1.txt");
//如果该路径下该文件存在,返回false,没有就创建
file.createNewFile();
File file1 = new File("C:\\Users\\Administrator\\Desktop\\abc\\child");
//如果该路下文件夹不存在,则创建
file1.mkdir();

判断指定目录下是否有后缀名为.jpg的文件,如果有,就输出该文件名称

    @Test
    public void test1(){
        File file = new File("D:\\Users");
        String[] list = file.list();
        for (String s : list) {
            if (s.endsWith(".jpg"))
                System.out.println(s);
        }
    }

    @Test
    public void test2(){
        File file = new File("D:\\Users");
        File[] files = file.listFiles();
        for (File file1 : files) {
            if (file1.getName().endsWith("jpg"))
                System.out.println(file.getName());
        }
    }

    @Test
    public void test3(){
        File file = new File("D:\\Users");
        //文件过滤器
        File[] files = file.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return name.endsWith(".jpg");
            }
        }) ;
        for (File file1 : files) {
                System.out.println(file1.getName());
        }
    }

遍历指定目录所有文件名称,包括子文件目录中的文件

@Test
public void test4(){
    File file = new File("D:\\cc");
}
public static void printSubFile(File dir){
    //列出该目录下所有文件和目录
    File[] files = dir.listFiles();
    for (File file : files) {
        if (file.isDirectory()){//判断是否为文件
            printSubFile(file);
        }else{
            System.out.println(file.getName());
        }
    }
}

求指定目录所在空间的大小

public static long getDiSize(File file){
    long size=0;
    if (file.isFile()){
        size=file.length()+size;
    }else{
        File[] files = file.listFiles();
        for (File file1 : files) {
            size+=getDiSize(file1);
        }
    }
    return size;
}

删除指定的目录

public static void delDi(File file){
    if (file.isDirectory()){
        File[] files = file.listFiles();
        for (File file1 : files) {
            delDi(file);
        }
    }
    file.delete();
}

第二章 IO流

2.1 Java IO原理

I/O是Input/Output的缩写, I/O技术是非常实用的技术,用于处理设备之间的数据传输。如读/写文件,网络通讯等。

文件都是以二进制的形式保存在电脑里,读取和写入都是二进制的流出和流入。

Java程序中,对于数据的输入/输出操作以“流(stream)” 的方式进行。

输入input:读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中。

输出output:将程序(内存) 数据输出到磁盘、光盘等存储设备中。

2.2 字符编码和字符集

首先明确**“字节(Byte)”“字符(Character)”**的大小:

  • 1 byte = 8 bit
  • 1 char = 2 byte = 16 bit
字符编码

按照A规则存储,同时按照A规则解析,那么就能显示正确的文本符号。反之,按照A规则存储,再按照B规则解析,就会导致乱码现象。

编码:字符(能看懂)–>字节(看不懂)

解码:字节(看不懂)–>字符(能看懂)

  • **字符编码:**就是一套自然语言的字符与二进制数之间的对应规则。

    编码表:生活中文字和计算机中二进制的对应规则。

字符集
  • **字符集:**也叫编码表。是一个系统支持的所有字符的集合,包括各国家文字、符号、图形符号等。

计算机要准确的存储和识别各种字符集符号,需要进行字符编码,一套字符集必然至少有一套字符编码。当指定了编码,它所对应的字符集自然就指定了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yghhZXIX-1602227013616)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20201007214743796.png)]

其实,上诉介绍看完,我也还是不能理解什么是字符集,什么是字符编码,下面就抽象加比喻的方式来讲解。

编码里最容易搞混的一件事就是:Unicode只是一套符号的编码。但计算机具体怎么读取这套编码,又是另外一件事。

如汉字“严”,根据查表,其Unicode码是十六进制数4E25,转换成二进制数足足有15位(100111000100101),也就是说,这个符号至少需要两个字节来存储;那么问题就来了,当存储“严a严”这组词时,其二进制就是100111000100101 0100001 100111000100101,计算机在读取这一串二进制时,怎么知道哪里到哪里是一个字符,就像“从”,计算机怎么知道这个字符是两个“人人”还是一个“从”呢,因此,你要将这个二进制变换一下,按照某种规则添加一些东西,读到这个规则时,计算机就知道这个字符还没读完,还要继续读才是一个完整字符,由于各个国家都有自己的想法,因此就出现了Ascii编码、GBK编码、UTF8编码等等。

为什么使用unicode字符集呢,因为最早出现的是美国的ASCII,西方世界只有26个英文字母,英文字母又都是一个字节,因此根本就用不上字符;但是欧洲不行呀,欧洲那么多国家,那么多种语言,因此欧洲也开发了一套自己的字符集ISO-8859-1,这个字符集是兼容ASCII的;后来中国也做了一套自己的字符集来收录GBK,GBK也是兼容欧洲那一套的,这样,为了计算机信息的交流互通无阻,就制定了一套包含全世界的字符集Unicode,Unicode兼容美国、欧洲和中国的;因此使用Unicode也可以当作使用前面的几个字符集。虽然是兼容,但是计算机读取的规则大家还是只认可自己那一套,因此就有了多种字符编码。

而Java呢,统一使用Unicode进行存储,就是这个“4E25”形式,这样就保证了,只要我存储的Unicode是正确的,那么不管以那种编码写出去都是可以的;再将java源文件编译成.class文件时,就将字符以Unicode的形式保存在.class文件中,引用优秀博客来解释:

在Java(其中主要包括在JVM中、内存中、在代码里声明的每一个char、String类型的变量中。)中字符只以一种形式存在,那就是Unicode,不选择任何特定的编码,直接使用它们在字符集中的编号,这是统一的唯一的方法。
在JVM内部,统一使用Unicode表示,当着字符从JVM内部移动到外部时(即保存为文件系统中的一个文件内容时),就进行了编码转换,使用了具体的编码方案。因此也可以说,所有的编码转换只发生在边界的地方,也就是各种输入/输出流的起作用的地方。

这个对编码讲的更加的清楚https://www.zhihu.com/question/39262026

这里有一个例子,windows系统中有一个记事本,里面有中文和英文,windows系统默认采用的是GBK的字符编码,在记事本中写字时,先将文字在unicode字符集中找到对应的编码,再将该二进制以GBK规则保存在电脑的磁盘中;java使用字符流读取时,使用GBk规则读取出来,以Unicode形式保存在内存中,然后再以UTF8规则进行写出。

2.3 流的分类

按操作数据单位不同分为:字节流(8 bit),字符流(16 bit)

按数据流的流向不同分为:输入流,输出流 =

按流的角色的不同分为:节点流,处理流

(抽象基类)字节流字符流
输入流InputStreamReader
输出流OutputStreamWriter

节点流:直接从数据源或目的地读写数据

处理流:不直接连接到数据源或目的地,而是“连接”在已存 在的流(节点流或处理流)之上,通过对数据的处理为程序提 供更为强大的读写功能。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9lbqCTbG-1602227013620)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200928214030472.png)]

InputStream

/**
* 从输入流中读取数据的下一个字节。返回 0 到 255 范围内的 int 字节值。如果因 为已经到达流末尾而没有可用的字节,则返回值 -1。
*/
int read() 

/**
* 从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。如果因为已 经到达流末尾而没有可用的字节,则返回值 -1。否则以整数形式返回实际读取 的字节数。
*/
int read(byte[] b)

/**
* 将输入流中最多 len 个数据字节读入 byte 数组。尝试读取 len 个字节,但读取 的字节也可能小于该值。以整数形式返回实际读取的字节数。如果因为流位于 文件末尾而没有可用的字节,则返回值 -1。
*/
int read(byte[] b, int off,int len) 

/**
* 关闭此输入流并释放与该流关联的所有系统资源
*/
public void close() throws IOException 

Reader

/**
* 读取单个字符。作为整数读取的字符,范围在 0 到 65535 之间 (0x00-0xffff)(2个 字节的Unicode码),如果已到达流的末尾,则返回 -1 
*/
int read() 

/**
* 将字符读入数组。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数。 
*/
int read(char[] b)

/**
* 将字符读入数组的某一部分。存到数组cbuf中,从off处开始存储,最多读len个字 符。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数。 
*/
int read((char[] cbuf,int off,int len) 

/**
* 关闭此输入流并释放与该流关联的所有系统资源
*/
public void close() throws IOException 

OutputStream

/**
* 将指定的字节写入此输出流。write 的常规协定是:向输出流写入一个字节。要写 入的字节是参数 b 的八个低位。b 的 24 个高位将被忽略。 即写入0~255范围的。 
*/
void write(int b)

/**
* 将 b.length 个字节从指定的 byte 数组写入此输出流。write(b) 的常规协定是:应该 与调用 write(b, 0, b.length) 的效果完全相同。 
*/
void write(byte[] b)

/**
* 将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。  
*/
 void write(byte[] b,int off,int len)

/**
* 刷新此输出流并强制写出所有缓冲的输出字节,调用此方法指示应将这些字节立 即写入它们预期的目标。
*/
 public void flush()throws IOException 
    
/**
* 关闭此输出流并释放与该流关联的所有系统资源
*/
 public void close() throws IOException 

Writer

/**
* 写入单个字符。要写入的字符包含在给定整数值的 16 个低位中,16 高位被忽略。 即 写入0 到 65535 之间的Unicode码。 
void write(int b)

/**
* 写入字符数组。 
*/
void write(char[] cbuf)

/**
* 写入字符数组的某一部分。从off开始,写入len个字符 
*/
 void write(char[] cbuf,int off,int len)

/**
* 写入字符串
*/
void write(String str) 

/**
* 刷新该流的缓冲,则立即将它们写入预期目标。 
*/
void flush() 
    
/**
* 关闭此输出流并释放与该流关联的所有系统资源
*/
 public void close() throws IOException 
节点流(或文件流)

读取文件

//建立一个流对象,将已存在的一个文件加载进流。
FileReader fr = new FileReader(new File(“Test.txt”));
//创建一个临时存放数据的数组。
char[] ch = new char[1024];
//调用流对象的读取方法将流中的数据读入到数组中。
fr.read(ch);
//关闭资源。
fr.close();

写入文件

//创建流对象,建立数据存放文件
FileWriter fw = new FileWriter(new File(“Test.txt”));
//调用流对象的写入方法,将数据写入流 
fw.write(“atguigu-songhongkang”);
//关闭流资源,并将流中的数据清空到文件中。
fw.close();

注意

定义文件路径时,注意:可以用“/”或者“\”。

在写入一个文件时,如果使用构造器FileOutputStream(file),则目录下有同名文件将被覆盖。

如果使用构造器FileOutputStream(file,true),则目录下的同名文件不会被覆盖, 在文件内容末尾追加内容。

在读取文件时,必须保证该文件已存在,否则报异常。

字节流操作字节,比如:.mp3,.avi,.rmvb,mp4,.jpg,.doc,.ppt

字符流操作字符,只能操作普通文本文件。最常见的文本文 件:.txt,.java,.c,.cpp 等语言的源代码。尤其注意.doc,excel,ppt这些不是文本文件。

字节流和字符流的区别?

实际上字节流在操作时本身不会用到缓冲区(内存),字节流是直接操作文件本身;而字符流在操作时使用了缓冲区,通过缓冲区再操作文件,如下图所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-noJcpY1P-1602227013622)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20201008234057462.png)]

字符流:二进制数据 --编码-> 字符编码表 --解码-> 二进制数据
字节流:二进制数据 —> 二进制数据

  • 字节流是直接操作二进制数据

  • 字符流需要将二进制数据在缓冲区转为Unicode,然后再输出

知道了区别,下面的问题就很好解释了

以字节流读取字符出现乱码的原因?

以字节读取不一定会出现乱码

当文件存储是以GBK格式,用字节流写时,若复制的文件也是以GBK解码,则能正确读取

当文件存储以GBk格式,将数据复制到另一个文件,而另一个文件是以UTF8格式进行解码的,则会乱码。

理解

你以什么样的字符编码保存的,就需要以什么样的字符编码进行解码。

比如你在windows中通过IO流进行文件的复制,使用字节流将二进制保存到另一个地方,但是还是以GBK进行解码,因此不会乱码;但是,如果你读取文件到java内存中,要输出到控制台,就会乱码,因为读取到内存后,你要输出到控制台,idea是以UTF8的编码方式,你以UTF8解码GBK的编码,肯定会出现乱码的。

为什么不能使用字符流读取图片?

字符流:二进制数据 --编码-> 字符编码表 --解码-> 二进制数据
字节流:二进制数据 —> 二进制数据

字符流多了一步数据编码和解码,所以图片二进制数据性质就变了。

为什么字符流读中文就不会乱码?

这个说法不准确,字节流也可以不出现乱码,只是字节流需要保证编码和解码都是同一种规则才会不出现乱码;而字符流只需要保证解码时为正确的规则就能保证不会出现乱码。

字符流做的事,是将字符以Unicode保存在java内存中,这样不管在哪输出,都可以保证不会乱码。

示例

/**
* 利用字节流实现复制文件
*/
public void ReadToWriter(){
    FileReader fr = null;
    FileWriter fw = null;
    try {
        //1.创建File类对象,指明读入和写入的文件
        File srcFile = new File("C:\\Users\\Administrator\\Desktop\\1.txt");
        File desFile = new File("C:\\Users\\Administrator\\Desktop\\12.txt");

        //2.创建输入流和输出流
        fr = new FileReader(srcFile);
        fw = new FileWriter(desFile);

        //3.数据的读入和写出操作
        //创建存储读取字节的数组
        char[] cbuf = new char[5];
        int len;//记录每次读入cbuf数组中的字符个数
        while((len = fr.read(cbuf))!= -1){
            fw.write(cbuf,0,len);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        //4.关闭流资源
        try {
            fr.close();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                fw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

注意:

使用字符流来处理图片是不行的,图片是二进制的字节,二字符流只是用来处理字符的,因此不能使用字符流来处理图片、视频等字节数据;需要使用字符流来实现。

同样,字节流不能用来处理字符相关的文件(有中文的情况下),一个中文一般占三个字节,字节是一个一个读的

处理流之一:缓冲流

当读取数据时,数据按块读入缓冲区,其后的读操作则直接访问缓冲区

当使用BufferedInputStream读取字节文件时,BufferedInputStream会一次性从 文件中读取8192个(8Kb),存在缓冲区中,直到缓冲区装满了,才重新从文件中 读取下一个8192个字节数组。

向流中写入字节时,不会直接写到文件,先写到缓冲区中直到缓冲区写满, BufferedOutputStream才会把缓冲区中的数据一次性写到文件里。使用方法 flush()可以强制将缓冲区的内容全部写入输出流

关闭流的顺序和打开流的顺序相反。只要关闭最外层流即可,关闭最外层流也 会相应关闭内层节点流

flush()方法的使用:手动将buffer中内容写入文件

如果是带缓冲区的流对象的close()方法,不但会关闭流,还会在关闭流之前刷 新缓冲区,关闭后不能再写出

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4tkCeLG6-1602227013626)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200928231329903.png)]

示例

public void bufferedTest() throws IOException {
    FileInputStream fs = null;
    FileOutputStream fw = null;
    BufferedInputStream bs = null;
    BufferedOutputStream bo = null;
    try {
        //1.创建文件对象
        File srcFile = new File("C:\\Users\\Administrator\\Desktop\\84026087_p0.png");
        File destFile = new File("C:\\Users\\Administrator\\Desktop\\leng.png");
        //2.造流
        //2.1创建字节流
        fs = new FileInputStream(srcFile);
        fw = new FileOutputStream(destFile);
        //2.2创建缓冲流
        bs = new BufferedInputStream(fs);
        bo = new BufferedOutputStream(fw);

        //3.对文件进行读写操作
        byte[]  buffer = new byte[10];
        int len;
        while ((len = bs.read(buffer))!=-1){
            bo.write(buffer,0,len);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        //4.关闭资源
        //重外往里关
        if (bs!=null){
            try {
                bs.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (bo!=null){
            try {
                bo.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        //关闭外层的同时,内层也会自动关闭
//            fs.close();
//            fw.close();
    }
}
/**
 * 计算字符个数
 */
@Test
public void test6(){
    FileReader fr = null;
    BufferedWriter bw = null;
    try {
        Map<Character,Integer> map = new HashMap<>();
        fr = new FileReader(new File("C:\\Users\\Administrator\\Desktop\\ae_az.txt"));

        byte[] buffer = new byte[1024];
        int len;
        while((len = fr.read()) !=-1){
            char ch = (char) len;
            if (map.get(ch) ==null){
                map.put(ch,1);
            }else{
                map.put(ch,map.get(ch)+1);
            }
        }

        //把Map数据写入文件中
        bw = new BufferedWriter(new FileWriter("1.txt"));

        Set<Map.Entry<Character,Integer>> entrySet = map.entrySet();
        for (Map.Entry<Character, Integer> entry : entrySet) {
            switch (entry.getKey()){
                case ' ':
                    bw.write("空格="+ entry.getValue());
                    break;
                case '\t':
                    bw.write("tab键="+entry.getValue());
                    break;
                case '\r':
                    bw.write("回车="+entry.getValue());
                    break;
                case '\n':
                    bw.write("换行="+entry.getValue());
                    break;
                default:
                    bw.write(entry.getKey()+"="+entry.getValue());
                    break;
            }
            bw.newLine();
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (bw!=null){
            try {
                bw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (fr!=null){
            try {
                fr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

节点流与缓冲流对比

基本作用就是提高文件的读写效率,一般都不会用基本的节点流

为什么说缓冲流比文件流高效?

文件流的一般步骤:CPU一次几个字节的往内存里面输入,然后再将这几个字节往文件里面写

缓冲流的一般步骤:DMA一次几个字节的往缓冲流里写,当缓冲流写满时,一次性往文件里写。

CPU调度的资源是有限的,CPU将IO操作交给DMA来处理,自己就负责其他方面的调度,DMA;当需要执行IO操作时,CPU执行完准备工作,将数据交互的工作交给DMA去处理,自己去执行其他任务,当DMA处理完成时,会通知CPU来处理后续的事情;缓冲区也是内存的一块区域,当把这个区域都堆满时,才执行写入操作,相当于数据库的批量操作,达到提升效率的作用。

总结:使用缓冲处理流包装就是一堆一堆的干活,还能不用CPU多次处理数据转换,只是设置一下数据转换成功后的文件。不使用缓冲处理流包装就是CPU傻傻的一个字节一个字节循环来干活存储写入文件中,相比可见效率明显变慢。

示例

/**
 * 对图片进行加密
 */
public void enCode(){
    FileInputStream fis = null;
    FileOutputStream fos = null;
    try {
        fis = new FileInputStream(new File("C:\\Users\\Administrator\\Desktop\\怎么学.png"));
        fos = new FileOutputStream(new File("1.png"));

        byte[] buffer = new byte[1024];
        int len;
        while ((len = fis.read(buffer)) !=-1){
            //对字节数组进行修改,加密
            for (int i = 0; i < len; i++) {
                buffer[i] = (byte) (buffer[i] ^ 5);
            }
            fos.write(buffer);
        }
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (fis!=null){
            try {
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (fos!=null){
            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
处理流之二:转换流

转换流

转换流提供了在字节流和字符流之间的转换

Java API提供了两个转换流:

​ InputStreamReader:将InputStream转换为Reader

​ OutputStreamWriter:将Writer转换为OutputStream

字节流中的数据都是字符时,转成字符流操作更高效。

很多时候我们使用转换流来处理文件乱码问题。实现编码和 解码的功能

 public InputStreamReader(InputStream in) 
 public InputSreamReader(InputStream in,String charsetName) 
 如: Reader isr = new InputStreamReader(System.in,”gbk”);

public OutputStreamWriter(OutputStream out) 
public OutputSreamWriter(OutputStream out,String charsetName)

FileInputStream fis = new FileInputStream("1.txt");
InputStreamReader isr = new InputStreamReader(fis,"utf-8");
//以字节流读,再将字节流转为对应格式的字符流

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x1dzWb2T-1602227013630)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200929223417385.png)]

为什么要转换一下,而不是直接使用字符流来读取?

FileReader默认使用"GBK"进行解码

按指定格式读文

public static void main(String[] args) throws IOException {
      //在IO流中,如果想指定编码读写数据,只能使用转换流。
      //采用指定编码从文本文件中读取内容
      BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("C:\\a.txt"), "UTF-8"));
      String line = null;
      while ((line=br.readLine())!=null) {
          System.out.println(line);
      }
      br.close();
}

字符编码的理解

https://blog.csdn.net/u013905744/article/details/51923671

处理流之三:数据流

为了方便地操作Java语言的基本数据类型和String的数据,可以使用数据流。

数据流有两个类:(用于读取和写出基本数据类型、String类的数据)

DataInputStream 和 DataOutputStream

分别“套接”在 InputStream 和 OutputStream 子类的流上

DataInputStream中的方法

boolean readBoolean() 
byte readByte() 
char readChar() 
float readFloat() 
double readDouble() 
short readShort() 
long readLong() 
int readInt() 
String readUTF()
void readFully(byte[] b)

DataOutputStream中的方法

将上述的方法的read改为相应的write即可。

示例

DataOutputStream dos = null;
try {
    // 创建连接到指定文件的数据输出流对象
    dos = new DataOutputStream(new FileOutputStream("destData.dat"));
    dos.writeUTF("我爱北京天安门"); // 写UTF字符串
    dos.writeBoolean(false); // 写入布尔值
    dos.writeLong(1234567890L); // 写入长整数
    System.out.println("写文件成功!"); 
} catch (IOException e) {
    e.printStackTrace();
} finally {
    // 关闭流对象 
    try {
        if (dos != null) {
        	// 关闭过滤流时,会自动关闭它包装的底层节点流
        	dos.close(); 
        }
    } catch (IOException e) {
    	e.printStackTrace(); 
    } 
}
处理流之四:对象流

ObjectInputStream和OjbectOutputSteam

用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。

序列化:用ObjectOutputStream类保存基本类型数据或对象的机制

反序列化:用ObjectInputStream类读取基本类型数据或对象的机制

ObjectOutputStream和ObjectInputStream不能序列化static和transient修 饰的成员变量

对象的序列化

java的"对象序列化"能让你将一个实现了Serializable接口的对象转换成一组byte,这样日后要用这个对象时候,你就能把这些byte数据恢复出来,并据此重新构建那个对象了。这一点甚至在跨网络的环境下也是如此,这就意味着序列化机制能自动补偿操作系统方面的差异。也就是说,你可以在Windows机器上创键一个对象,序列化之后,再通过网络传到Unix机器上,然后在那里进行重建。你不用担心在不同的平台上数据是怎样表示的,byte顺序怎样,或者别的什么细节。

之所以要在语言里加入对象序列化是因为要用它来实现两个重要的功能。Java的远程方法调用(Remote Method Invocation简称RMI)能让你像调用自己机器上的对象那样去调用其它机器上的对象。当你向远程对象传递消息的时候,就需通过对象序列化来传送参数和返回值了。RMI会在Thinking in Enterprise Java作讨论。

如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一。 否则,会抛出NotSerializableException异常

凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量:

​ private static final long serialVersionUID;

​ serialVersionUID用来表明类的不同版本间的兼容性。简言之,其目的是以序列化对象 进行版本控制,有关各版本反序列化时是否兼容。

​ 如果类没有显示定义这个静态常量,它的值是Java运行时环境根据类的内部细节自 动生成的。若类的实例变量做了修改, serialVersionUID 可能发生变化。故建议, 显式声明

简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验 证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的 serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同 就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异 常。(InvalidCastException)

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(“data.txt")); 
Person p = new Person("韩梅梅", 18, "中华大街", new Pet()); 
oos.writeObject(p);
oos.flush(); 
oos.close();


ObjectInputStream ois = new ObjectInputStream(new FileInputStream(“data.txt")); 
Person p1 = (Person)ois.readObject(); 
System.out.println(p1.toString()); 
ois.close();

1.为什么要实现Serializable

(1)将内存中的对象数据存在磁盘中持久化

(2)将对象从一个应用程序发送到另一个应用程序

实现Serializable接口可以把对象序列化为字节流,实现传输必须实现序列化。

使用时将字节流反序列化为对象,应用其创建的副本。

2.为什么网络传输时对象要序列化,而字符串就不用序列化

网络传输需要将对象转换成字节流传输,序列化可以将一个对象转化成一段字节编码,以便在网络上传输或者做存储处理,使用时再进行反序列;

而字符串不用序列化的原因是字符串String是已经实现了Serializable接口的,所以它已经是序列化了的。而JSON是一个有特殊规则的字符串。

如果使用dubbo这种RPC(Remote Procedure Call)框架那么使用面向接口编程的时候,接口中的参数如果是类那么该类就必须implements Serializable才可以让对象在远程调用的时候在网络上传输。

3.javabean为什么不implements Serializable也可以保存到数据库中

RandomAccessFile 类

RandomAccessFile 声明在java.io包下,但直接继承于java.lang.Object类。并 且它实现了DataInput、DataOutput这两个接口,也就意味着这个类既可以读也 可以写。

RandomAccessFile 类支持 “随机访问” 的方式,程序可以直接跳到文件的任意 地方来读、写文件

​ 支持只访问文件的部分内容

​ 可以向已存在的文件后追加内容

RandomAccessFile 对象包含一个记录指针,用以标示当前读写处的位置。 RandomAccessFile 类对象可以自由移动记录指针:

​ long getFilePointer():获取文件记录指针的当前位置

​ void seek(long pos):将文件记录指针定位到 pos 位置

创建 RandomAccessFile 类实例需要指定一个 mode 参数,该参数指 定 RandomAccessFile 的访问模式:

r: 以只读方式打开 
rw:打开以便读取和写入 
rwd:打开以便读取和写入;同步文件内容的更新 
rws:打开以便读取和写入;同步文件内容和元数据的更新 

如果模式为只读r。则不会创建文件,而是会去读取一个已经存在的文件, 如果读取的文件不存在则会出现异常。 如果模式为rw读写。如果文件不 存在则会去创建文件,如果存在则不会创建

作用

可以用RandomAccessFile这个类,来实现一个多线程断点下载的功能, 用过下载工具的朋友们都知道,下载前都会建立两个临时文件,一个是与 被下载文件大小相同的空文件,另一个是记录文件指针的位置文件,每次 暂停的时候,都会保存上一次的指针,然后断点下载的时候,会继续从上 一次的地方下载,从而实现断点下载或上传的功能

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值