java基础io流——配角也风流(不求甚解)

96
韵呀
2018.05.30 09:35* 字数 702

本章简单介绍几个常见的io流派生。

1:数据操作流(操作基本类型数据的流)(理解)

(1)可以操作基本类型的数据
(2)流对象名称    
    DataInputStream
    DataOutputStream

代码示例:

private static void read() throws IOException {
        // DataInputStream(InputStream in)
        // 创建数据输入流对象
        DataInputStream dis = new DataInputStream(
                new FileInputStream("dos.txt"));

        // 读数据
        byte b = dis.readByte();
        short s = dis.readShort();
        int i = dis.readInt();
        long l = dis.readLong();
        float f = dis.readFloat();
        double d = dis.readDouble();
        char c = dis.readChar();
        boolean bb = dis.readBoolean();

        // 释放资源
        dis.close();

        System.out.println(b);
        System.out.println(s);
        System.out.println(i);
        System.out.println(l);
        System.out.println(f);
        System.out.println(d);
        System.out.println(c);
        System.out.println(bb);

    }

    private static void write()throws IOException {
        DataOutputStream dos = new DataOutputStream(new FileOutputStream("dos.txt"));
        dos.writeByte(10);
        dos.writeShort(100);
        dos.writeInt(1000);
        dos.writeLong(10000);
        dos.writeFloat(12.34F);
        dos.writeDouble(12.56);
        dos.writeChar('a');
        dos.writeBoolean(true);
        dos.close();
    }

2:内存操作流(理解)

用于处理临时存储信息的,程序结束,数据就从内存中消失。

(1)有些时候我们操作完毕后,未必需要产生一个文件,就可以使用内存操作流。
(2)三种
    A:ByteArrayInputStream,ByteArrayOutputStream
    B:CharArrayReader,CharArrayWriter
    C:StringReader,StringWriter

代码示例:

//ByteArray
    private static void fun1() throws IOException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        // 写数据
        for (int x = 0; x < 10; x++) {
            bos.write(("hello" + x).getBytes());
        }
        byte[] bytes = bos.toByteArray();
        ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
        int by = 0;
        while ((by = bis.read()) != -1) {
            System.out.print((char) by);
        }
    }
    //CharArray
    private static void fun2() throws IOException {
        CharArrayWriter caw = new CharArrayWriter();
        caw.write("hello");
        char[] chars =caw.toCharArray();
        CharArrayReader car = new CharArrayReader(chars);

        //car.read(chars);
        System.out.println(chars);

        int len = 0;
        while ((len = car.read()) != -1) {
            System.out.print((char)len);
        }
    }
    //String
    private static void fun3() throws IOException {
        StringWriter sw = new StringWriter();
        sw.write("hello");

        StringReader sr = new StringReader(sw.toString());
        int len = 0;
        while ((len = sr.read()) != -1) {
            System.out.print((char)len);
        }
    }

注:查看源码,其close并不起作用,所以无需关闭。

3:打印流(掌握)

    (1)字节打印流,字符打印流
    (2)特点:
        A:只操作目的地,不操作数据源
        B:可以操作任意类型的数据
        C:如果启用了自动刷新,在调用println()方法的时候,能够换行并刷新
        D:可以直接操作文件
            问题:哪些流可以直接操作文件呢?
            看API,如果其构造方法能够同时接收File和String类型的参数,一般都是可以直接操作文件的
    (3)复制文本文件
        BufferedReader br = new BufferedReader(new FileReader("a.txt"));
        PrintWriter pw = new PrintWriter(new FileWriter("b.txt"),true);
        
        String line = null;
        while((line=br.readLine())!=null) {
            pw.println(line);
        }
        
        pw.close();
        br.close();

PrintStream是OutputStream的子类,PrintWriter是Writer的子类,两者处于对等的位置上,所以它们的API是非常相似的。

区别无非一个是字节打印流,一个是字符打印流。更多的应用会在以后详细扩展。

需要注意:
字节打印流还是标准输出流的对象哦。

4:标准输入输出流(理解)

    (1)System类下面有这样的两个字段
        in 标准输入流
        out 标准输出流
    (2)三种键盘录入方式
        A:main方法的args接收参数
        B:System.in通过BufferedReader进行包装
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        C:Scanner
            Scanner sc = new Scanner(System.in);
    (3)输出语句的原理和如何使用字符流输出数据
        A:原理
            System.out.println("helloworld");
            
            PrintStream ps = System.out;
            ps.println("helloworld");
        B:把System.out用字符缓冲流包装一下使用
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

代码示例:

在scanner没有出现以前,我们是这样键盘录入的:

BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        System.out.println("请输入名字:");
        String data =br.readLine();
        System.out.println(data);
        System.out.println("请输入数字:");
        String i = br.readLine();
        System.out.println(Integer.parseInt(i));

5:随机访问流(理解)

(1)可以按照文件指针的位置写数据和读数据。
    (2)案例:
        A:写数据
        B:读数据
        C:获取和改变文件指针的位置

代码示例:

/*
 * 随机访问流:
 *      RandomAccessFile类不属于流,是Object类的子类。
 *      但它融合了InputStream和OutputStream的功能。
 *      支持对文件的随机访问读取和写入。
 *
 * public RandomAccessFile(String name,String mode):第一个参数是文件路径,第二个参数是操作文件的模式。
 *      模式有四种,我们最常用的一种叫"rw",这种方式表示我既可以写数据,也可以读取数据。
 访问模式:
 "r" 以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException。  
"rw" 打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。  
"rws" 打开以便读取和写入,对于 "rw",还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。  
"rwd" 打开以便读取和写入,对于 "rw",还要求对文件内容的每个更新都同步写入到底层存储设备。 
一般使用rw模式。
 
 */
public class RandomAccessFileDemo {

    public static void main(String[] args) throws IOException {
        // write();
        read();
    }

    private static void read() throws IOException {
        // 创建随机访问流对象
        RandomAccessFile raf = new RandomAccessFile("raf.txt", "rw");

        int i = raf.readInt();
        System.out.println(i);
        // 该文件指针可以通过 getFilePointer方法读取,并通过 seek 方法设置。
        System.out.println("当前文件的指针位置是:" + raf.getFilePointer());

        char ch = raf.readChar();
        System.out.println(ch);
        System.out.println("当前文件的指针位置是:" + raf.getFilePointer());

        String s = raf.readUTF();
        System.out.println(s);
        System.out.println("当前文件的指针位置是:" + raf.getFilePointer());

        // 我不想重头开始了,我就要读取a,怎么办呢?
        raf.seek(4);
        ch = raf.readChar();
        System.out.println(ch);
    }

    private static void write() throws IOException {
        // 创建随机访问流对象
        RandomAccessFile raf = new RandomAccessFile("raf.txt", "rw");

        // 怎么玩呢?
        raf.writeInt(100);
        raf.writeChar('a');
        raf.writeUTF("中国");

        raf.close();
    }
}

6:合并流(理解)

    (1)把多个输入流的数据写到一个输出流中。
    (2)构造方法:
        A:SequenceInputStream(InputStream s1, InputStream s2) 
        B:SequenceInputStream(Enumeration<? extends InputStream> e)

合并复制1:

/*复制:
 * 以前的操作:
 * a.txt -- b.txt
 * c.txt -- d.txt
 *
 * 现在想要:
 * a.txt+b.txt -- ab.txt
 */
public class SequenceInputStreamDemo {
    public static void main(String[] args) throws IOException {
        FileInputStream f1 = new FileInputStream("a.txt");
        FileInputStream f2 = new FileInputStream("b.txt");
        SequenceInputStream sis = new SequenceInputStream(f1,f2);

        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("ab.txt"));
        byte[] bytes = new byte[1024];
        int len = 0 ;
        while ((len = sis.read(bytes)) != -1) {
            bos.write(bytes,0,len);
        }

        bos.close();
        sis.close();

    }
}

合并复制2:

/*
 * 以前的操作:
 * a.txt -- b.txt
 * c.txt -- d.txt
 * e.txt -- f.txt
 *
 * 现在想要:
 * a.txt+b.txt+c.txt -- abc.txt
 */
public class SequenceInputStreamDemo2 {
    public static void main(String[] args) throws IOException {
        Vector<InputStream> v = new Vector<>();
        InputStream s1 = new FileInputStream("a.txt");
        InputStream s2 = new FileInputStream("b.txt");
        InputStream s3 = new FileInputStream("c.txt");
        v.add(s1);
        v.add(s2);
        v.add(s3);
        Enumeration<InputStream> en = v.elements();
        SequenceInputStream sis = new SequenceInputStream(en);
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("abc.txt"));
        byte[] bytes = new byte[1024];
        int len = 0 ;
        while ((len = sis.read(bytes)) != -1) {
            bos.write(bytes,0,len);
        }

        bos.close();
        sis.close();


    }
}

7:序列化流(理解)

    (1)可以把对象写入文本文件或者在网络中传输
    (2)如何实现序列化呢?
        让被序列化的对象所属类实现序列化接口。
        该接口是一个标记接口。没有功能需要实现。
    (3)注意问题:
        把数据写到文件后,在去修改类会产生一个问题。
        如何解决该问题呢?
            在类文件中,给出一个固定的序列化id值。
            而且,这样也可以解决黄色警告线问题
    (4)面试题:
        什么时候序列化? 
        如何实现序列化?
        什么是反序列化?

代码示例:

Person.java

/*
 * NotSerializableException:未序列化异常
 *
 * 类通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。
 * 该接口居然没有任何方法,类似于这种没有方法的接口被称为标记接口。
 *
 * java.io.InvalidClassException:
 * cn.itcast_07.Person; local class incompatible:
 * stream classdesc serialVersionUID = -2071565876962058344,
 * local class serialVersionUID = -8345153069362641443
 *
 * 为什么会有问题呢?
 *      Person类实现了序列化接口,那么它本身也应该有一个标记值。
 *      这个标记值假设是100。
 *      开始的时候:
 *      Person.class -- id=100
 *      wirte数据: oos.txt -- id=100
 *      read数据: oos.txt -- id=100
 *
 *      现在:
 *      Person.class -- id=200
 *      wirte数据: oos.txt -- id=100
 *      read数据: oos.txt -- id=100
 *
 * 我们要知道的是:
 *      看到类实现了序列化接口的时候,要想解决黄色警告线问题,就可以自动产生一个序列化id值。
 *      而且产生这个值以后,我们对类进行任何改动,它读取以前的数据是没有问题的。
 *
 * 注意:
 *      我一个类中可能有很多的成员变量,有些我不想进行序列化。请问该怎么办呢?
 *      使用transient关键字声明不需要序列化的成员变量
 */
public class Person implements Serializable {
    private static final long serialVersionUID = 5816344743154801933L;
    private String name;

    // private int age;

    private transient int age;

    // int age;

    public Person() {
        super();
    }

    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

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

ObjectStreamDemo.java

/*
 * 序列化流:把对象按照流一样的方式存入文本文件或者在网络中传输。对象 -- 流数据(ObjectOutputStream)
 * 反序列化流:把文本文件中的流对象数据或者网络中的流对象数据还原成对象。流数据 -- 对象(ObjectInputStream)
 */
public class ObjectStreamDemo {
    public static void main(String[] args) throws IOException,
            ClassNotFoundException {
        // 由于我们要对对象进行序列化,所以我们先自定义一个类
        // 序列化数据其实就是把对象写到文本文件
        // write();

        read();
    }

    private static void read() throws IOException, ClassNotFoundException {
        // 创建反序列化对象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
                "oos.txt"));

        // 还原对象
        Object obj = ois.readObject();

        // 释放资源
        ois.close();

        // 输出对象
        System.out.println(obj);
    }

    private static void write() throws IOException {
        // 创建序列化流对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(
                "oos.txt"));

        // 创建对象
        Person p = new Person("林青霞", 27);

        // public final void writeObject(Object obj)
        oos.writeObject(p);

        // 释放资源
        oos.close();
    }
}

8:Properties(理解)

    (1)是一个集合类,Hashtable的子类
    (2)特有功能
        A:public Object setProperty(String key,String value)
        B:public String getProperty(String key)
        C:public Set<String> stringPropertyNames()
    (3)和IO流结合的方法
        把键值对形式的文本文件内容加载到集合中
        public void load(Reader reader)
        public void load(InputStream inStream)

        把集合中的数据存储到文本文件中
        public void store(Writer writer,String comments)
        public void store(OutputStream out,String comments)
    (4)案例:
        写一个程序实现控制猜数字小游戏程序不能玩超过5次(用Properties控制玩的次数)

Properties当做map来用:

Properties prop = new Properties();

        // 添加元素
        prop.put("it002", "hello");
        prop.put("it001", "world");
        prop.put("it003", "java");


        // 遍历集合
        Set<Object> set = prop.keySet();
        for (Object key : set) {
            Object value = prop.get(key);
            System.out.println(key + "---" + value);
        }

properties的特殊功能:

// 创建集合对象
        Properties prop = new Properties();

        // 添加元素
        prop.setProperty("张三", "30");
        prop.setProperty("李四", "40");
        prop.setProperty("王五", "50");

        // public Set<String> stringPropertyNames():获取所有的键的集合
        Set<String> set = prop.stringPropertyNames();
        for (String key : set) {
            String value = prop.getProperty(key);
            System.out.println(key + "---" + value);
        }

properties和io流的联合:

private static void myStore() throws IOException {
        // 创建集合对象
        Properties prop = new Properties();

        prop.setProperty("林青霞", "27");
        prop.setProperty("武鑫", "30");
        prop.setProperty("刘晓曲", "18");

        //public void store(Writer writer,String comments):把集合中的数据存储到文件
        Writer w = new FileWriter("name1.properties");
        prop.store(w, null);//第二个参数是列表的描述
        w.close();
    }

    private static void myLoad() throws IOException {
        Properties prop = new Properties();

        // public void load(Reader reader):把文件中的数据读取到集合中
        // 注意:这个文件的数据必须是键值对形式
        Reader r = new FileReader("name1.properties");
        prop.load(r);
        r.close();

        System.out.println("prop:" + prop);
    }

案例猜数字游戏:

游戏源码:

public class GuessNumber {
    private GuessNumber() {
    }

    public static void start() {
        // 产生一个随机数
        int number = (int) (Math.random() * 100) + 1;

        // 定义一个统计变量
        int count = 0;

        while (true) {
            // 键盘录入一个数据
            Scanner sc = new Scanner(System.in);
            System.out.println("请输入数据(1-100):");
            int guessNumber = sc.nextInt();

            count++;

            // 判断
            if (guessNumber > number) {
                System.out.println("你猜的数据" + guessNumber + "大了");
            } else if (guessNumber < number) {
                System.out.println("你猜的数据" + guessNumber + "小了");
            } else {
                System.out.println("恭喜你," + count + "次就猜中了");
                break;
            }
        }
    }
}

用properties将游戏次数记录在文本中:

public class PropertiesTest2 {
    public static void main(String[] args) throws IOException {


        Reader reader =new FileReader("count.properties");
        Properties pro = new Properties();
        pro.load(reader);
        reader.close();

        String count =pro.getProperty("count");
        int num =Integer.parseInt(count);
        if (num>=3){
            System.out.println("您已经免费玩过三次了,再玩请付费!");
        }
        else {
            num++;
            pro.setProperty("count",String.valueOf(num));
            Writer writer = new FileWriter("count.properties");
            pro.store(writer,null);
            writer.close();
            GuessNumber.start();
        }

    }
}

一般的单机版游戏,都会有.properties文件来记录本地游戏状态。当然实际情况中这个文件是加密的。在后面的框架学习中,我们有时候会数据库的配置放到.properties文件中并且加密。

9:NIO(了解)

    (1)JDK4出现的NIO,对以前的IO操作进行了优化,提供了效率。但是大部分我们看到的还是以前的IO
    (2)JDK7的NIO的使用  
        Path:路径
        Paths:通过静态方法返回一个路径
        Files:提供了常见的功能
            复制文本文件
            把集合中的数据写到文本文件

代码示例:新型复制:

/*
 * nio包在JDK4出现,提供了IO流的操作效率。
 *
 * JDK7的之后的nio:
 * Path:路径
 * Paths:有一个静态方法返回一个路径
 *      public static Path get(URI uri)
 * Files:提供了静态方法供我们使用
 *      public static long copy(Path source,OutputStream out):复制文件
 *      public static Path write(Path path,Iterable<? extends CharSequence> lines,Charset cs,OpenOption... options)
 */
public class NIODemo {
    public static void main(String[] args) throws IOException {
        //复制
        //Files.copy(Paths.get("a.txt"),new FileOutputStream("newa.txt"));

        ArrayList<String> array = new ArrayList<String>();
        array.add("hello");
        array.add("world");
        array.add("java");
        Files.write(Paths.get("f.txt"),array, Charset.defaultCharset());
    }
}

现在jdk已经出到9了,io流也有了更多的变化。给我最大的感受是操作更方便了。有时间会整理一下java8,java9的一些新特性。

io流的基础回顾就告一段落了,浅尝辄止。无论是做学问,做科研都不可能一下子做到很全,很好。循序渐进,实践中慢慢总结。io流用到的地方很多,上传下载,传输,设计模式等。基础打扎实了,才能玩更高端的。

以上是本人学习笔记整理,重温java经典,欢迎各位同道中人批评指正。

源码码云地址:
https://gitee.com/stefanpy/java

梦回io流完整目录:

java基础io流——File告白(重温经典)

java基础io流——OutputStream和InputStream的故事(温故知新)

java基础io流——字符流的变革(深入浅出)

java基础io流——配角也风流(不求甚解)

java基础
Web note ad 1