• javaSE I/O流(二)—— 各种各样的流


    目录

    一、Java IO

    3. 一般I/O有两种

    4. 流的分类

    5. IO流的体系

    6、在Main函数中和Test单元测试中,文件路径是不同的

    二、访问文件的4个节点流

    2.1 FileReader

    1、 FileReader类记忆点

    2、 FileReader类常用方法

    3、int read()一次读一个字符 操作演示

    4、一次读多个字符int read(char[] ) 操作演示 

    2.2 FileWriter

    2.2.1 FileWriter类的记忆点

    2.2.2 FileWriter类的常用方法

    2.2.3 三个常用write()方法的一波操作

    2.3 使用FileReader和FileWriter实现文本文件的复制

    2.4 FileInputStream

    2.4.1 FileInputStream的记忆点

    2.4.2 FileInputStream的常用函数

    2.5 FileOutputStream

    2.6 FileInputStream和FileOutputStream实现图片的复制

    三、缓冲流

    3.1 BufferedInputStream类

    3.2 BufferedOutputStream类

    3.3   BufferedInputStream和BufferedOutputStream实现非文本文件的复制

    3.4 BufferedReader类

    3.5 BufferedWriter类

    3.6 BufferedReader和BufferedWriter实现文本文件的复制

    3.7 练习题

    四、转换流

    4.1 记忆点

    4.2 InputStreamReader常用函数

    4.3 InputStreamReader用法

    4.4 OutputStreamWriter常用函数

    4.5 OutputStreamWriter用法

    五、标准输入流System.in、标准输出流System.out

    5.1 记忆点

    5.2 常用方法

    5.3 例题

    六、打印流——printStream和printWriter

    七、数据流——DataInputStream、DataOutputStream

    八、对象流—— ObjectInputStream、ObjectOutputStream

    8.1 记忆点

    8.2 常用方法

    8.2.1 ObjectOutputStream类

    8.2.2 ObjectInputStream类

    8.3 栗子

    8.4 自定义类可序列化的3个条件

    8.5 被static和transient修饰的属性无法序列化

    9、RandomAccessFile类

    9.1 记忆点

    9.2 常用方法

    9.3 多线程断点下载


    一、Java IO

    1. 关于C++在I/O流上的操作,之前也整理过一点。

    C语言fputs fgets fputc fgetc fscanf fprintf及fopen操作的整理_玛丽莲茼蒿的博客-CSDN博客_fgetc fscanf最近写了一些文件操作的程序,频繁使用下面的函数。所以整理一下。一、fopen函数fopen函数的一般用法FILE *f;if((f=fopen("1.txt","这里写文件操作"))==NULL){ printf("Open file failed\n");}1.以只读方式打开文件。fopen第二个参数为r。要求文件“1.txt”必须存在,不存在的话只读方式不会帮你创建的FILE *f;if((f=fopen("1.txt","r"))==NULL){ //打开只读文件phttps://blog.csdn.net/qq_44886213/article/details/1173658072. 输入是输出是相对来说的(一个程序和文件进行通信,文件的输出对于程序来说是输入),那我们站位在什么角度呢?

    答:站位在程序(内存)的角度。内存读取文件叫输入,往文件里写东西叫输出。

    3. 一般I/O有两种

    以前接触过的是对文件的I/O,还有一种是网络传输,两个网络设备之间的I/O。

    4. 流的分类

     理解节点流和处理流:从进行I/O的两方之间简单建立起来的I/O流叫节点流。如果想让这个节点流传输得更快或者更安全,就要在这个节点流外面再包一层流,叫处理流

    5. IO流的体系

    四个抽象基类:

    字节流:InputStream、OutputStream

    字符流:Reader 、Writer

     哪些是重点呢?

    6、在Main函数中和Test单元测试中,文件路径是不同的

    二、访问文件的4个节点流

    1. FileReader和FileWriter进行字符流的处理:文本文件(.txt,.java,.c,.cpp)。不可以实现非文本文件的复制。

    2. FileInputStream和FileOutputStream进行字节流的处理:非文本本(jpg,png,mp3,

    mp4,doc,ppt)。但是可以实现文本文件的复制。

    2.1 FileReader

    1、 FileReader类记忆点

    • 为了保证close操作一定执行,需要使用try-catch-finally
    • 读入的文件一定要存在。不然在调用new FileReader的时候会报“FileNotFound”异常

    2、 FileReader类常用方法

    (1)构造方法

            可以用File类的对象

    FileReader fr = new FileReader(file);

            也可以直接用文件路径 

    FileReader fr = new FileReader("hello.txt");

    (2)int read()

    一次只能读一个字符,直接作为返回值返回。

    注意:返回的不是char类型,而是char对应的int。返回值-1时,表示读到文件末尾

    (3)int read(char [])

    一次读多个字符,读入到char[] 数组中

    注意:返回的依然是int类型“每次实际读出的个数”。返回值-1时,表示读到文件末尾

    (4)close

    3、int read()一次读一个字符 操作演示

    首先,我们在对应的Module下建立一个hello.txt文件,输入内容“hello world!!!”。

    1. @Test
    2. public void test01() throws IOException {
    3. //1)创建File对象
    4. File file = new File("hello.txt");
    5. //2)创建FileReader对象
    6. FileReader fr = new FileReader(file);
    7. //3) 读文件
    8. int data;
    9. while((data = fr.read()) !=-1){
    10. System.out.print((char)data);
    11. }
    12. //4)关闭流
    13. fr.close();
    14. }

    运行结果:

    hello world!!! 

    但这个代码当中存在一些问题,需要优化一下:

    现在是在整个函数外抛出异常,如果我的read函数出错了,那么程序中止,会导致前面打开的流没有close。换句话说,close操作是必须要执行的,那就用try-catch-finally语句。

            1)选中要被try包含的代码,按住快捷键Ctrl+Alt+t,选中“try/catch/finally” 

            

            2)由于close本身也需要做异常处理。所以close也要用try-catch包起来。

            

              3)这里还有一个问题。如果不是在read处出错,而是在new FileReader的时候就出错了,fr没有创建成功,那么进入finally的fr.close()时,会出现“空指针异常”。所以我们在进行close操作的时候要先判断fr是不是空指针

            

             优化后的完整代码:

    1. @Test
    2. public void test01() {
    3. FileReader fr = null;
    4. try {
    5. //1)创建File对象
    6. File file = new File("hello.txt");
    7. //2)创建FileReader对象
    8. fr = new FileReader(file);
    9. //3) 读文件
    10. int data;
    11. while((data = fr.read()) != -1){
    12. System.out.println((char)data);
    13. }
    14. } catch (IOException e) {
    15. e.printStackTrace();
    16. } finally {
    17. //4)关闭流
    18. try {
    19. if(fr != null)
    20. fr.close();
    21. } catch (IOException e) {
    22. e.printStackTrace();
    23. }
    24. }
    25. }

    4、一次读多个字符int read(char[] ) 操作演示 

    难点:错误写法那里,不能用buf.length,而要用len。

    1. @Test
    2. public void test02(){
    3. FileReader fr = null;
    4. try {
    5. //1)创建File对象
    6. File file = new File("hello.txt");
    7. //2)创建FileReader对象
    8. fr = new FileReader(file);
    9. //3)读文件
    10. char []buf = new char[5];
    11. int len;
    12. while((len = fr.read(buf))!= -1){
    13. /*------输出方式(一)------*/
    14. //错误写法
    15. // for(int i=0; i
    16. // System.out.println(buf[i]);
    17. // }
    18. for(int i=0; i
    19. System.out.print(buf[i]);
    20. }
    21. /*-----输出方式(二)------*/
    22. //错误写法
    23. // String string = new String(buf);
    24. // System.out.println(string);
    25. String string1 = new String(buf, 0, len);
    26. System.out.println(string1);
    27. }
    28. } catch (IOException e) {
    29. e.printStackTrace();
    30. } finally {
    31. //4)关闭文件
    32. try {
    33. if(fr != null)
    34. fr.close();
    35. } catch (IOException e) {
    36. e.printStackTrace();
    37. }
    38. }
    39. }

    2.2 FileWriter

    2.2.1 FileWriter类的记忆点

    • 为了保证close操作一定执行,需要使用try-catch-finally
    • 输出的文件可以不存在。不存在的话会自动创建
    • “append追加”还是覆盖

    2.2.2 FileWriter类的常用方法

    (1)构造方法

    1. fw = new FileWriter(file, true); //true为追加
    2. fw = new FileWriter(file, false); //false为不追加
    3. //等同于fw = new FileWriter(file);

    (2)write(char)

            一次写入一个字符

    (3)write(char [] )

            一次写入多个字符

    (4)write(String)

            一次写入一个字符串

    2.2.3 三个常用write()方法的一波操作

    1. @Test
    2. public void test01(){
    3. FileWriter fw = null;
    4. try {
    5. //1)创建File对象
    6. File file = new File("notExist.txt");
    7. //2)创建FileWriter对象
    8. fw = new FileWriter(file, true);
    9. //3)写入操作
    10. fw.write('3');
    11. fw.write(new char[]{'S','e','p','t','e','m','b','e','r'});
    12. fw.write("You must find more time on this work");
    13. } catch (IOException e) {
    14. e.printStackTrace();
    15. } finally {
    16. //4) 关闭流
    17. if(fw !=null){
    18. try {
    19. fw.close();
    20. } catch (IOException e) {
    21. e.printStackTrace();
    22. }
    23. }
    24. }
    25. }

    运行结果: 

    3SeptemberYou must find more time on this work

     如果想要换行,就输入“\n”

    2.3 使用FileReader和FileWriter实现文本文件的复制

     模板:

    1. //1)创建File对象:读出和写入各一个
    2. //2)创建FileReader对象 和 FileWriter 对象
    3. //3)读出和写入操作
    4. //4)关闭两个流

    完整代码

    1. @Test
    2. public void test02() {
    3. FileReader fr = null;
    4. FileWriter fw = null;
    5. try {
    6. //1)创建File对象:读出和写入各一个
    7. File readFile = new File("hello.txt");
    8. File writeFile = new File("notExist.txt");
    9. //2)创建FileReader对象 和 FileWriter 对象
    10. fr = new FileReader(readFile);
    11. fw = new FileWriter(writeFile, true);
    12. //3)读出和写入操作
    13. char []buf = new char[5];
    14. int len;
    15. while((len = fr.read(buf)) != -1){
    16. // //方式(一)
    17. // String s = new String(buf, 0, len);
    18. // fw.write(s);
    19. //方式(二)
    20. fw.write(buf,0,len);
    21. }
    22. } catch (IOException e) {
    23. e.printStackTrace();
    24. } finally {
    25. try {
    26. //4)关闭两个流
    27. if(fr != null)
    28. fr.close();
    29. } catch (IOException e) {
    30. e.printStackTrace();
    31. }
    32. try {
    33. //4)关闭两个流
    34. if(fw != null)
    35. fw.close();
    36. } catch (IOException e) {
    37. e.printStackTrace();
    38. }
    39. }
    40. }

    PS:发现File类中有创建文件、删除文件,但好像并没有拷贝文件的操作。所以不能通过file.copy(源文件,目的文件)一行代码直接实现吗 

    那能否用FileWriter和FileReader去拷贝一张图片呢?

    答:不能。

     出来的bbb.jpg是打不开的。

    2.4 FileInputStream

    2.4.1 FileInputStream的记忆点

    1.如果用FileReader以Byte[]数组的形式去读txt文本文件会怎样?

            读英文和数字正常,中文会出现乱码。

    英文和数字:因为我们使用的是UTF-8编码,英文和数字刚好占一字节,所以不会出现乱

    中文:在UTF-8编码中占3个字节,一个一个字节去读就会把一个汉字给拆开,出现乱码。

    PS:但是现在JDK的版本已经直接不支持read函数去读Byte[]了,只能读char[]。

    2. 读的文件必须存在,否则会报FileNotFoundException异常

    2.4.2 FileInputStream的常用函数

    和FileReader一样,把char变成byte

    2.5 FileOutputStream

    和FileWriter一样,把char变成byte

    2.6 FileInputStream和FileOutputStream实现图片的复制

    因为我们在单元测试中进行,所以把要拷贝的图片放在对应的模块目录下,而不是项目目录下。

    1. @Test
    2. public void test01() throws IOException {
    3. FileInputStream fi = null;
    4. FileOutputStream fo = null;
    5. try {
    6. //1)创建File对象
    7. File file1 = new File("053.png");
    8. File file2 = new File("053-1.png");
    9. //2)创建FileInputStream和FileOutputStream对象
    10. fi = new FileInputStream(file1);
    11. fo = new FileOutputStream(file2);
    12. //3)读和写操作
    13. byte []buf = new byte[5];
    14. int len;
    15. while((len = fi.read(buf)) != -1){
    16. fo.write(buf,0,len);
    17. }
    18. } catch (IOException e) {
    19. e.printStackTrace();
    20. } finally {
    21. //4)关闭流
    22. if(fi != null){
    23. try {
    24. fi.close();
    25. } catch (IOException e) {
    26. e.printStackTrace();
    27. }
    28. }
    29. if(fo != null){
    30. try {
    31. fo.close();
    32. } catch (IOException e) {
    33. e.printStackTrace();
    34. }
    35. }
    36. }
    37. }

    复制成功!

    那能否实现(带有中文)文本文件的复制?

    能。

    所以再说一遍我们的结论:

    1. FileReader和FileWriter进行字符流的处理:文本文件(.txt,.java,.c,.cpp)。不可以实现非文本文件的复制。

    2. FileInputStream和FileOutputStream进行字节流的处理:非文本本(jpg,png,mp3,

    mp4,doc,ppt)。但是可以实现文本文件的复制。

    三、缓冲流

    记忆点

    1. 处理流的一种。
    2. 作用:提高文件的读写效率。
    3. 开发的时候我们不会用FileReader、FileWriter、FileInputStream和FileOutputStream。因为他们的效率太慢了。
    4. 四种
    5. 装饰者模式,增加了readLine方法
    6. 关闭外层流时自动关闭内层流

    因为缓冲流的操作和前面的四个节点流差不多,所以就不单独介绍四个Buffered类了。直接上操作

    3.1 BufferedInputStream类

    和FileInputStream一样

    3.2 BufferedOutputStream类

    和FileOutputStream一样

    3.3   BufferedInputStream和BufferedOutputStream实现非文本文件的复制

    两个注意点:

    1. 前面我们知道,处理流是包在节点流的外面的。所以new处理流之前要先new对应的结点流,并且节点流的对象作为处理流构造函数的参数。

    2. 相应的,关闭流的时候我们也应该关闭节点流和处理流。按照“先关闭外层的流再关闭内层的流”原则(和“创建析构顺序”,“穿衣脱衣顺序”同理),应该先关闭处理流的两个流,再关闭节点流的两个流。但是这里外层流自动关闭内层流,所以我们只需要关闭外层处理流就可以了。

    1. @Test
    2. public void test01(){
    3. BufferedInputStream bis = null;
    4. BufferedOutputStream bos = null;
    5. try {
    6. //1)创建File对象
    7. File srcFile = new File("053.png");
    8. File desFile = new File("053-2.png");
    9. //2)创建节点流对象和处理流对象
    10. FileInputStream fis = new FileInputStream(srcFile);
    11. FileOutputStream fos = new FileOutputStream(desFile);
    12. bis = new BufferedInputStream(fis);
    13. bos = new BufferedOutputStream(fos);
    14. //3)读 和 写
    15. byte []buf = new byte[10];
    16. int len;
    17. while((len = bis.read(buf)) != -1){
    18. bos.write(buf,0,len);
    19. }
    20. } catch (IOException e) {
    21. e.printStackTrace();
    22. } finally {
    23. //4)关闭流 :只需要关闭外层流就可以了。外层流自动关闭内层流
    24. if(bis != null){
    25. try {
    26. bis.close();
    27. } catch (IOException e) {
    28. e.printStackTrace();
    29. }
    30. }
    31. if(bos != null){
    32. try {
    33. bos.close();
    34. } catch (IOException e) {
    35. e.printStackTrace();
    36. }
    37. }
    38. }
    39. }

    为什么用BufferedInputStream、BufferedOutputStream对文件进行处理比FileInputStream和FileOutputStream快得多?

    操作系统笔记——缓冲区管理_玛丽莲茼蒿的博客-CSDN博客

            和缓冲区的原理一样。文件在磁盘上,磁盘是一种I/O设备,写文件就是写I/O设备。在缓冲区(其实就是在内存开辟一块地方)这个定义出现之前,CPU和I/O设备是直接交互的(这里可以理解为程序和文件是直接交互的),但是CPU和I/O设备的速度差太大了啊,I/O完全是在拖后腿!有了缓冲区之后:

    注意:当缓冲区为空时,可以往缓冲区冲入数据,但是必须充满以后才能传出;当缓冲区数据非空时,不能往缓冲区冲入数据, 只能从缓冲区把数据传出;如此就不会频繁地“写-读-写-读”,而是“写写写写——一次性读走”

    因为Bufferd在内存里定义了一个缓冲区。这个缓冲区的大小=8×1024=8192个字节。当这个缓冲区被填满以后,会自动调用一个flush()方法,把缓冲区的内容一次性写到磁盘里。而不是读一次文件接着写一次磁盘。

    3.4 BufferedReader类

    和FileReader类不同的是,多了一个新方法

    • readLine():一次读一行返回String类型,读到末尾返回null。但是该函数读不出来换行符。

    3.5 BufferedWriter类

    和FileWriter一样。

    3.6 BufferedReader和BufferedWriter实现文本文件的复制

    1. @Test
    2. public void test02(){
    3. BufferedReader br = null;
    4. BufferedWriter bw = null;
    5. try {
    6. //1)和2)合并
    7. br = new BufferedReader(new FileReader(new File("hello.txt")));
    8. bw = new BufferedWriter(new FileWriter(new File("hello-1.txt")));
    9. //3)读 写
    10. int len;
    11. char []cbuf = new char[1024];
    12. while((len = br.read(cbuf)) != -1){
    13. bw.write(cbuf, 0,len);
    14. }
    15. //法(二)
    16. String data;
    17. while((data = br.readLine()) != null){
    18. bw.write(data+'\n'); //加换行符是因为readLine读不出换行符,所以要手动加上
    19. }
    20. } catch (IOException e) {
    21. e.printStackTrace();
    22. } finally {
    23. //4)关闭流
    24. if(br!=null){
    25. try {
    26. br.close();
    27. } catch (IOException e) {
    28. e.printStackTrace();
    29. }
    30. }
    31. if(bw!=null){
    32. try {
    33. bw.close();
    34. } catch (IOException e) {
    35. e.printStackTrace();
    36. }
    37. }
    38. }
    39. }

    3.7 练习题

    1. 图片加密存储、解密显示

    加密的方法很简单,让图片的每个byte和一个数做异或操作,得到的加密图片和原图大小一样

     解密:由于两次异或同一个数等于本身,即“m ^ n ^ n = m”,解密操作和加密是一样的,也是和5做异或。

    2. 获取文本文件里每个字符出现的个数。

    1. BufferedReader reader = null;
    2. HashMap hashMap = null;
    3. try {
    4. //1)创建Map对象
    5. hashMap = new HashMap();
    6. //2)创建BufferedReader对象
    7. reader = new BufferedReader(new FileReader(new File("hello.txt")));
    8. //3)读 和 统计
    9. int c = 0;
    10. while((c = reader.read()) != -1){
    11. char c2 = (char) c;
    12. if(hashMap.get(c2) != null){
    13. hashMap.put(c2,hashMap.get(c2)+1);
    14. }else{
    15. hashMap.put(c2,1);
    16. }
    17. }
    18. } catch (IOException e) {
    19. e.printStackTrace();
    20. } finally {
    21. if(reader != null){
    22. try {
    23. //4)关闭流
    24. reader.close();
    25. } catch (IOException e) {
    26. e.printStackTrace();
    27. }
    28. }
    29. }
    30. //输出结果
    31. System.out.println(hashMap);

     还可以用read(char [])替代read()。还可以用contasinsKey()替代get检查字符是否出现过。

    四、转换流

    4.1 记忆点

            1.InputStreamReader和OutputStreamWriter的作用

            记下面这张图就行了

            

            InputStreamReader:把【输入的字节流】转换成【输入的字符流】;把看不懂的变成看得懂的,解码

            OutputStreamWriter:把【输出的字符流】转换成【输出的字节流】;反之,编码

            2. InputStreamReader和OutputStreamWriter属于抽象基类InputStream和OuputStream还是Reader和Writer?

            属于Reader和Writer这两个抽象基类的实现类————是谁的实现类只看后缀

            3.什么时候用?

            需要进行字符流和字节流之间转换时,简单说,涉及到编码时。

    4.2 InputStreamReader常用函数

    1. 构造函数

            第二个参数:所读文件“utf-8.txt”保存时的编码方式

    1. InputStreamReader reader = new InputStreamReader(节点流对象, "UTF-8");
    2. //如果构造函数的第二个参数不写,默认采用IDEA的编码方式去解码
    3. InputStreamReader reader = new InputStreamReader(节点流对象);

    4.3 InputStreamReader用法

    为了便于看,我们就不用try-catch-finnaly了

    1. @Test
    2. public void test01() throws IOException {
    3. //1)不用创建File对象了,直接创建FileInputStream和InputStreamReader对象
    4. FileInputStream fis = new FileInputStream("hello.txt");
    5. InputStreamReader reader = new InputStreamReader(fis, "UTF-8");
    6. //2)读,然后输出到命令行看看
    7. char []cbuf = new char[5];
    8. int len=0;
    9. while((len = reader.read(cbuf)) != -1){
    10. System.out.println(new String(cbuf,0,len));
    11. }
    12. //3)关闭流
    13. reader.close();
    14. }

    上面这个hello.txt是用UTF-8保存的文件,我们试着用gbk去解码看看:

    4.4 OutputStreamWriter常用函数

    1. 构造函数

            第二个参数:指定用什么方式编码

            OutputStreamWriter writer = new OutputStreamWriter(节点流对象, "gbk");

    4.5 OutputStreamWriter用法

            我们用FileReader读一个utf-8编码的文件“hello.txt”,然后用转换流对其重新编码,以"gbk"方式存储。

                    

    1. @Test
    2. public void test02() throws IOException {
    3. //1)以字符的方式读文件,以转换流的方式对文件重新编码写文件
    4. FileReader reader = new FileReader("hello.txt");
    5. FileOutputStream fos = new FileOutputStream("gbk.txt");
    6. OutputStreamWriter writer = new OutputStreamWriter(fos, "gbk");
    7. //2)读文件 写文件
    8. char []cbuf = new char[5];
    9. int len = 0;
    10. while((len = reader.read(cbuf)) != -1){
    11. writer.write(cbuf,0,len);
    12. }
    13. //3)关闭流
    14. reader.close();
    15. writer.close();
    16. }

    接下来讲的三个流稍作了解:

    • 标准输入流、标准输出流
    • 打印流
    • 数据流

    五、标准输入流System.in、标准输出流System.out

    5.1 记忆点

            1. System.in:默认从键盘输入 。System.out 默认从显示器输出。可以通过System类的setIn()和setOut()方法更改默认的输出端

            2. System.in和System.out是字节流!!!想一下也不可能是字符流,怎么可能只向显示器输出字符呢?

    5.2 常用方法

    最常用的不就是我们的

    1. System.out.print();
    2. System.out.println();

    5.3 例题

     这里我们用方法二,因为我们想用BufferdReader的readline方法,但是BufferdReader读的是字符流,而system.in是字节流。这时候就要想到字节流向字符流的转换——转换流。

    PS:因为IDEA的限制,system.in在单元测试下没法通过键盘输入,所以我们写在main函数里。同时,为了方便看程序的逻辑,这里也没用try-catch-finally,而是直接抛的异常

    1. public static void main(String[] args) throws IOException {
    2. //1)转换流把标准输入的字节流变成字符流
    3. InputStreamReader charReader = new InputStreamReader(System.in);
    4. //再包上一层Buffer处理流
    5. BufferedReader reader = new BufferedReader(charReader);
    6. //2)读
    7. while(true){
    8. String data = reader.readLine();
    9. if("e".equalsIgnoreCase(data) || "exit".equalsIgnoreCase(data)){
    10. System.out.println("程序结束");
    11. break;
    12. }
    13. System.out.println(data.toUpperCase());
    14. }
    15. //关闭流
    16. reader.close();
    17. }

    六、打印流——printStream和printWriter

            printStream:字节输出流

            printWriter:字符输出流

            如果程序的结果不想往控制台输出,想输入到一个文件里。就用打印流指定文件的位置,然后用System的静态方法setOut去设置默认的输出端。

    七、数据流——DataInputStream、DataOutputStream

    注意:DataOutputStream写到文件去的基本数据类型不能直接打开看,而只能通过DataInputStream读到内存中去看

    八、对象流—— ObjectInputStream、ObjectOutputStream

    8.1 记忆点

    (1)通过后缀可以看出,对象流本质上是字节流

    (2)ObjectOutputStream实现对象的序列化:可以把java中的对象写入到磁盘的数据源(文件、数据库)中(目的是为了将java对象存储下来,因为程序运行结束后内存中创建的java对象也就消失了,要想留下Java对象,就要存储到到磁盘中),或者通过网络将这种二进制流从一个网络结点传输到另一个网络结点。

            ObjectInputStream实现对象的反序列化:将二进制流恢复成java对象

    (3)基本数据类型和String、Data、File、TreeSet等java常用类可以直接序列化。但是我们自定义的类的对象要实现序列化需要满足以下3点要求:

    • 自定义类要实现Serializable接口。Serializable接口是一个“标识接口”,里面没有任何方法。
    • 在类内提供一个静态常量serialVersionUID(随便给一个long型的数,正负都行)
    private(public) static final long serialVersionUID = 665132135465L; 
    •  对象的所有属性都要可序列化

    (4)类中被statictransient修饰的属性无法序列化。

    (5)实际开发的中进行两个进程间数据传输的时候,我们一般不把类进行序列化,而是将类转换成JSON字符串,直接传送JSON字符串。

    8.2 常用方法

    8.2.1 ObjectOutputStream类

    1.将对象序列化

    writeObject(java对象)

    2.刷新到硬盘上去 

    void flush(); //刷新

    8.2.2 ObjectInputStream类

    1. 将对象反序列化 

    Object readObject();

    8.3 栗子

    test01将Person类的对象进行序列化,test02进行反序列化,先运行test01,再运行test02。

    1. /**
    2. * ObjectOutputStream进行序列化
    3. */
    4. @Test
    5. public void test01(){
    6. //1)
    7. ObjectOutputStream oos = null; //写入txt文件也可以,但是写入的文件不能打开看
    8. try {
    9. oos = new ObjectOutputStream(new FileOutputStream("Object.data"));
    10. //2)写
    11. oos.writeObject(new Person("程野",15));
    12. oos.writeObject(new Person("王小虎",16));
    13. } catch (IOException e) {
    14. e.printStackTrace();
    15. } finally {
    16. try {
    17. if(oos != null){
    18. oos.close();
    19. }
    20. } catch (IOException e) {
    21. e.printStackTrace();
    22. }
    23. }
    24. }
    25. /**
    26. * ObjectInputStream进行反序列化
    27. */
    28. @Test
    29. public void test02(){
    30. ObjectInputStream ois = null;
    31. try {
    32. //1)
    33. ois = new ObjectInputStream(new FileInputStream("Object.data"));
    34. //2)读
    35. Object o = ois.readObject();
    36. System.out.println(o.toString());
    37. Object o1 = ois.readObject();
    38. System.out.println(o1.toString());
    39. } catch (IOException e) {
    40. e.printStackTrace();
    41. } catch (ClassNotFoundException e) {
    42. e.printStackTrace();
    43. } finally {
    44. try {
    45. if (ois != null) {
    46. ois.close();
    47. }
    48. } catch (IOException e) {
    49. e.printStackTrace();
    50. }
    51. }
    52. }

    结果如下:

    Person{name='程野', age=15}
    Person{name='王小虎', age=16}

    注意

    1)和数据流一样,对象流的数据源“Object.data”也不能打开看,只能通过ObjectInputStream读进内存后才能看。

    2)先写入的对象先读出

    以上的例子是发送端把序列化的对象存在本地文件中,接收端再去读取这个文件。那如果是网络传输呢?比如我们想要通过UDP实现对象的传输。如果还采用“存储到文件”的方法,就需要再把文件转换成byte[]字符数组。像下面这样,很麻烦的,其实是多此一举。

     有没有其他办法呢?其实可以借助ByteArrayOutputStream,直接把对象序列化到byte[]数组中,这样就省去了“本地文件”转换这一步。

    1. package UDP;
    2. import org.junit.Test;
    3. import java.io.*;
    4. import java.net.DatagramPacket;
    5. import java.net.DatagramSocket;
    6. import java.net.InetAddress;
    7. import java.net.SocketException;
    8. public class UDPtest2 {
    9. @Test
    10. public void send() throws IOException {
    11. //1.创建一个数据报套接字对象
    12. DatagramSocket socket = new DatagramSocket();
    13. //2.将person对象序列化,序列化的结果存放在data[]中,
    14. ByteArrayOutputStream bos = new ByteArrayOutputStream();
    15. ObjectOutputStream oos = new ObjectOutputStream(bos);
    16. oos.writeObject(new Person("2022/11/21",22));
    17. byte [] data = bos.toByteArray();
    18. //3.生成UDP数据报
    19. DatagramPacket packet = new DatagramPacket(data,0, data.length, InetAddress.getLocalHost(),5566);
    20. //3.套接字发送数据报
    21. socket.send(packet);
    22. //4.关闭资源
    23. socket.close();
    24. }
    25. @Test
    26. public void receive() throws IOException, ClassNotFoundException {
    27. //1.创建服务器端的套接字对象
    28. DatagramSocket socket = new DatagramSocket(5566);
    29. //2.创建服务器端的数据报对象
    30. byte []data = new byte[1024];
    31. DatagramPacket packet = new DatagramPacket(data, 0, data.length);
    32. //3.套接字接收数据报
    33. socket.receive(packet);
    34. ByteArrayInputStream bis = new ByteArrayInputStream(packet.getData());
    35. ObjectInputStream ois = new ObjectInputStream(bis);
    36. Object o = ois.readObject();
    37. Person p = (Person) o;
    38. System.out.println(p.getName());
    39. System.out.println(p.getAge());
    40. //4. 关闭资源
    41. socket.close();
    42. }
    43. }
    44. class Person implements Serializable{
    45. private String name;
    46. private int age;
    47. public Person(String name,int age) {
    48. this.name = name;
    49. this.age = age;
    50. }
    51. public String getName() {
    52. return name;
    53. }
    54. public void setName(String name) {
    55. this.name = name;
    56. }
    57. public int getAge() {
    58. return age;
    59. }
    60. public void setAge(int age) {
    61. this.age = age;
    62. }
    63. }

    运行结果:

    2022/11/21
    22 

    8.4 自定义类可序列化的3个条件

    • 自定义类要实现Serializable接口。Serializable接口是一个“标识接口”,里面没有任何方法。
    • 在类内提供一个静态常量serialVersionUID(随便给一个long型的数,正负都行)
    private(public) static final long serialVersionUID = 665132135465L; 
    •  对象的所有属性可序列化

     问题(一):那为什么java常用类可以直接序列化呢?原因很简单,因为他们在实现的时候已经满足上述两个要求,比如以File类为例:

    问题(二): 静态常量serialVersionUID的作用。

    识别java对象。 老师举了一个“哆啦A梦任意门”的例子很形象。进任意门的时候就是序列化的过程,人被打碎成“二进制流”才能传递,出任意门的时候就是反序列化的过程,二进制流又转换成人。如果有人、猪、狗同时通过任意门传递,那我们怎么知道这一段二进制流应该恢复成人还是猪还是狗呢?就是通过serialVersionUID去判断

    问题(三):当我们自定义的类实现Serializable接口后,即使我们不去显示地定义静态常量serialVersionUID,java也会帮我们自动生成。那为什么我们还要显式定义呢?

            因为一旦我们对Person进行修改,如果UID是自动生成的话,UID也会自动修改。在下面这种情况下会出错:我们已经序列化了Person类的对象,它携带着UID1,在反序列化之前,我们对Person的代码进行了改动,现在Person类的UID变成了UID2,现在进行反序列化,发现找不到UID1对应的Person类了。

            现在解释第三个条件。在上面的例子中,因为我们的Person类的属性只有基本数据类型和java常用类String,Person的对象自然可以序列化。现在,我们给Person类添加一个Arm属性。

    1. class Arm{
    2. }

    此时我们再去序列化

     

     异常会直接提醒我们,Arm不可以序列化

     所以,Arm类也要实现Serializable接口

     此外,还要注意的是,可不可以序列化看的是这个对象携带了什么属性。如果类中有Arm属性,但是对象在实例化的时候并没有Arm属性,还是可以实例化的。

     那么该对象是可以实例化的

    1. oos.writeObject(new Person("程野",15));
    2. oos.writeObject(new Person("王小虎",16));

    8.5 被static和transient修饰的属性无法序列化

    还是Person的例子,我们将age属性设为static

     然后进行序列化

     反序列化的结果:

     可以这样理解,如果Person类在进行时空穿梭前,将胳膊这一属性定义为static,那么穿梭后恢复的人没有胳膊。

    八、序列化详解

            以上介绍的实现Serializable接口是JDK自带的序列化方式,但是JDK 自带的序列化方式一般不会用 ,因为序列化效率低并且存在安全问题。

            比较常用的序列化协议有 KryoProtobuf、ProtoStuff、Hessian,这些都是基于二进制的序列化协议。还有 JSON XML 这种属于文本类序列化方式(字符直接根据编码转化成二进制流在信道中传播),可读性比较好。

    9、RandomAccessFile类

    9.1 记忆点

    (1)和前面所说的各种流不一样,RandomAccessFile类直接继承于Object类,和四个基类无瓜。

    (2)RandomAccessFile类自己既可以实现输入又可以实现输出。

            其中写操作要注意的是,前面讲的输出流进行写文件操作时,都是“整个文件的覆盖”。比如文件原来存有“今天吃排骨”,我们写入“明天”两个字,会直接把整个文件覆盖成只有“明天”两个字。而RandomAccessFile类会把文件覆盖成“明天吃排骨”。

    (3)利用seek()方法,实现多线程断点下载

    9.2 常用方法

    1. 读

    read()

    2. 写 

    从指针指到的位置开始写

    write()

    3. 找到文件指针 

    seek()

    9.3 多线程断点下载

    1. 浏览器下载:下载前只建立一个与被下载文件大小相同的空文件。如果下载因为网络中断,用户再次点击“开始”按钮,需要重新从头下载,而不是从断点继续下载。

            那什么是断点下载呢?就是下一次下载可以从断点开始,而不是从头开始。

            如何实现的呢?用一个文件指针记录断点位置。

    2. 下载工具:使用RandomAccessFile类实现多线程断点下载,这也是下载工具更快的原因。下载前建立两个文件,一个是与被下载文件大小相同的空文件,一个是记录文件指针的位置文件。

    如何实现的呢?把一个文件在逻辑上切分成4份,开4个线程同时下载。用下载文件的大小和分割的份数4可以计算出4个指针的位置,每个线程用RandomAccessFile类调用seek()函数找到4个指针的位置,分别下载。

  • 相关阅读:
    初识网络编程
    LeetCode:746. 使用最小花费爬楼梯【动态规划】
    ArcGIS笔记8_测量得到的距离单位不是米?一经度一纬度换算为多少米?
    ASP.NET Core - .NET 6 的入口文件
    CRM系统可以给销售人员带来什么?
    nacos鉴权报invalid username or password
    git实战-多分支开发-2022新项目
    【selection】 学习光标API并实现编辑区插入表情图片的功能
    Kotlin的关键字 lateinit 和 lazy
    C++核心编程——P36-友元
  • 原文地址:https://blog.csdn.net/qq_44886213/article/details/126918085