首页 > 编程语言 >Java IO流(Stream)

Java IO流(Stream)

时间:2022-09-21 14:56:10浏览次数:96  
标签:文件 Java 字节 Stream int File IOException IO new

1. Stream 流

一个流可以理解为一个数据的序列。输入流表示从一个源读取数据,输出流表示向一个目标写数据。

Stream可以定义为数据序列。 有两种流-

  • InPutStream     - InputStream用于从源读取数据。

  • OutPutStream  - OutputStream用于将数据写入目标。

Streams

 

Java.io 包几乎包含了所有操作输入、输出需要的类。所有这些流类代表了输入源和输出目标。

流的分类:

1.按方向:

1.1输入流:将<存储设备>中的内容读入到<内存>中

1.2输出流:将<内存>中的内容写入到<存储设备>中

2按单位:

2.1字节流:以字节为单位,可以读写所有数据

2.2字符流:以字符为单位,只能读写文本数据

3.按功能

3.1节点流:具有实际传输数据的读写功能

3.2过滤流:在节点流的基础之上增强功能

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

 

 

2. IO流分为字节流和字符流

字节流、字符流处理数据的单位:

  • 字节流:一次读或写1字节的数据,也就是8位二进制,常见的有:视频、音频、图片、PDF、Excel
  • 字符流:一次读或写1个字符,也就是至少1字节的数据(因为汉字是有占2-3字节的),也就是大于8位的二进制。常见的有文本.txt

两者的区别:

  • 字节流和字符流操作的本质区别只有一个:字节流是原生的操作,字符流是经过处理后的操作。
  • 字节流和字符流的区别在于数据处理单位的不同。一个是1字节一个2字节。

 

2.1 字节输入流

InputStream这个类是表示输入字节流的所有类的超类,掌握这个就基本掌握其他类了。

输入流可划分为 :节点流和包装流。

节点流:我可以向某一个特定的地方(也称作为节点)读取内部数据。

包装流也叫做(处理流),简单的说,包装流就是用来包装节点流的。作用就是对节点流进行包装处理,增加一些功能,是一种典型的装饰器设计模式。

处理流:它也叫做高级流或包装流,处理流对一个已存在的流进行连接,通过封装后的流来进行读写。

 

节点流的常用类:

FileInputStream、ByteArrayInputStream基本都是父类的方法,只是某些使用场景略有不同。

文件:FileInputStream,读取文件内容的适合使用。

字节数组:ByteArrayInputStream,读取字节数组的时候使用。

包装流的常用类:

SequenceInputStream:序列流。

ObjectInputStream:对象反序列化流。

什么是序列化、反序列化?

序列化:将Java对象保存到硬盘上。

反序列化:从硬盘上转化成Java对象。

 

BufferedInputStream:缓冲输入流。

这个缓冲流为另一个输入流添加了功能,即缓冲输入和支持mark和reset方法的功能。

对于输出地缓冲流,写出的数据,会先写入到内存中,再使用flush方法将内存中的数据刷到硬盘。所以,在使用字符缓冲流的时候,一定要先flush,然后再close,避免数据丢失。

DataInoutStream:数据流。

它提供了可以直接传输功能,基本数据类型的功能,不需要提前转换,可以直接读取到数据内容。

使用数据输入流“包装”“文件输入流”获取数据,可以直接读取到指定的类型数据。

2.2 字符流

和上面的字节流差不多,但是它使用的常见不太一样。

输入流(Reader)

节点流:

在字符流中,需要注意的是,没有字节数组流(ByteArrayInputStream)只有字符数组流(CharArrayReader)

FileReader是节点流,但是它的父类InputStreamReader是包装流

InputStreamReader:字符输入流(字节流到字符流的桥梁)

FileReader;文件流:在文件字符输入读取时使用。

CharArrayReader:字符数组流;用于字符数组的读取。

包装流:

StringReader:字符串流;读取字符串时使用。

InputStreamReader:字符输入流(字节流到字符流的桥梁)

输出流(Writer)

FileWriter是节点流,父类OutputStreamWriter是包装流。

BufferedWriter:将对象的格式表示打印到文本输出流。(缓冲流)

PrintWriter:将对象的格式表示打印到文本输出流。和PrintStream类似,但是他不会自动刷新

StringWriter:在字符串缓冲区中收集其输出的字符流,然后可以用于构造字符串。(读取字符串使用)

OutputStreamWriter:字符输出流(字节流到字符流的桥梁)

FileWriter:文件字符输写出。

3. 输入流和输出流的类层次图

1. Java的IO流共涉及40多个类,实际上非常规则,都是从如下4个 抽象基类派生的。
2. 由这四个类派生出来的子类名称都是以其父类名作为子类名后缀。

https://www.cnblogs.com/skywang12345/p/io_01.html这篇中有详细的展开,下面文章中将来看不懂,去这里查找下。

 

 

 

 

IO流的索引头 (File)

 File文件类,因为io的引介都是围绕着文件的,所有File文件类就是必不可少的了。

 File类在别的随笔中。

 

4. 节点流(文件流)

  • 定义文件路径时,注意:可以用“/”或者“\”。
  • 在写入一个文件时,如果使用构造器FileOutputStream(file),则目录下有同名文件将被覆盖。
  • 如果使用构造器FileOutputStream(file,true),则目录下的同名文件不会被覆盖,在文件内容末尾追加内容。
  • 在读取文件时,必须保证该文件已存在,否则报异常。
  • 字节流操作字节,比如:.mp3,.avi,.rmvb,mp4,.jpg,.doc,.ppt
  • 字符流操作字符,只能操作普通文本文件。最常见的文本文件:.txt,.java,.c,.cpp 等语言的源代码。尤其注意.doc,excel,ppt这些不是文本文件。

 

4.1 FileReader/FileWriter(字符流)

FileReader

在给定从中读取数据的 File 的情况下创建一个新 FileReader。
FileReader(File file)
在给定从中读取数据的 FileDescriptor 的情况下创建一个新 FileReader。 FileReader(FileDescriptor fd)
在给定从中读取数据的文件名的情况下创建一个新 FileReader。 FileReader(String fileName)

 

FileReader常用方法

int read():读取单个字符。作为整数读取的字符,范围在 0 到 65535 之间 (0x00-0xffff)(2个字节的Unicode码),如果已到达流的末尾,则返回 -1
int read(char[] cbuf):将字符读入数组。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数。
int read(char[] cbuf,int off,int len):将字符读入数组的某一部分。存到数组cbuf中,从off处开始存储,最多读len个字符。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数。
public void close() throws IOException:关闭此输入流并释放与该流关联的所有系统资源。

 

 基本用法
几乎所有的I/O类中的方法都会抛出异常 ;java.io.IOException。因此,必须在方法中声明会抛出:java.io.IOException 异常,或者将代码放到 try-catch 块中。

 //将module下的hello.txt文件内容读入程序中,并输出到控制台
 @Test
    public void test1() throws IOException {
        FileReader fr = null;
        try {
            //1.File类的实例化
            File file = new File("hello.txt");
            
            //2.FileReader流的实例化
            fr = new FileReader(file);
            
            //3.读入的操作 使用read(char[] cbuf)
            char[] cbuf = new char[5];
            int len;
            while ((len = fr.read(cbuf)) != -1) {
                //方法一
                // for (int i = 0; i < len; i++) {//不能用cbuf.length
                //     System.out.print(cbuf[i]);
                // }
                //方法二
                String s = new String(cbuf, 0, len);
                System.out.print(s);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fr != null) {
                try {
                    //4.资源的关闭
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
 

FileWriter

在给出 File 对象的情况下构造一个 FileWriter 对象。
FileWriter(File file)
在给出 File 对象的情况下构造一个 FileWriter 对象。 FileWriter(File file, boolean append) 参数:
file:要写入数据的 File 对象。
append:如果 append 参数为 true,则将字节写入文件末尾处,相当于追加信息。如果 append 参数为 false, 则写入文件开始处。
构造与某个文件描述符相关联的 FileWriter 对象。 FileWriter(FileDescriptor fd)
在给出文件名的情况下构造 FileWriter 对象,它具有指示是否挂起写入数据的 boolean 值。 FileWriter(String fileName, boolean append)

 

FileWriter常用方法

void write(int c):写入单个字符。要写入的字符包含在给定整数值的 16 个低位中,16 高位被忽略。 即写入0 到 65535 之间的Unicode码。
void write(char[] cbuf):写入字符数组。
void write(char[] cbuf,int off,int len):写入字符数组的某一部分。从off开始,写入len个字符写入字符串。
void write(String str,int off,int len):写入字符串的某一部分。
void flush():刷新该流的缓冲,则立即将它们写入预期目标。
public void close()throws IOException :关闭此输出流并释放与该流关联的所有系统资源


基本用法
输出操作,对应的File可以不存在的。并不会报异常
File对应的硬盘中的文件如果不存在,在输出的过程中,会自动创建此文件。
File对应的硬盘中的文件如果存在:
① 如果流使用的构造器是:FileWriter(file,false) / FileWriter(file):对原有文件的覆盖
② 如果流使用的构造器是:FileWriter(file,true):不会对原有文件覆盖,而是在原有文件基础上追加内容

//从内存中写出数据到硬盘的文件里
@Test
    public void test2() {
        FileWriter fw = null;
        try {
            //1.创建File类的对象,指明写出的文件
            File file = new File("hello1.txt");

            //2.提供FileWrite的对象,用于数据的写出
            fw = new FileWriter(file);

            //3.写出的操作
            fw.write("I have a dream!\n");
            fw.write("you need to have a dream!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fw != null) {
                //4.关闭资源
                try {
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

 

 @Test
    public void test3() {
        FileReader fr = null;
        FileWriter fw = null;
        try {
            //1.创建File类的对象,指明读入与写出的文件
            File srcFile = new File("hello.txt");
            File destFile = new File("hello2.txt");

            //2.創建输入流与输出流的对象
            fr = new FileReader(srcFile);
            fw = new FileWriter(destFile);

            //3.数据的读入与写出的操作
            char[] cbuf = new char[5];
            int len;
            while ((len = fr.read(cbuf)) != -1) {
                fw.write(cbuf, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4.关闭流资源
            try {
                if (fw != null)
                    fw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (fr != null)
                    fr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
实现文本之间的复制

 

4.2 FileInputStream/FileOutputStream(字节流)

FileInputStream

该流用于从文件读取数据,它的对象可以用关键字 new 来创建。

可以使用字符串类型的文件名来创建一个输入流对象来读取文件:

构造File对象时,既可以传入绝对路径,也可以传入相对路径。绝对路径是以根目录开头的完整路径。

注意Windows平台使用\作为路径分隔符,在Java字符串中需要用\\表示一个\。Linux平台使用/作为路径分隔符,可以用.表示当前目录,..表示上级目录。

InputStream f = new FileInputStream("C:/java/hello");

 

也可以使用一个文件对象来创建一个输入流对象来读取文件。我们首先得使用 File() 方法来创建一个文件对象:

File f = new File("C:/java/hello"); 
InputStream in = new FileInputStream(f);

 

创建了InputStream对象,就可以使用下面的方法来读取流或者进行其他的流操作。

序号方法及描述
1 public void close() throws IOException{}
关闭此文件输入流并释放与此流有关的所有系统资源。抛出IOException异常。
2 protected void finalize()throws IOException {}
这个方法清除与该文件的连接。确保在不再引用文件输入流时调用其 close 方法。抛出IOException异常。
3 public int read(int r)throws IOException{}
这个方法从 InputStream 对象读取指定字节的数据。返回为整数值。返回下一字节数据,如果已经到结尾则返回-1。
4 public int read(byte[] r) throws IOException{}
这个方法从输入流读取r.length长度的字节。返回读取的字节数。如果是文件结尾则返回-1。
5 public int available() throws IOException{}
返回下一次对此输入流调用的方法可以不受阻塞地从此输入流读取的字节数。返回一个整数值。
int read():从输入流中读取数据的下一个字节。返回 0 到 255 范围内的 int 字节值。如果因为已经到达流末尾而没有可用的字节,则返回值 -1。
int read(byte[] b):从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。如果因为已经到达流末尾而没有可用的字节,则返回值 -1。否则以整数形式返回实际读取的字节数。
int read(byte[] b, int off,int len):将输入流中最多 len 个数据字节读入 byte 数组。尝试读取 len 个字节,但读取的字节也可能小于该值。以整数形式返回实际读取的字节数。如果因为流位于文件末尾而没有可用的字节,则返回值 -1。
public void close() throws IOException:关闭此输入流并释放与该流关联的所有系统资
常用方法

除了 InputStream 外,还有一些其他的输入流,更多的细节参考下面链接:

 

FileOutputStream

该类用来创建一个文件并向文件中写数据。

如果该流在打开文件进行输出前,目标文件不存在,那么该流会创建该文件。

有两个构造方法可以用来创建 FileOutputStream 对象。

使用字符串类型的文件名来创建一个输出流对象:

OutputStream f = new FileOutputStream("C:/java/hello")

也可以使用一个文件对象来创建一个输出流来写文件。我们首先得使用File()方法来创建一个文件对象:

File f = new File("C:/java/hello"); 
OutputStream fOut = new FileOutputStream(f);

 

创建OutputStream 对象完成后,就可以使用下面的方法来写入流或者进行其他的流操作。

序号方法及描述
1 public void close() throws IOException{}
关闭此文件输入流并释放与此流有关的所有系统资源。抛出IOException异常。
2 protected void finalize()throws IOException {}
这个方法清除与该文件的连接。确保在不再引用文件输入流时调用其 close 方法。抛出IOException异常。
3 public void write(int w)throws IOException{}
这个方法把指定的字节写到输出流中。
4 public void write(byte[] w)
把指定数组中w.length长度的字节写到OutputStream中。
void write(int b):将指定的字节写入此输出流。write 的常规协定是:向输出流写入一个字节。要写入的字节是参数 b 的八个低位。b 的 24 个高位将被忽略。 即写入0~255范围的。
void write(byte[] b):将 b.length 个字节从指定的 byte 数组写入此输出流。write(b) 的常规协定是:应该与调用 write(b, 0, b.length) 的效果完全相同。
void write(byte[] b,int off,int len):将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。
public void flush()throws IOException:刷新此输出流并强制写出所有缓冲的输出字节,调用此方法指示应将这些字节立即写入它们预期的目标。
public void close() throws IOException:关闭此输出流并释放与该流关联的所有系统资源。
常用方法

除了OutputStream外,还有一些其他的输出流,更多的细节参考下面链接:

import java.io.*;
 
public class fileStreamTest {
    public static void main(String[] args) {
        try {
            byte bWrite[] = { 11, 21, 3, 40, 5 };
            OutputStream os = new FileOutputStream("test.txt");
            for (int x = 0; x < bWrite.length; x++) {
                os.write(bWrite[x]); // writes the bytes
            }
            os.close();
 
            InputStream is = new FileInputStream("test.txt");
            int size = is.available();
 
            for (int i = 0; i < size; i++) {
                System.out.print((char) is.read() + "  ");
            }
            is.close();
        } catch (IOException e) {
            System.out.print("Exception");先·
        }
    }
}
//文件名 :fileStreamTest2.java
//解决乱码
import java.io.*; public class fileStreamTest2 { public static void main(String[] args) throws IOException { File f = new File("a.txt"); FileOutputStream fop = new FileOutputStream(f); // 构建FileOutputStream对象,文件不存在会自动新建 OutputStreamWriter writer = new OutputStreamWriter(fop, "UTF-8"); // 构建OutputStreamWriter对象,参数可以指定编码,默认为操作系统默认编码,windows上是gbk writer.append("中文输入"); // 写入到缓冲区 writer.append("\r\n"); // 换行 writer.append("English"); // 刷新缓存冲,写入到文件,如果下面已经没有写入的内容了,直接close也会写入 writer.close(); // 关闭写入流,同时会把缓冲区内容写入文件,所以上面的注释掉 fop.close(); // 关闭输出流,释放系统资源 FileInputStream fip = new FileInputStream(f); // 构建FileInputStream对象 InputStreamReader reader = new InputStreamReader(fip, "UTF-8"); // 构建InputStreamReader对象,编码与写入相同 StringBuffer sb = new StringBuffer(); while (reader.ready()) { sb.append((char) reader.read()); // 转成char加到StringBuffer对象中 } System.out.println(sb.toString()); reader.close(); // 关闭读取流 fip.close(); // 关闭输入流,释放系统资源 } }
 @Test
    public void test1() {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            //1.造文件对象
            File srcFile = new File("photo1.jpg");
            File destFile = new File("photo2.jpg");

            //2.造流
            fis = new FileInputStream(srcFile);
            fos = new FileOutputStream(destFile);

            //3.读数据
            byte[] buffer = new byte[5];
            int len;
            while ((len = fis.read(buffer)) != -1) {
                fos.write(buffer, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4.关闭资源
            try {
                if (fos != null)
                    fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (fis != null)
                    fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
实例

 

5. 缓冲流

  • 为了提高数据读写的速度,Java API提供了带缓冲功能的流类,在使用这些流类时,会创建一个内部缓冲区数组,缺省使用8192个字节(8Kb)的缓冲区。
  • 当读取数据时,数据按块读入缓冲区,其后的读操作则直接访问缓冲区
  • 当使用BufferedInputStream读取字节文件时,BufferedInputStream会一次性从文件中读取8192个(8Kb),存在缓冲区中,直到缓冲区装满了,才重新从文件中读取下一个8192个字节数组。
  • 向流中写入字节时,不会直接写到文件,先写到缓冲区中直到缓冲区写满,BufferedOutputStream才会把缓冲区中的数据一次性写到文件里。使用方法flush()可以强制将缓冲区的内容全部写入输出流
  • 关闭流的顺序和打开流的顺序相反。只要关闭最外层流即可,关闭最外层流也会相应关闭内层节点流
  • flush()方法的使用:手动将buffer中内容写入文件
  • 如果是带缓冲区的流对象的close()方法,不但会关闭流,还会在关闭流之前刷新缓冲区,关闭后不能再写出

BufferedInputStream/BufferedOutputStream

    @Test
    public void test1() {
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        try {
            //1.造文件对象
            File srcFile = new File("D:\\QQmusic\\MV\\1988.mp4");
            File descFile = new File("D:\\QQmusic\\MV\\copy1988.mp4");

            //2.1造节点流
            FileInputStream fis = new FileInputStream(srcFile);
            FileOutputStream fos = new FileOutputStream(descFile);

            //2.2造缓冲流
            bis = new BufferedInputStream(fis);
            bos = new BufferedOutputStream(fos);

            //3.数据读入与写出操作
            byte[] buffer = new byte[1024];
            int len;
            while ((len = bis.read(buffer)) != -1) {
                bos.write(buffer, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4.关闭资源
            try {
                if (bos != null)
                    bos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (bis != null)
                    bis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            //说明:先关闭外层的流,再关闭内层的流
            // 关闭外层流的同时,内层流也会自动的进行关闭。关于内层流的关闭,我们可以省略.
//        fos.close();
//        fis.close();
        }
    }

 

BufferedReader/BufferedWriter

    @Test
    public void test2() throws IOException {
        BufferedReader br = null;
        BufferedWriter bw = null;
        try {
            //创建文件和相应的流
            //  BufferedReader br = new BufferedReader(new FileReader(new File("hello.txt")));
            br = new BufferedReader(new FileReader("hello.txt"));
            bw = new BufferedWriter(new FileWriter("hello3.txt"));

            //读写操作
            //方式一
            char[] cbuf = new char[1024];
            int len;
            while ((len = br.read(cbuf)) != -1) {
                bw.write(cbuf, 0, len);
            }
//        //方式二
//        String data;
//        while ((data = br.readLine())!= null){//一次读取字符文本文件的一行字符
//            bw.write(data);//data中不包含换行符, 一次写入一行字符串
//            bw.newLine();
//            //bw.write(data + "\n");
//        }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (bw != null)
                    bw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (br != null)
                    br.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

 

6. 转换流

InputStreamReader/OutputStreamWriter

转换流提供了在字节流和字符流之间的转换
Java API提供了两个转换流:

InputStreamReader:将InputStream转换为Reader
OutputStreamWriter:将Writer转换为OutputStream

字节流中的数据都是字符时,转成字符流操作更高效。
很多时候我们使用转换流来处理文件乱码问题。实现编码和解码的功能。
编码:字符串——>字节数组
解码:字节数组——>字符串

    /**
     * 综合使用InputStreamReader和OutputStreamWriter
     */
    @Test
    public void test1() {
        InputStreamReader isr = null;//默认IDE的字符集
        OutputStreamWriter osw = null;
        try {
            //1.造文件对象
            File file1 = new File("hello.txt");
            File file2 = new File("hello_gbk.txt");
            //2.造流
            FileInputStream fis = new FileInputStream(file1);
            FileOutputStream fos = new FileOutputStream(file2);

            //InputStreamReader isr = new InputStreamReader(fis,"UTF-8");
            isr = new InputStreamReader(fis);

            osw = new OutputStreamWriter(fos, "gbk");
            //OutputStreamWriter osw = new OutputStreamWriter(fos, "gbk");

            //3.数据读写过程
            char[] cbuf = new char[20];
            int len;
            while ((len = isr.read(cbuf)) != -1) {
                osw.write(cbuf, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4.关闭资源
            try {
                if (isr != null)
                    isr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (osw != null)
                    osw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
// 字符流转换为字节流
try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
     BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));) {
    // 循环获取键盘的输入(exit退出),输出此内容
    String msg = "";
    while (!msg.equals("exit")) {
        msg = reader.readLine(); // 循环读取
        writer.write(msg);   // 循环写出
        writer.newLine();
        writer.flush();   // 强制刷新
    }
} catch (IOException e) {
    System.out.println("操作异常");
}

 

7. 对象流

对象的的序列化

ObjectlnputStream 类和 ObjectOutputStream 类除了可以实现基本数据类型与字符串的输人和输出之外,还可以实现对象的输人和输出。

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

ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量
对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。//当其它程序获取了这种二进制流,就可以恢复成原来的Java对象

序列化的好处在于可将任何实现了Serializable接口的对象转化为字节数据,使其在保存和传输时可被还原。
序列化是 RMI(Remote Method Invoke – 远程方法调用)过程的参数和返回值都必须实现的机制,而 RMI 是 JavaEE 的基础。因此序列化机制是JavaEE 平台的基础
如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一。否则,会抛出NotSerializableException异常

  • Serializable
  • Externalizable

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

  • private static final long serialVersionUID;
  • serialVersionUID用来表明类的不同版本间的兼容性。简言之,其目的是以序列化对象进行版本控制,有关各版本反序列化时是否兼容。
  • 如果类没有显示定义这个静态常量,它的值是Java运行时环境根据类的内部细节自动生成的。若类的实例变量做了修改,serialVersionUID 可能发生变化。故建议,显式声明

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

谈谈你对java.io.Serializable接口的理解,我们知道它用于序列化,是空方法接口,还有其它认识吗?

实现了Serializable接口的对象,可将它们转换成一系列字节,并可在以后完全恢复回原来的样子。这一过程亦可通过网络进行。这意味着序列化机制能自动补偿操作系统间的差异。换句话说,可以先在Windows机器上创建一个对象,对其序列化,然后通过网络发给一台Unix机器,然后在那里准确无误地重新“装配”。不必关心数据在不同机器上如何表示,也不必关心字节的顺序或者其他任何细节。
由于大部分作为参数的类如String、Integer等都实现了java.io.Serializable的接口,也可以利用多态的性质,作为参数使接口更灵活。

 

ObjectInputStream/ObjectOutputStream

若某个类实现了 Serializable 接口,该类的对象就是可序列化的:

  • 创建一个 ObjectOutputStream
  • 调用 ObjectOutputStream 对象的writeObject(对象) 方法输出可序列化对象
  • 注意写出一次,操作flush()一次

反序列化

  • 创建一个 ObjectInputStream
  • 调用 readObject() 方法读取流中的对象
强调:如果某个类的属性不是基本数据类型或 String类型,而是另一个引用类型,那么这个引用类型必须是可序列化的,否则拥有该类型的Field 的类也不能序列化
package objectTest;

import org.junit.Test;

import java.io.*;

/**
 * @author mazouri
 * @create 2020-04-21 20:08
 */
public class ObjectInOutputStream {
    /**
     * 序列化过程:将内存中的java对象保存到磁盘中或通过网络传输出去
     * 使用ObjectOutputStream实现
     */
    @Test
    public void test1() {
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("object.dat"));
            oos.writeObject(new Person("张三", 18));
            //刷新操作
            oos.flush();

            oos.writeObject(new Person("李四", 23, 1001, new Account(5000)));
            oos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (oos != null)
                    oos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /*
     *反序列化:将磁盘文件中的对象还原为内存中的一个java对象
     *使用ObjectInputStream来实现
     */
    @Test
    public void test2() {
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream("object.dat"));

            Person p = (Person) ois.readObject();
            Person p1 = (Person) ois.readObject();

            System.out.println(p + "\n" + p1);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            try {
                if (ois != null)
                    ois.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

 

 

8. 随机存取文件流

  • 到现在为止, 所使用的所有流都是只读的(read.only ) 或只写的(write.only)。这些流称为顺序( sequential)流。使用顺序流打开的文件称为顺序访问文件。顺序访问文件的内容不能更新。然而,经常需要修改文件。Java 提供了 RandomAccessFile 类,允许在文件的任意位置上进行读写。使用RandomAccessFile 类打开的文件称为随机访问文件。
  • RandomAccessFile 声明在java.io包下,但直接继承于java.lang.Object类。并且它实现了DataInput、DataOutput这两个接口,也就意味着这个类既可以读也可以写
  • RandomAccessFile 类支持 “随机访问” 的方式,程序可以直接跳到文件的任意地方来读、写文件
    • 支持只访问文件的部分内容
    • 可以向已存在的文件后追加内容
  • RandomAccessFile 对象包含一个记录指针,用以标示当前读写处的位置。RandomAccessFile 类对象可以自由移动记录指针:
    • long getFilePointer():获取文件记录指针的当前位置
    • void seek(long pos):将文件记录指针定位到 pos 位置

RandomAccessFile

构造器

  • public RandomAccessFile(File file, String mode)
  • public RandomAccessFile(String name, String mode)
  • 创建 RandomAccessFile 类实例需要指定一个 mode 参数,该参数指定 RandomAccessFile 的访问模式:
    • r: 以只读方式打开
    • rw:打开以便读取和写入
    • rwd:打开以便读取和写入;同步文件内容的更新
    • rws:打开以便读取和写入;同步文件内容和元数据的更新
  • 如果模式为只读r。则不会创建文件,而是会去读取一个已经存在的文件,如果读取的文件不存在则会出现异常。 如果模式为rw读写。如果文件不存在则会去创建文件,如果存在则不会创建。
    @Test
    public void test1() {

        RandomAccessFile raf1 = null;
        RandomAccessFile raf2 = null;
        try {
            //1.
            raf1 = new RandomAccessFile(new File("爱情与友情.jpg"),"r");
            raf2 = new RandomAccessFile(new File("爱情与友情1.jpg"),"rw");
            //2.
            byte[] buffer = new byte[1024];
            int len;
            while((len = raf1.read(buffer)) != -1){
                raf2.write(buffer,0,len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //3.
            if(raf1 != null){
                try {
                    raf1.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
            if(raf2 != null){
                try {
                    raf2.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }
    }

    @Test
    public void test2() throws IOException {

        RandomAccessFile raf1 = new RandomAccessFile("hello.txt","rw");

        raf1.seek(3);//将指针调到角标为3的位置
        raf1.write("xyz".getBytes());//

        raf1.close();

    }
    /*
    使用RandomAccessFile实现数据的插入效果
     */
    @Test
    public void test3() throws IOException {

        RandomAccessFile raf1 = new RandomAccessFile("hello.txt","rw");

        raf1.seek(3);//将指针调到角标为3的位置
        //保存指针3后面的所有数据到StringBuilder中
        StringBuilder builder = new StringBuilder((int) new File("hello.txt").length());
        byte[] buffer = new byte[20];
        int len;
        while((len = raf1.read(buffer)) != -1){
            builder.append(new String(buffer,0,len)) ;
        }
        //调回指针,写入“xyz”
        raf1.seek(3);
        raf1.write("xyz".getBytes());

        //将StringBuilder中的数据写入到文件中
        raf1.write(builder.toString().getBytes());

        raf1.close();
    }
}

 

9. 数组流

数组可以看成另外的一种内存,它可以不用向之前的那种流一样,需要关闭资源。

代码,数组输入

构造方法

ByteArrayInputStream(byte[] buf)   // 创建一个 ByteArrayInputStream ,使其使用 buf作为其缓冲区数组。
ByteArrayInputStream(byte[] buf, int offset, int length) // 创建 ByteArrayInputStream使用 buf作为其缓冲器阵列。
byte[] src = "io is so easy".getBytes();  // 使用平台的默认字符集将该String编码为字节序列,并将结果储存到一个新的字节数组中
       InputStream is = null;
       try {
           is = new ByteArrayInputStream(src);
           byte[] flush = new byte[5];
           int len = -1;
           while ((len = is.read(flush)) != -1) {
               String str = new String(flush, 0, len);
               System.out.println(str);
           }
       } catch (IOException e) {
           e.printStackTrace();
       }

 

代码,数组输出

构造方法

ByteArrayOutputStream()   // 创建一个新的字节数组输出流。
ByteArrayOutputStream(int size)   // 创建一个新的字节数组输出流,具有指定大小的缓冲区容量(以字节为单位)。
byte[] dest = null;
        ByteArrayOutputStream baos = null;
        try {
            baos = new ByteArrayOutputStream();
            String str = "io is so easy";
            byte[] datas = str.getBytes();
            baos.write(datas, 0, datas.length);
            baos.flush();
            //获取数据
            dest = baos.toByteArray();
            System.out.println(dest.length+"--->"+new String(dest,0, baos.size()));
        } catch (IOException e) {
            e.printStackTrace();
        }

 

10. 数据流

数据流是方便处理基本类型和八大基本类型,以及字符串的。

// 写出
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(bos));
        // 操作数据类型+数据
        dos.writeUTF("一二三四五六");
        dos.writeInt(18);
        dos.writeBoolean(false);
        dos.writeChar('a');
        dos.flush();
        byte[] datas = bos.toByteArray();
        // 读取
        DataInputStream dis = new DataInputStream(new BufferedInputStream(new ByteArrayInputStream(datas)));
        // 顺序与写出一致
        String msg = dis.readUTF();
        int age = dis.readInt();
        boolean flag = dis.readBoolean();
        char ch = dis.readChar();
        System.out.println(age);

 

11.解决编码乱码的问题

我们知道一般常用的编码有(ASKLL ,GBK ,UTF-8等)

如何解决编码乱码的问题呢,首先我们要知道编码乱的原因。

乱码产生的原因分两种,一是字数不够了,这样程序运行的时候就会出现少一个字节问题,从而产生乱码的问题,二是字符集不同,比如JVM要的是UTF-8的编码,你给了GBK的编码,也会产生乱码问题。

// 乱码 字数不够
msg = new String(datas, 0, datas.length - 1, "UTF8");
System.out.println(msg)**;
msg = new String(datas, 0, datas.length - 2, "UTF8");
System.out.println(msg);
// 字符集不统一。产生乱码
msg = new String(datas, 0, datas.length - 2, "gbk");

 

 

 转自:12.

Java 8 中文版 - 在线API中文手册 - 码工具 (matools.com)

 

 

 

标签:文件,Java,字节,Stream,int,File,IOException,IO,new
From: https://www.cnblogs.com/yunlong-study/p/16711607.html

相关文章