JavaSE高级开发之I/O流(二)

1. 内存操作流

除了文件之外,I/O的操作也可以发生在内存之中,这种流被称为内存操作流。文件流不管最后这个文件数据是否会被保留都会产生一个文件数据,而内存流是跟内存有关的,不需要关联文件。如果有个需求是要进行I/O处理,但是又不希望产生临时文件,这种情况下就可以使用内存流。

1.1 基本使用
内存流的分类:
1. 字节内存流:ByteArrayInputStream、 ByteArrayOutputStream
2. 字符内存流:CharArrayReader、 CharArrayWriter
  • 通过内存流实现小写转大写:
package com.file;

import java.io.*;

//将字符串中的字符转换成大写,要求使用内存流
public class TestMemoryStream {

    public static void main(String[] args) {

        String message = "hello world";
        byte[] messageBytes = message.getBytes();

        //内存操作,不需要捕获异常
        //in out 都是内存流,数据都存在内存中
        ByteArrayInputStream in = new ByteArrayInputStream(messageBytes);
        ByteArrayOutputStream out = new ByteArrayOutputStream();

        try {
            int c = -1;
            while((c = in.read()) != -1) {
                //转大写
                c = c-32;
                out.write(c);
            }
            out.flush();

            //输出流里面没有把输出流变成字节数组的方法,是ByteArray里面有的
            //byte[] newMessage = ((ByteArrayOutputStream) out).toByteArray();
            byte[] newMessage = out.toByteArray();
            System.out.println(new String(newMessage));
            
        } catch (IOException e) {

        }
    }
}

在这里插入图片描述
这个时候发生了I/O操作,但是没有文件产生,可以理解为一个临时文件处理。

1.2 文件合并
package com.file;

import java.io.*;

public class MemoryStreamMerge {

    public static void main(String[] args) {

        //data-1.txt + data-2.txt = data.txt

        /*步骤:
         * 1. data-1.txt 复制到内存的输出流
         * 2. data-2.txt 复制到内存的输出流
         * 3. 内存的输出流 -> byte[]字节流
         * 4. byte[] -> 输出到文件的输出流
         */

        File part1 = new File("D:" + File.separator + "test" + File.separator + "data-1.txt");
        File part2 = new File("D:" + File.separator + "test" + File.separator + "data-2.txt");
        File part = new File("D:" + File.separator + "test" + File.separator + "data.txt");

        try(FileInputStream in1 = new FileInputStream(part1);
            FileInputStream in2 = new FileInputStream(part2);
            ByteArrayOutputStream out1 = new ByteArrayOutputStream();
            FileOutputStream out2 = new FileOutputStream(part)
        ) {

            byte[] buff = new byte[1024];
            int len = -1;
            while((len = in1.read(buff)) != -1) {
                out1.write(buff, 0, len);
            }
            while((len = in2.read(buff)) != -1) {
                out1.write(buff, 0, len);
            }
            out1.flush();

            byte[] data = out1.toByteArray();
            out2.write(data);
            out2.flush();

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

    }
}
  • 既然是在内存中读写,内存流速度虽然快,但是受制于内存大小,适合于处理批量的少量数据。
/**源码:
 * Closing a <tt>ByteArrayOutputStream</tt> has no effect. The methods in
 * this class can be called after the stream has been closed without
 * generating an <tt>IOException</tt>.
 */
public void close() throws IOException {
}

上面是内存流的源码,看出来内存流可以关闭也可以不关闭,因为close()里面没有任何代码。

2. 打印流

PrintStream可以称之为OutputStream的加强版。OutputStream只能放字节或者字节数组,如果操作的不是二进制数据,就需要getBytes,所以不是很方便,总结下来其缺点有两个:
① 所有的数据必须转换为字节数组。
② 如果要输出的是int或者double等类型就不方便了。

2.1 基本使用

打印流设计的主要目的是为了解决OutputStream的设计问题,其本质不会脱离OutputStream

package com.file;

import java.io.*;

/**
 * 目的:为了OutputStream的输出更加简单
 * 类似代理模式,但是不完全遵循
 * 代码复用性强
 */
public class PrintUtil {

    private final OutputStream out;

    public PrintUtil(OutputStream out) {
        this.out = out;
    }

    public void print(String value) {
        try {
            this.out.write(value.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void println(String value) {
        this.print(value);
        try {
            this.out.write((int)'\n');
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void print(int value) {
        this.println(String.valueOf(value));
    }

    public static void main(String[] args) {
        try(FileOutputStream out = new FileOutputStream("D:" + File.separator + "test"
                + File.separator + "printutil.txt")
        ) {

            PrintUtil printUtil = new PrintUtil(out);
            printUtil.print("hello");
            printUtil.print(" world");
            printUtil.print(22);
            printUtil.print(10);
            /*
             * hello world
             * 22
             * 10
             */

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

经过简单处理之后,让OutputStream的功能变的更加强大了,其实本质就只是对OutputStream的功能做了一个封装而已。

  • 但是上面仅仅是我们自己定义的打印流,还不能满足全部的需求。
2.2 系统提供的打印流
1. 字节打印流:PrintStream
2. 字符打印流:PrintWriter

打印流的设计属于装饰设计模式:核心依然是某个类的功能,但是为了得到更好的操作效果,让其支持的功能更多一些。

  • 不光支持数字、字符、字符串和字符数组等,还可以支持格式化输出
    格式化输出:printf(String format, Object ... args)
package com.file;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;

public class TestPrintStream {

    public static void main(String[] args) {

        String file = "D:" + File.separator + "test"
                + File.separator + "printStream.txt";

        //字符的打印流
        try(PrintWriter writer = new PrintWriter(file)) {

            writer.write("姓名");
            writer.write("张三");
            writer.write("\n");
            writer.write(50);
            writer.write("\n");
            writer.write(new char[]{'A', 'B', 'C'});

            //链式调用
            writer.append("A").append("B").append("C");

            //格式化输出,String里面也有个格式化输出的方法, System.out.printf也是
            writer.printf("姓名:%s 年龄:%d 身高:%.2fcm \n", "张三", 22, 180.25F);
            String str = String.format("姓名:%s 年龄:%d 身高:%.2fcm", "张三", 22, 180.25F);
            writer.println(str);

            writer.flush();
        } catch(IOException e) {

        }

    }
}

3. System对I/O的支持

实际上我们一直在使用的系统输出System.out.println就是利用了I/O流的模式完成。

/*
System:类
out:静态属性,PrintStream类型
println:out属性的一个对象的方法
 */
System.out.println("hello world");
  • System类中定义了三个操作的常量:
    ① 标准输出(显示器) :public final static PrintStream out
    ② 错误输出:public final static PrintStream err
    ③ 标准输入(键盘):public final static InputStream in
3.1 系统输出

系统输出一共有两个常量:outerr,并且这两个常量表示的都是PrintStream类的对象:
out输出的是希望用户能看到的内容
err输出的是不希望用户看到的内容

package com.file;

public class SystemIO {

    public static void main(String[] args) {

        try {
            //String -> Integer
            Integer.parseInt("abc");
        } catch(NumberFormatException e) {
            System.out.println(e.getMessage());
            System.err.println(e.getMessage());
        }
	}
}

在这里插入图片描述
但是这两种输出在实际的开发之中都没用了,取而代之的是 “ 日志 ” 。

  • System.err只是作为一个保留的属性而存在,现在几乎用不到。唯一可能用得到的就是System.out

  • 由于System.outPrintStream的实例化对象,而PrintStream又是OutputStream的子类,所以可以直接使用System.out直接为OutputStream实例化对象,也就是说如果要得到输出到控制台或屏幕的对象,选择System.out

  • 使用System.outOutputStream实例化:

package com.file;

import java.io.*;

public class SystemIO {

    public static void main(String[] args) {
    
        //输出
        PrintStream printStream = System.out;
        //PrintStream是OutputStream的子类
        OutputStream out = System.out;
        try {
            out.write("hello".getBytes());
        } catch(IOException e) {
            e.printStackTrace();
        }
	}
}

在这里插入图片描述

3.2 系统输入

System.in对应的类型是InputStream,而这种输入流指的是由用户通过键盘进行输入(用户输入)。java本身并没有直接的用户输入处理,如果要想实现这种操作,必须使用java.io的模式来完成。

  • 使用System.in实现数据输入:
package com.file;

import java.io.*;

public class SystemIO {

    public static void main(String[] args) {
    	
    	//输入
        InputStream in = System.in;
        try {
            byte[] buff = new byte[5];
            int len = in.read(buff);
            System.out.println("读取了"+len+"字节, 内容是:"+new String(buff, 0, len));
        } catch (IOException e) {
            e.printStackTrace();
        }
	
	}
}

在这里插入图片描述

  • 以上的程序本身有一个致命的问题,核心点在于:开辟的字节数组长度固定,如果现在输入的长度超过了字节数组长度,那么只能够接收部分数据。这个时候是由于一次读取不完所造成的问题,所以此时最好的做法是引入内存操作流来进行控制,这些数据先保存在内存流中而后一次取出。
package com.file;

import java.io.*;

public class SystemIO {

    public static void main(String[] args) {
    	
    	InputStream in = System.in;
        ByteArrayOutputStream out = new ByteArrayOutputStream();

        try {
            byte[] buff = new byte[5];
            int len = -1;
            while((len = in.read(buff)) != -1) {
                out.write(buff, 0, len);
                if(len < buff.length) {
                    //已经读到最后一批
                    break;
                }
            }
            byte[] data = out.toByteArray();
            System.out.println("读取了"+data.length+"字节, 内容是:"+new String(data));
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

在这里插入图片描述

  • 至于为什么命名输入的只有8个字节,却显示读取了9个字节的问题,是因为输入完成之后在键盘上敲下回车,所有的键盘操作都是会被记录下来的。

现在虽然实现了键盘输入数据的功能,但是整体的实现逻辑过于混乱了,即java提供的System.in并不好用,还要结合内存流来完成,导致复杂度很高。所以也就有了下面两种输入流的产生。

4. 两种输入流

4.1 BufferedReader类

BufferedReader类属于一个缓冲的输入流,而且是一个字符流的操作对象。

1. 字节缓冲流:BufferedInputStream
2. 字符缓冲流:BufferedReader

之所以选择BufferedReader类操作是因为在此类中提供有如下方法:
按行读取,回车换行:String readLine() throws IOException

  • 利用BufferedReader实现键盘输入:
package com.file;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

public class Buffer {

    public static void main(String[] args) {

        //字节输入流
        InputStream inputStream = System.in;

        //字符输入流(字节流转字符流)
        InputStreamReader inputStreamReader = new InputStreamReader(inputStream);

        //缓冲的字符输入流
        BufferedReader reader = new BufferedReader(inputStreamReader);

        //交互式的反复输入
        while(true) {
            System.out.println("请输入名字:");
            try {
                String line = reader.readLine();
                System.out.println(line);
                if(line.equals("quit")) {
                    break;
                }
            } catch(IOException e) {
                e.printStackTrace();
            }
        }

    }
}

在这里插入图片描述
以上操作形式是java十多年前输入的标准格式,但是时过境迁,这个类也淹没在历史的潮流之中,被JDK1.5提供的java.util.Scanner类所取代。

4.2 java.util.Scanner类

Scanner是一个专门进行输入流处理的程序类,利用这个类可以方便处理各种数据类型,同时也可以结合正则表达式进行各项处理,在这个类中主要关注以下方法:
① 判断是否有指定类型数据:public boolean hasNextXxx()
② 取得指定类型的数据:public 数据类型 nextXxx()
③ 定义分隔符:public Scanner useDelimiter(Pattern pattern)

  • 使用Scanner实现数据输入:
package com.file;

import java.util.Scanner;

public class TestScanner {

    public static void main(String[] args) {

        Scanner scanner = new Scanner(System.in);

        System.out.println("请输入数据:");
        if(scanner.hasNext()) {
            System.out.println("输入的内容:"+scanner.next());
        }

	}
}

在这里插入图片描述

  • 接收其他类型数据:
  • 使用Scanner还可以接收各种数据类型,并且帮助用户减少转型处理。
package com.file;

import java.util.Scanner;

public class TestScanner {

    public static void main(String[] args) {
		
		Scanner scanner = new Scanner(System.in);
		
		System.out.println("请输入年龄:");
        if(scanner.hasNextInt()) {
            System.out.println("输入的内容是int");
        } else {
            System.out.println("输入的内容不是int");
        }

	}
}

在这里插入图片描述

  • 对接收的数据类型使用正则表达式判断:
package com.file;

import java.util.Scanner;

public class TestScanner {

    public static void main(String[] args) {

        Scanner scanner = new Scanner(System.in);
		
		//正则表达式
        System.out.println("请输入生日:");
        if(scanner.hasNext("\\d{4}-\\d{2}-\\d{2}")) {
            System.out.println("输入的内容:"+scanner.next());
        } else {
            System.out.println("输入的内容不是生日格式");
        }

	}
}

在这里插入图片描述

  • Scanner读取文件操作:
  • 使用Scanner本身能够接收的是一个InputStream对象,那么也就意味着可以接收任意输入流,例如:文件输入流,Scanner完美的替代了BufferedReader,而且更好的实现了InputStream的操作。
package com.file;

import java.io.IOException;
import java.nio.file.Paths;
import java.util.Scanner;

public class TestScanner {

    public static void main(String[] args) {

		//读取文件(针对字符)
        try(Scanner scanner = new Scanner(Paths.get("D:", "test", "data.txt"))) {
            //比File.separater更方便

            scanner.useDelimiter("\n");
            while(scanner.hasNext()) {
                System.out.println(scanner.next());
            }

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


    }
}

在这里插入图片描述

  • 总结:
    PrintStream 解决的是 OutputStream 类的缺陷,BufferedReader 解决的是 InputStream 类的缺陷。而 Scanner 则解决的是 BufferedReader 类的缺陷(替换了BufferedReader类)。

5. 序列化与反序列化

 - 序列化(Java Object -> byte[]):ObjectOutputStream
 - 反序列化(byte[] -> Java Object):ObjectInputStream
5.1 序列化技术

概念:把Java对象变成byte数组(二进制流),主要是用于网络传输。

  • 一个类的实例化对象要能够进行序列化必须实现序列化(Serializable)接口!
  • ObjectOutputStream里面的方法有:
    ObjectOutputStream(OutputStream out)
    writeObject(Object obj),写对象
package com.serializable;

import java.io.*;

class Person implements Serializable {

    private String name;
    private Integer age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

public class TestSerializable {

    public static void main(String[] args) {

        Person person1 = new Person();
        person1.setName("张三");
        person1.setAge(22);

        //序列化
        //Java Object  ->  byte[]
        //可以写到file或者byte[]里面
        try(ByteArrayOutputStream stream = new ByteArrayOutputStream();
            ObjectOutputStream out = new ObjectOutputStream(stream)
        ) {

            out.writeObject(person1);
            out.flush();
            //data对象变成的二进制流
            byte[] data = stream.toByteArray();
            System.out.println(new String(data));

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

}

在这里插入图片描述

5.1 反序列化技术

概念:把byte数组转换成对象。

  • ObjectInputStream里面的方法有:
    ObjectInputStream(InputStream in)
    readObject(Object obj),读对象
package com.serializable;

import java.io.*;

class Person implements Serializable {

    private String name;
    private Integer age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

public class TestSerializable {

    public static void main(String[] args) {

        Person person1 = new Person();
        person1.setName("张三");
        person1.setAge(22);
        System.out.println("person1:"+person1);

        //序列化
        //Java Object  ->  byte[]
        //可以写到file或者byte[]里面
        try(ByteArrayOutputStream stream = new ByteArrayOutputStream();
            ObjectOutputStream out = new ObjectOutputStream(stream)
        ) {

            out.writeObject(person1);
            out.flush();
            //data对象变成的二进制流
            byte[] data = stream.toByteArray();

            //反序列化
            //byte[] -> java Object
            try(ByteArrayInputStream instream = new ByteArrayInputStream(data);
                    ObjectInputStream in = new ObjectInputStream(instream)
            ) {

                Object returnValue = in.readObject();
                System.out.println(returnValue.getClass());

                Person person2 = (Person) returnValue;
                System.out.println("Person2:" + person2);

                //前者Person1是通过实例化new出来的,在堆上分配出来的
                //后者person2是通过二进制流变成的Java对象
                System.out.println(person1 == person2);

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

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

    }

}

在这里插入图片描述

5.3 通过网络传输
package com.serializable;

import java.io.*;

class Person implements Serializable {

    private String name;
    private Integer age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

public class TestSerializable {

    public static void main(String[] args) {

        Person person1 = new Person();
        person1.setName("张三");
        person1.setAge(22);

        try(FileOutputStream stream = new FileOutputStream("D:" + File.separator + "test" + File.separator + "person.obj");
            ObjectOutputStream out = new ObjectOutputStream(stream)
        ) {

            out.writeObject(person1);
            out.flush();

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

假设我把person.obj复制粘贴到了D:\\test目录下,相当于通过网络传输给了别人,那么别人如何打开这个文件并获取到其中的内容呢?

package com.serializable;

import java.io.*;

class Person implements Serializable {

    private String name;
    private Integer age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

public class TestSerializable {

    public static void main(String[] args) {

        try(FileInputStream stream = new FileInputStream("D:" + File.separator + "person.obj");
            ObjectInputStream in = new ObjectInputStream(stream)
        ) {

            Object returnValue = in.readObject();
            Person person = (Person) returnValue;
            System.out.println(person);

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

    }
}

在这里插入图片描述

5.4 transient关键字

Serializable默认会将对象中所有属性进行序列化保存,如果现在某些属性(比如密码)不希望被保存了,那么就可以使用transient关键字,即被transient修饰过的属性不再参与序列化。

package com.serializable;

import com.sun.corba.se.impl.orb.PropertyOnlyDataCollector;

import java.io.*;

class Person implements Serializable {

    private String name;
    private Integer age;
    private transient String password;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", password='" + password + '\'' +
                '}';
    }
}

public class TestSerializable {
    public static void main(String[] args) {

        Person person1 = new Person();
        person1.setName("李四");
        person1.setAge(20);
        person1.setPassword("abc123");

		//序列化
        try(FileOutputStream stream = new FileOutputStream("D:" + File.separator + "person.obj");
            ObjectOutputStream out = new ObjectOutputStream(stream1)
        ) {

            out.writeObject(person1);
            out.flush();

			//反序列化
            try(FileInputStream inStream = new FileInputStream("D:" + File.separator + "person.obj");
            ObjectInputStream in = new ObjectInputStream(inStream)
            ) {

                Object returnValue = in.readObject();
                Person person2 = (Person) returnValue;
                System.out.println(person2);


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


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

    }
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值