Java I/O流复习(三)—转换流、内存流、打印流、对象流、RandomAccessFile类、装饰者模式

Java I/O流复习(三)

1.转换流(重点掌握)

字节流转字符流,称作转换流,包括:

解决文件的乱码,即编码和解码不统一,或者是字节缺失,长度不一
  1. InputStreamReader—> 将字节流转换为字符流。是字节流通向字符流的桥梁。如果不指定字符集编码,该解码过程将使用平台默认的字符编码,如:GBK/UTF-8。
  2. OutputStreamWriter—> 将字节流转换为字符流。是字节流通向字符流的桥梁。如果不指定字符集编码,该解码过程将使用平台默认的字符编码,如:GBK/UTF-8。

1.1 InputStreamReader的构造方法

  1. InputStreamReader(InputStream in);//构造一个默认编码集的InputStreamReader类。
  2. InputStreamReader(InputStream in,String charsetName);构造一个指定编码集的InputStreamReader类。

1.2 InputStreamReader的使用(解码)

try {
    File file = new File("test.txt");
    FileInputStream fis = new FileInputStream(file);
    InputStreamReader isr = new InputStreamReader(fis);
    int read = isr.read();
    //char cbuf[]=new char[512];
    //isr.read(cbuf);
    System.out.println((char)read);
    isr.close();
}...

1.3 OutputStreamWriter的构造方法

  1. OutputStreamWriter(OutputStream out);构造一个默认编码集的OutputStreamWriter类
  2. OutputStreamWriter(OutputStream out,String charsetName);构造一个指定编码集的OutputStreamWriter类。

1.4 OutputStreamWriter的使用(编码)

try {
    File file = new File("test.txt");
    FileOutputStream fos = new FileOutputStream(file);
    OutputStreamWriter osw = new OutputStreamWriter(fos);
    String str="育知同创";
    osw.write(str);//直接写入字符串
    //osw.write(str.toCharArray());//写入 char 数组
    osw.flush();
    osw.close();
}...

2 内存流(重点掌握)

2.1 什么是内存流

当输出流的目的,和输入流的源是内存时,这样的流称之为内存流。(就是将数据写入RAM)

2.2 内存流的构造方法

  1. ByteArrayInputStream(byte buf[]); 创建一个 ByteArrayInputStream 并把指定该输入流的数据源buf[]。
  2. ByteArrayOutputStream(); 创建一个 ByteArrayOutputStream 并把分配一个32字节(默认大小)的缓冲区。
  3. ByteArrayOutputStream(int size); 创建一个 ByteArrayOutputStream 并分配自定 size 字节的缓冲区。

2.3 读取内存数据和写入到内存数据

2.3.1 读取内存数据
try {
    String testContent = "ABCDEFG";//程序运行的时候 这数据本身就在内存,
    ByteArrayInputStream bais = new ByteArrayInputStream(testContent.getBytes());//创建内存输入流,指定要读取的数据 byte[]
    int read;
    while ((read = bais.read()) != -1) {//和普通流读取字节是一样的(也可以嵌套管道)
        System.out.println((char) read);
    }
    bais.close();//关闭流,释放内存资源
}...
2.3.2 写入数据到内存(主要)
try {
    String testContent = "ABCDEFG";
    ByteArrayOutputStream baos = new ByteArrayOutputStream();//创建内存输出流,把数据写入到内存中
    baos.write(testContent.getBytes());//和普通的输出流写输入一样,(也可以嵌套管道)
    baos.flush();
    baos.close();
}...

2.4 ByteArrayOutputStream 常用方法:toByteArray(), toString()

  1. toByteArray() 方法;是将 ByteArrayOutputStream 对象所写入到内存的数据 转换成 byte[] 返回。
  2. toString() 方法 ;是将 ByteArrayOutputStream 对象所写入到内存的数据 转换成 String 返回。

提示:内存流 除了 ByteArrayInputStream 与 ByteArrayOutputStream 主要处理字节数据之外,对应的还有:

  • CharArrayReader 与 CharArrayWriter 主要处理字符数组。
  • StringReader 与 StringWriter 主要处理字符串。

使用方式大同小异。

3. 打印流(了解)

在整个IO包中,打印流是输出信息最方便的类,主要包括字节打印流(PrintStream)和字符打印流(PrintWriter)。打印流提供了非常方便的打印功能,可以打印任何的数据类型。如:小数、整数、字符串等。

PrintStream和PrintWriter都属于输出流,分别针对输出字节和字符。

PrintStream和PrintWriter提供了重载的print()、println()方法用于多种数据类型的输出。

PrintStream和PrintWriter不会抛出异常,用户通过检测错误状态获取错误信息。

PrintStream和PrintWriter有自动flush 功能。

3.1 打印流构造方法

  1. PrintStream 字节打印流
    1. PrintStream(OutputStream out);获得指定输出流的字节打印流对象。
    2. PrintStream(OutputStream out, boolean auotflush);获得指定输出流的字节打印流对象。autoflush 是否自动刷新。
    3. PrintStream(OutputStream out, boolean auotflush, String encoding);获得指定输出流的字节打印流对象。autoflush 是否自动刷新。encoding 指定字符编码。
    4. 等…
  2. PrintWriter 字符打印流
    1. PrintWriter(OutputStream out);获得指定输出流的字符打印流对象。
    2. PrintWriter(OutputStream out, boolean autoflush);获得指定输出流的字符打印流对象。autoflush 是否自动刷新。
    3. PrintWriter(Writer out);获得指定字符输出流的字符打印流对象。
    4. PrintWriter(Writer out, boolean autoflush);获得指定字符输出流的字符打印流对象。autoflush 是否自动刷新。
    5. 等…

3.2 打印流的常用操作:print(), println()

  1. print();不带换行的打印输出。

    try {
    File file = new File("test.txt");
    FileOutputStream fos = new FileOutputStream(file);
    PrintWriter ps = new PrintWriter(fos,true);
    ps.print("天青色等烟雨,");
    ps.print("而我在等你。");
    ps.print("炊烟袅袅升起,");
    ps.print("晕开了结局。");
    ps.close(); 
    }...

    效果:

  2. println();每打印出一行数据后,执行换行。

    try {
    File file = new File("test.txt");
    FileOutputStream fos = new FileOutputStream(file);
    PrintWriter ps = new PrintWriter(fos,true);
    ps.println("天青色等烟雨,");
    ps.println("而我在等你。");
    ps.println("炊烟袅袅升起,");
    ps.println("晕开了结局。");
    ps.close(); 
    }...

    效果:

4. 对象流(了解)

4.1 对象流

​ ObjectInputStream ObjectOutputStream类分别是InputStream和OutputStream的子类,对象输出流使用writeObject(Object obj)方法,将一个对象obj写入到一个文件,使用readObject()读取一个对象。

用的少,因为数据库用途大

构造方法:

  1. ObjectInputStream (InputStream in)
  2. ObjectOutputStream(OutputStream out)

代码示例:

  1. 将对象写入文件:

    //'序列化'的对象写入文件
    OutputStream outputStream = new FileOutputStream(file);
    ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
    objectOutputStream.writeObject(Object obj);
    objectOutputStream.close();
  2. 从文件读取对象:

    //序列化读取对象
    InputStream inputStream = new FileInputStream(file);
    ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
    Object obj = objectInputStream.readObject();
    objectInputStream.close();

注意:当使用对象流写入或者读取对象的时候,必须保证该对象是序列化的,这样是为了保证对象能够正确的写入文件,并能够把对象正确的读回程序。

什么是对象序列化?

4.2 对象序列化

​ 所谓的对象的序列化就是将对象转换成二进制数据流的一种实现手段,通过将对象序列化,可以方便的实现对象的传输及保存。在Java中提供了ObejctInputStream 和ObjectOutputStream这两个类用于序列化对象的操作。用于存储和读取对象的输入输出流类,要想实现对象的序列化需要实现Serializable接口,但是Serializable接口中没有定义任何的方法,仅仅被用作一种标记,以被编译器作特殊处理。

package com.yztc.main;

import java.io.Serializable;
//实现了Serializable接口。序列化
public class Student implements Serializable {
    //由编译器自动生成,用来解决不同的版本之间的序列化问题。 
    private static final long serialVersionUID = -79485540193100816L;

    private int age;
    private String name;

    public int getAge() {
        return age;
    }

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

    public String getName() {
        return name;
    }

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

    public Student() {
        super();
    }

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

}

4.3 transient

  1. 一旦变量被transient修饰,变量将不再是对象持久化(写到磁盘里持久保存)的一部分,该变量内容在序列化后无法获得访问。
  2. transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现Serializable接口。
  3. 被transient关键字修饰的成员变量不再能被序列化。
  4. 静态变量不管是否被transient修饰,均不能被序列化。

5. RandomAccessFile类

RandomAccessFile 类可以说是Java语言中功能最为丰富的文件访问类,它提供了众多的文件访问方法。RandomAccessFile 类支持“随机访问”方式,可以跳转到文件的任意位置处读写数据。在要访问一个文件的时候,不想把文件从头读到尾,而是希望像访问一个数据库一样地访问一个文本文件,这时,使用RandomAccessFile类就是最佳选择

RandomAccessFile 对象类有个位置指示器,指向当前读写处的位置,当读写n个字节后,文件指示器将指向这n个字节后的下一个字节处。刚打开文件时,文件指示器指向文件的开头处,可以移动文件指示器到新的位置,随后的读写操作将从新的位置开始。RandomAccessFile在数据等长记录格式文件的随机(相对顺序而言)读取时有很大的优势,但该类仅限于操作文件,不能访问其它的IO 设备,如网络、内存映像等。

以读写的方式打开一个文件时,如果文件不存在,程序会自动创建此文件。

有关RandomAccessFile类中的成员方法及使用说明请参阅JDK文档。常见API如下:

方法名描述
void close();关闭此随机访问文件流并释放与该流关联的所有系统资源。
long getFilePointer();返回此文件中的当前偏移量。
long length();返回此文件的长度。
read函数集从文件读
void seek(long pos);设置到此文件开头测量到的文件指针偏移量,在该位置发生下一个读取或写入操作。
int skipBytes(int n)尝试跳过输入的 n 个字节以丢弃跳过的字节。
write函数集往文件写
setLength(long)设置文件大小(设置临时文件)

5.1 RandomAccessFile 类的构造方法

  1. new RandomAccessFile(f, “rw”); // 读写方式
  2. new RandomAccessFile(f, “r”); // 只读方式

5.2 向文件中记忆写入数据

    File file = new File("accessFile");
    RandomAccessFile raf = new RandomAccessFile(file, "rw");  
      // 以下向 raf 文件中写数据  
    raf.writeInt(20);// 占4个字节
    raf.writeDouble(8.236598);// 占8个字节  
    raf.writeShort(395);// 占2个字节  
    raf.writeUTF("这是一个UTF字符串");// 这个长度写在当前字符串指针的前两个字节处,可用readShort()读取  
    raf.writeBoolean(true);// 占1个字节  
    raf.writeLong(2325451l);// 占8个字节  
    raf.writeUTF("又是一个UTF字符串哈哈");  
    raf.writeFloat(35.5f);// 占4个字节  
    raf.writeChar('a');// 占2个字节  
    raf.close();

22.4.3 从文件中读取随机记忆的文件内容

    File file = new File("accessFile");
    RandomAccessFile raf = new RandomAccessFile(file, "rw");
    System.out.println(raf.readInt());//读取Int数据,指针会往后移动4字节
    System.out.println(raf.readDouble());//读取Double数据,指针会往后移动8字节
    System.out.println(raf.readUTF());//读取字符串,指针会移到该字符串后
    raf.skipBytes(3);//跳过3个字节,也就是跳过上面例子的 boolen 和 short 值。
    System.out.println(raf.readLong());//读取long值
    short readShort = raf.readShort();//读取字符串的长度
    System.out.println("目前指针处的字符串长度为=" + readShort);
    raf.skipBytes(readShort);//跳过该字符串
    System.out.println(raf.readFloat());//读取float值
    System.out.println(raf.readChar());//读取char值

    //long length = raf.length();
    //System.out.println("文件的总字节数为:"+length);

    //long filePointer = raf.getFilePointer();//当前指针的位置,定位到哪个字节了。
    //System.out.println("目前字节指针定位在:"+filePointer);

    //raf.seek(4);//直接定位到第4个字节处。

6 装饰者模式

  1. 装饰者和被装饰者拥有共同的抽象基类。
  2. 装饰者必须持有被装饰者的引用(父类的引用)
  3. 装饰者可以在被装饰者的行为之前/或之后,加上自己的行为,以达到功能拓展的目的

6.1 装饰者模式的定义

​ 扩展类功能,(继承也能做到)。但是相比继承,装饰者模式是动态地将责任(扩展功能)附加到对象上,比继承更有弹性。

6.2 装饰者模式的特点

  1. 装饰者和被装饰对象有相同的超类型
  2. 可以用一个或多个装饰者包装一个对象。
  3. 因为装饰者和被装饰者具有相同的类型,所以任何需要原始对象的场合,可以用装饰过的对象代替。
  4. 装饰者可以在所委托被装饰者的行为之前/或之后,加上自己的行为,以达到特定的目的
  5. 对象可以在任何时候被装饰,所以可以在运行时动态地、不限量地用你喜欢的装饰者来装饰对象。

6.3 用装饰者解决问题

  比如开车起步问题:

  1. 普通司机(CommonDriver)正常起步。
  2. 新手司机(NewDriver)起步老是会熄火。
  3. 老司机(竞技车手)(OldDriver)狂踩油门加速起步。

6.4 代码

  1. 定义被装饰者能被扩展的行为 Car.java (汽车接口)

    public interface Car {
    public abstract void starting();// 起步行为
    }
  2. 被装饰者抽象类 Driver.java (司机的抽象类)

    public abstract class Driver implements Car {
    protected String description = "未知的司机";
    
    public String getDescription() {
        return description;
    }
    
    }
  3. 被装饰者具体类 CommonDriver.java (普通司机)

    public class CommonDriver extends Driver {
    
    public CommonDriver() {
        description = "普通司机";
    }
    
    @Override
    public void starting() {
        System.out.println("起步中...CommonDriver");
    }
    }
  4. 装饰者抽象类:OtherDriver.java (非普通司机)

    public abstract class OtherDriver extends Driver {
    
    protected Driver driver;
    
    public OtherDriver(Driver driver) {
        this.driver = driver;
    }
    
    @Override
    public void starting() {
        driver.starting();
    }
    
    }
  5. 具体装饰者类一:OldDriver.java

    public class OldDriver extends OtherDriver {
    
    public OldDriver(Driver driver) {
        super(driver);
        description = "老司机";
    }
    //老司机扩展的代码
    private void superStarting(){
        System.out.println("油门踩到底...OldDriver");
        System.out.println("松离合,放手刹...OldDriver");
    }
    
    @Override
    public void starting() {
        superStarting();//在被装饰者代码之前 扩展代码
        super.starting();//被装饰者代码
    }
    
    }
  6. 具体装饰者类一:NewDriver.java

    public class NewDriver extends OtherDriver {
    
    public NewDriver(Driver driver) {
        super(driver);
        description = "新手司机";
    }
    
    private void lowStaring(){
        System.out.println("熄火了...NewDriver");
        System.out.println("又熄火了...NewDriver");
    }
    
    @Override
    public void starting() {
        lowStaring();
        super.starting();
    }
    }
  7. 测试:

    public static void main(String[] args) {
    
    CommonDriver commonDriver = new CommonDriver();
    System.out.println(commonDriver.getDescription());
    commonDriver.starting();
    
    System.out.println("******************");
    OldDriver oldDriver = new OldDriver(commonDriver);//对普通司机装饰
    System.out.println(oldDriver.getDescription());
    oldDriver.starting();
    System.out.println("******************");
    
    NewDriver newDriver = new NewDriver(commonDriver);//对普通司机装饰
    System.out.println(newDriver.getDescription());
    newDriver.starting();
    }

    结果:(控制台输出)

    普通司机
    起步中...CommonDriver
    <hr />
    
    老司机
    油门踩到底...OldDriver
    松离合,放手刹...OldDriver
    起步中...CommonDriver
    <hr />
    
    新手司机
    熄火了...NewDriver
    又熄火了...NewDriver
    起步中...CommonDriver

6.5 装饰者模式缺点

​ 会在设计中加入大量的小类,如果过度使用,会让程序变得复杂。

6.6 装饰者模式在JDK中的运用

Java当中的IO是运用了装饰者模式的最典型的例子。
下面是一个简单的例子,通过BufferedReader对象来装饰InputStreamReader对象:

BufferedReader input=new BufferedReader(new InputStreamReader(System.in));
//System.in 是一个 InputStream 对象

课后练习

  1. 将D盘的一张图片存入内存中,然后再把内存中的图片数据 存入到E盘中。
public static void copyPhoto() {
        File file = new File("1.jpg");
        File file2 = new File("1/1.jpg");
        try (RandomAccessFile raf = new RandomAccessFile(file, "rw");
                ByteArrayOutputStream baos = new ByteArrayOutputStream();) {
            byte[] b = new byte[1024];
            int read = -1;
            while ((read = raf.read(b)) != -1) {
                baos.write(b, 0, read);
            }
            System.out.println("写入内存完成");
            byte[] byteArray = baos.toByteArray();
            // ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
            RandomAccessFile raf1 = new RandomAccessFile(file2, "rw");
            raf1.write(byteArray);
            raf1.close();
            System.out.println("写入盘中");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        copyPhoto();
    }

2.在D盘新建一个 十年.txt 文件,并把《十年》的歌词存入该文件中。然后编写一个程序,结合学过的IO流知识将文件的内容读入到程序中,并使用打印流将《十年》的歌词打印到控制台!

public static void print() {
        File file = new File("shinian.txt");
        try (RandomAccessFile raf = new RandomAccessFile(file, "rw");
                ByteArrayOutputStream baos = new ByteArrayOutputStream();) {
            byte[] b = new byte[1024];
            int read = -1;
//          PrintWriter pw = new PrintWriter(baos);
            while ((read = raf.read(b)) != -1) {
                baos.write(b, 0, read);
                String string = baos.toString();
                 System.out.println(string);
//              pw.println(string);
            }
//          pw.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        print();
    }

3.编写一个程序,使用 RandomAccessFile 将一段文字写入到一个名为text.dat的文件中,然后使用RandomAccessFile对象将写入的文字读出来并打印到屏幕上。

public static void readWrite() {
        String content = "想你的夜,如果你再看我们一眼";
        File file = new File("text.dat");
        if (!file.exists()) {
            try (RandomAccessFile raf = new RandomAccessFile(file, "rw");) {
                raf.write(content.getBytes());
//              raf.writeUTF(content);
                System.out.println("写入完毕");
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }else {
            try {
                RandomAccessFile raf1 = new RandomAccessFile(file, "r");
                int read=-1;
                byte[] b = new byte[1024];
                while((read=raf1.read(b))!=-1) {
                    String string = new String(b,0,read);
                    System.out.println(string);
                }
                raf1.close();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

    public static void main(String[] args) {
        readWrite();
    }

4.把一个mp3文件截取为3个文件。

/*public static void main(String[] args) {

        File from = new File("D/ggh.mp3");

        int n = 8;// 截取3段

        cupFiles(from, n);

    }

    *//**
     * 将一个File 平均截取为 n 段
     * 
     * @param from
     * @param n
     *//*
    private static void cupFiles(File from, int n) {
        long start = 0;
        long end = 0;
        for (int i = 1; i <= n; i++) {
            if (i == n) {
                end = from.length();
            } else {
                end = start + from.length() / n;
            }
            File to = formatFile(from, i);
            boolean cupFile = Main.cupFile(from, start, end, to);
            System.out.println(i + "复制完成了?:" + cupFile);
            start = end;
        }
    }

    public static File formatFile(File file, int num) {
        String name = file.getName();
        int lastIndexOf = name.lastIndexOf('.');
        String newName = new StringBuffer().append(name).insert(lastIndexOf, num).toString();
        return new File(file.getParentFile(), newName);
    }

    *//**
     * 
     * @param from
     * @param start
     *            0 1024 2048 3072 4096
     * @param end
     *            5000
     * @param to
     * @return
     *//*
    public static boolean cupFile(File from, long start, long end, File to) {
        try {
            RandomAccessFile raf = new RandomAccessFile(from, "r");
            BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream(to));
            raf.seek(start);
            byte[] b = new byte[1024];
            long sum = 0;
            int len = 0;// 每次要求读的个数
            while (true) {
                long k = (end - start) - sum;// 剩余需要读的字节数
                if (k < b.length) {
                    len = (int) k;
                } else {
                    len = b.length;
                }
                int read = raf.read(b, 0, len);
                fos.write(b, 0, read);
                sum += read;
                if (sum >= end - start) {
                    break;
                }
            }
            return true;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值