• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

cwhat

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

IO流★★★★★

目录
  • 流
  • 字符流
    • 字符流继承体系简图
    • FileWriter
    • FileWriter-IO异常处理
    • FileReader读取
    • 字符流练习-复制文本文件
      • 方法一:
      • 方法二:
      • 图解
    • 字符流-缓冲区
      • 字符流-BufferedWriter
      • 字符流-BufferedReader
      • readLine方法原理
      • 缓冲区-复制文本文件
      • 自定义MyBufferedReader方法
      • 装饰设计模式
      • LineNumberReader
  • 字节流
    • 字节流继承体系简图
    • 操作文件基本演示
    • 练习-复制Mp3
  • 转换流-前奏
    • 演示键盘录入
    • 练习-读取键盘录入
    • 转换流键盘录入
    • 转换流-基本演示
  • 转换流
    • 流--操作基本规律
    • 转换流的编码解码
    • 转换流的具体作用
    • 什么时候使用转换流?
  • File类
    • File类常见方法:
      • 1. 创建与删除
      • 2. 获取
      • 3. 判断
      • 4. 重命名
      • 5. 系统根目录和容量获取
    • 过滤器
    • 递归-深度遍历
      • 练习-删除目录
  • Properties集合
    • 基本功能
    • 基本方法
    • .list方法
    • .store方法
    • 练习-修改配置信息
    • 练习-限制程序运行次数
    • 练习(综合)-文件清单列表
  • 数据类型在内存的字节数
  • 打印流
    • PrintStream-字节打印流
    • PrintWriter-字符打印流
  • 序列流
    • 文件的切割与合并
    • 文件切割合并+配置文件
    • 对象的序列化
      • ObjectOutputStream
      • 序列化接口-Serializable
      • transient关键字
    • RandomAccessFile
    • 管道流
    • 操作基本数据类型的流对象
    • 操作数组的流
    • 操作字符与字符串的流
  • 编码表
    • 概述
    • UTF-8的编码规范
    • 简单的编码解码
    • 编码解码问题
    • 问题二

流

流:可以理解数据的流动,就是一个数据流。IO流最终要以对象来体现,对象都存在IO包中。

流也进行分类:

    1:输入流(读)和输出流(写)。

    2:因为处理的数据不同,分为字节流和字符流。

 字节流:处理字节数据的流对象。设备上的数据无论是图片或者dvd,文字,它们都以二进制存储的。二进制的最终都是以一个8位为数据单元进行体现,所以计算机中的最小数据单元就是字节。意味着,字节流可以处理设备上的所有数据,所以字节流一样可以处理字符数据。

  那么为什么要有字符流呢?因为字符每个国家都不一样,所以涉及到了字符编码问题,那么GBK编码的中文用unicode编码解析是有问题的,所以需要获取中文字节数据的同时+ 指定的编码表才可以解析正确数据。为了方便于文字的解析,所以将字节流和编码表封装成对象,这个对象就是字符流。只要操作字符数据,优先考虑使用字符流体系。

  注意:流的操作只有两种:读和写。

image-20221110132210288

  流的体系因为功能不同,但是有共性内容,不断抽取,形成继承体系。该体系一共有四个基类,而且都是抽象类。

  字节流:InputStream OutputStream

  字符流:Reader Writer

  在这四个系统中,它们的子类,都有一个共性特点:子类名后缀都是父类名,前缀名都是这个子类的功能名称。

字符流

字符流继承体系简图

IO流——(9) 流的继承体系概览

字符流:

Reader:用于读取字符流的抽象类。子类必须实现的方法只有 read(char[], int, int) 和 close()。

  • BufferedReader:从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。 可以指定缓冲区的大小,或者可使用默认的大小。大多数情况下,默认值就足够大了。

    • LineNumberReader:跟踪行号的缓冲字符输入流。此类定义了方法 setLineNumber(int) 和 getLineNumber(),它们可分别用于设置和获取当前行号。
  • InputStreamReader:是字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。

    • FileReader:用来读取字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是适当的。要自己指定这些值,可以先在 FileInputStream 上构造一个 InputStreamReader。
  • CharArrayReader:

  • StringReader:


Writer:写入字符流的抽象类。子类必须实现的方法仅有 write(char[], int, int)、flush() 和 close()。

  • BufferedWriter:将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。

  • OutputStreamWriter:是字符流通向字节流的桥梁:可使用指定的 charset 将要写入流中的字符编码成字节。它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。

    • FileWriter:用来写入字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是可接受的。要自己指定这些值,可以先在 FileOutputStream 上构造一个 OutputStreamWriter。
  • PrintWriter:

  • CharArrayWriter:

  • StringWriter:

FileWriter

package IO.FileWriter;

import java.io.FileWriter;
import java.io.IOException;

public class FileWriterDemo {
    public static void main(String[] args) throws IOException { //读、写都会发生IO异常
        
        /*
    1:创建一个字符输出流对象,用于操作文件。该对象一建立,就必须明确数据存储位置,是一个文件。

    2:对象产生后,会在堆内存中有一个实体,同时也调用了系统底层资源,在指定的位置创建了一个存储数据的文件。

    3:如果指定位置,出现了同名文件,文件会被覆盖。

    */

        FileWriter fw = new FileWriter("demo.txt"); // FileNotFoundException

        /*
    调用Writer类中的write方法写入字符串。字符串并未直接写入到目的地中,而是写入到了流中,(其实是写入到内存缓冲区中)。怎么把数据弄到文件中?
    */

        fw.write("abcde");

        fw.flush(); // 刷新缓冲区,将缓冲区中的数据刷到目的地文件中。

        fw.close(); // 关闭流,其实关闭的就是java调用的系统底层资源。在关闭前,会先刷新该流。

    }
}

close()和flush()的区别:

  • flush():将缓冲区的数据刷到目的地中后,流可以使用。
  • close():将缓冲区的数据刷到目的地中后,流就关闭了,该方法主要用于结束调用的底层资源。这个动作一定做。

FileWriter-IO异常处理

io异常的处理方式:io一定要写finally;

FileWriter写入数据的细节:

  1. window中的换行符:\r\n两个符号组成。 linux:\n。
  2. 续写数据,只要在构造函数中传入新的参数true。
  3. 目录分割符:window \ /
package IO.FileWriter;

import java.io.FileWriter;
import java.io.IOException;

public class FileWriterDemo {
    private static final String LINE_SEPARATOR = System.getProperty("line.separator");

    public static void main(String[] args)  {


        FileWriter fw = null;
        try {
            fw = new FileWriter("t://demo.txt");
            fw.write("abcde"+LINE_SEPARATOR+"fjaslgjl");
        } catch (IOException e) {
            System.out.println(e.toString()+".......");
        }
        finally {
            //一定要进行if判断是否为空
            if (fw != null) {
                try {
                    fw.close();
                } catch (IOException e) {

                    throw new RuntimeException("关闭失败");
                }
            }
        }

    }
}

FileReader读取

 FileReader:使用Reader体系,读取一个文本文件中的数据。返回 —1 ,标志读到结尾。

image-20221130160838849

读取第一种方式

package IO.FileReader;

import java.io.*;

class FileReaderDemo {
    public static void main(String[] args) throws IOException {
		/*

      创建可以读取文本文件的流对象,FileReader让创建好的流对象和指定的文件相关联。

      */

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

        int ch = 0;

        while ((ch = fr.read()) != -1) { //条件是没有读到结尾

            System.out.println((char) ch); //调用读取流的read方法,读取一个字符。

        }
        fr.close();
    }
}

image-20221110172543306

读取数据的第二种方式:第二种方式较为高效,自定义缓冲区。

package IO.FileReader;

import java.io.*;

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

        FileReader fr = new FileReader("demo.txt"); //创建读取流对象和指定文件关联。

        //因为要使用read(char[])方法,将读取到字符存入数组。所以要创建一个字符数组,一般数组的长度都是1024的整数倍。

        char[] buf = new char[1024];

        int len = 0;

        while ((len = fr.read(buf)) != -1){
            System.out.println(new String(buf, 0, len));

        }
        fr.close();

    }
}

image-20221110173946741

字符流练习-复制文本文件

方法一:

        /*

        作业:将c盘的一个文本文件复制到d盘。

        分析:

        复制原理:

        读取c盘文件中的数据,

        将这些数据写入到d盘当中。

        连读带写。

         * 需求:作业:将c盘的一个文本文件复制到d盘。
         *
         * 思路:
         * 1,需要读取源,
         * 2,将读到的源数据写入到目的地。
         * 3,既然是操作文本数据,使用字符流。
         */

public class CopyTextTest {

    /**

     * @param args

     * @throws IOException

     */

    public static void main(String[] args) throws IOException {
 
        //1,读取一个已有的文本文件,使用字符读取流和文件相关联。

        FileReader fr = new FileReader("IO流_2.txt");

        //2,创建一个目的,用于存储读到数据。

        FileWriter fw = new FileWriter("copytext_1.txt");

        //3,频繁的读写操作。

        int ch = 0;

        while((ch=fr.read())!=-1){
            fw.write(ch);
        }
        //4,关闭流资源。
        
        fw.close();

        fr.close();
    }
}

方法二:

public static void main(String[] args) {

        FileReader fileReader = null;
        FileWriter fileWriter = null;
        try {
            //1,读取一个已有的文本文件,使用字符读取流和文件相关联。
            fileReader = new FileReader("demo.txt");
            //2,创建一个目的,用于存储读到数据。
            fileWriter = new FileWriter("text_for_IO");
            //3,频繁的读写操作。
            
            //创建一个临时容器,用于缓存读取到的字符。
            char[] buf = new char[BUFFER_SIZE];

            //定义一个变量记录读取到的字符数,(其实就是往数组里装的字符个数—)。
            int len = 0;

            while ((len = fileReader.read(buf)) != -1){
                fileWriter.write(buf,0,len);
            }
        } catch (Exception  e) {
            throw new RuntimeException("读写失败");
        }finally {
            if (fileReader != null){
                try {
                    fileReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fileWriter != null){
                try {
                    fileWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

图解

image-20221115174450184

字符流-缓冲区

我们做代码呢,有两部份优化,一部分叫做设计优化,另一部份叫性能优化,提高性能最常用的就是缓冲区

image-20221115191658317

字符流-BufferedWriter

image-20221116172426189

缓冲区是用来提高效率的

关闭缓冲区的时候也把流也关闭了的,不用再关闭fw了

 public static final String LINE_SEPARATOR = System.getProperty("line.separator");
    public static void main(String[] args) throws IOException {
        FileWriter fw = new FileWriter("buf.txt");

        //为了提高写入的效率。使用了字符流的缓冲区。
        //创建了一个字符写入流的缓冲区对象,并和指定要被缓冲的流对象相关联

        BufferedWriter bufw = new BufferedWriter(fw);

        //使用缓冲区的写入方法将数据先写入到缓冲区中。

//      bufw.write("abcdefq"+LINE_SEPARATOR+"hahahha");

//      bufw.write("xixiixii");

//      bufw.newLine();//换行

//      bufw.write("heheheheh");

        for (int x = 1; x <= 4; x++) {
            bufw.write("abcdef" + x);

            bufw.newLine();

            bufw.flush();//每读一行刷新一行,防止丢失数据。

        }

        //使用缓冲区的刷新方法将数据刷目的地中。
//      bufw.flush();

        //关闭缓冲区。其实关闭的就是被缓冲的流对象。
        bufw.close();

//      fw.close();

    }

字符流-BufferedReader


    public static void main(String[] args) throws IOException {
        FileReader fr = new FileReader("buf.txt");

        BufferedReader bufferedReader = new BufferedReader(fr);

        String line = null;

        while ((line = bufferedReader.readLine())!= null){
            System.out.println(line);
        }
        bufferedReader.close();
    }

字符流缓冲区:

BufferedWriter:

newLine();写一行行分隔符。 行分隔符字符串由系统属性line.separator定义,并不一定是单个换行符('\ n')字符。

BufferedReader:

readLine();读一行文字。

readLine方法原理

image-20221116182714386

image-20221116182543775

缓冲区-复制文本文件

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

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

        BufferedReader bufr = new BufferedReader(fr);

        FileWriter fw = new FileWriter("buf_copy.txt");

        BufferedWriter bufw = new BufferedWriter(fw);

        String line = null;

        while ((line = bufr.readLine()) != null) {
            bufw.write(line);

            bufw.newLine();

            bufw.flush();

        }
        bufw.close();

        bufr.close();
    }

自定义MyBufferedReader方法

package IO.MyBufferedReader;

import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;

/**
 * 自定义的读取缓冲区。其实就是模拟一个BufferedReader.

 * 分析:
 * <p>
 * 缓冲区中无非就是封装了一个数组,
 * 并对外提供了更多的方法对数组进行访问。
 * 其实这些方法最终操作的都是数组的角标。

 * 缓冲的原理:

 * 其实就是从源中获取一批数据装进缓冲区中。

 * 在从缓冲区中不断的取出一个一个数据。
 * <p>
 * 在此次取完后,在从源中继续取一批数据进缓冲区。
 * <p>
 * 当源中的数据取光时,用-1作为结束标记。
 *
 * @author Administrator
 */

public class MyBufferedReader extends Reader{


    private Reader r;


    //定义一个数组作为缓冲区。

    private char[] buf = new char[1024];


    //定义一个指针用于操作这个数组中的元素。当操作到最后一个元素后,指针应该归零。

    private int pos = 0;


    //定义一个计数器用于记录缓冲区中的数据个数。当该数据减到0,就从源中继续获取数据到缓冲区中。

    private int count = 0;


    MyBufferedReader(Reader r) {
        this.r = r;

    }

    /**
     * 该方法从缓冲区中一次取一个字符。
     *
     * @return
     * @throws IOException
     */

    public int myRead() throws IOException {

        //1,从源中获取一批数据到缓冲区中。需要先做判断,只有计数器为0时,才需要从源中获取数据。

        if (count == 0) {
            count = r.read(buf);
            //每次获取数据到缓冲区后,角标归零.
            pos = 0;

        }

        if (count < 0)

            return -1;

        char ch = buf[pos++];

        count--;

        return ch;

    }
    public String myReadLine() throws IOException{

        //定义一个临时容器。原BufferReader封装的是字符数组。
        //为了演示方便。定义一个StringBuilder容器。因为最终还是要将数据变成字符串。
        StringBuilder sb = new StringBuilder();

        int ch = 0;

        while((ch = myRead())!=-1){


            if(ch=='\r')

                continue;

            if(ch=='\n')

                return sb.toString();

            //将从缓冲区中读到的字符,存储到缓存行数据的缓冲区中。

            sb.append((char)ch);

        }

        //健壮性判断
        if(sb.length()!=0)

            return sb.toString();

        return null;

    }
    /*
    覆盖Reader类中的抽象方法。

    */
    public void myClose() throws IOException {

        r.close();
    }

    @Override
    public int read(char[] cbuf, int off, int len) throws IOException {
        return 0;
    }

    @Override
    public void close() throws IOException {

    }
}
class  MyBufferedReaderDemo
{
    public static void main(String[] args) throws IOException
    {
        FileReader fr = new FileReader("buf.txt");

        MyBufferedReader myBuf = new MyBufferedReader(fr);

        String line = null;

        while((line=myBuf.myReadLine())!=null)
        {
            System.out.println(line);
        }

        myBuf.myClose();
    }
}

装饰设计模式

我们使用了MyBufferedReader对FileReader这个类进行了功能增强,这种增强啊在设计模式当中叫做装饰设计模式

介绍:

装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。

意图:

动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。

主要解决:

一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。

何时使用:

在不想增加很多子类的情况下扩展类。

如何解决:

将具体功能职责划分,同时继承装饰者模式。

关键代码:

  1. Component 类充当抽象角色,不应该具体实现。

  2. 修饰类引用和继承 Component 类,具体扩展类重写父类方法。

应用实例:

  1. 孙悟空有 72 变,当他变成"庙宇"后,他的根本还是一只猴子,但是他又有了庙宇的功能。

  2. 不论一幅画有没有画框都可以挂在墙上,但是通常都是有画框的,并且实际上是画框被挂在墙上。在挂在墙上之前,画可以被蒙上玻璃,装到框子里;这时画、玻璃和画框形成了一个物体。

优点:

装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。

缺点:

多层装饰比较复杂。

使用场景:

  1. 扩展一个类的功能。
  2. 动态增加功能,动态撤销。

注意事项:

可代替继承。

更加详细的介绍;详见:

装饰设计模式:


/*
装饰设计模式:
当想要对已有的对象进行功能增强时,
可以定义类,将已有对象传入,基于已有的功能,并提供加强功能。
那么自定义的该类称为装饰类。

装饰类通常会通过构造方法接收被装饰的对象。
并基于被装饰的对象的功能,提供更强的功能。


*/
class Person
{
    public void chifan()
    {
        System.out.println("吃饭");
    }
}

//这个类的出现是为了增强Person而出现的
class SuperPerson
{
    private Person p ;
    SuperPerson(Person p)
    {
        this.p = p;
    }
    public void superChifan()
    {
        System.out.println("开胃酒");
        p.chifan();
        System.out.println("甜点");
        System.out.println("来一根");
    }
}

class  PersonDemo
{
    public static void main(String[] args)
    {
        Person p = new Person();
        //p.chifan();

        SuperPerson sp = new SuperPerson(p);
        sp.superChifan();

    }
}
/*
class NewPerson2 extends Person{
    public void chifan(){
        System.out.println("开胃酒");

        super.chifan();

        System.out.println("甜点");

    }

}

装饰和继承都能实现一样的特点:进行功能的扩展增强
    */

LineNumberReader

image-20221117203659953

读取.java文件的时候可以用LineNumberReader

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


        FileReader fr = new FileReader("IO流_2.txt");

        LineNumberReader lnr = new LineNumberReader(fr);


        String line = null;

        lnr.setLineNumber(100);

        while ((line = lnr.readLine()) != null) {
            System.out.println(lnr.getLineNumber() + ":" + line);

        }

        lnr.close();

    }

字节流

字节流继承体系简图

IO流——(9) 流的继承体系概览

InputStream:是表示字节输入流的所有类的超类。

  • FileInputStream:从文件系统中的某个文件中获得输入字节。哪些文件可用取决于主机环境。FileInputStream 用于读取诸如图像数据之类的原始字节流。要读取字符流,请考虑使用 FileReader。

  • FilterInputStream:包含其他一些输入流,它将这些流用作其基本数据源,它可以直接传输数据或提供一些额外的功能。

    • BufferedInputStream:该类实现缓冲的输入流。

    • Stream:

  • ObjectInputStream:

  • PipedInputStream:


OutputStream:此抽象类是表示输出字节流的所有类的超类。

  • FileOutputStream:文件输出流是用于将数据写入 File 或 FileDescriptor 的输出流。

  • FilterOutputStream:此类是过滤输出流的所有类的超类。

    • BufferedOutputStream:该类实现缓冲的输出流。

    • PrintStream:将对象的格式表示打印到文本输出流。

    • DataOutputStream:

  • ObjectOutputStream:

  • PipedOutputStream:

操作文件基本演示

public class ByteStreamTest {

    public static void main(String[] args) {

        try {
            demo_write();
            demo_read();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void demo_write() throws IOException {
        FileOutputStream fos = new FileOutputStream("ByteDemo.txt");

        fos.write("shishuo".getBytes());

        fos.close();
    }

    public static void demo_read() throws IOException {
        FileInputStream fis = new FileInputStream("ByteDemo.txt");
        //      System.out.println(fis.available());

//      byte[] buf = newbyte[fis.available()];      

//      fis.read(buf);

//      System.out.println(newString(buf));
 	//建议使用这种读取数据的方式
        byte[] buf = new byte[1024];

        int len = 0;
        while ((len = fis.read(buf)) != -1)
            System.out.println(new String(buf, 0, len));

 		fis.close();
    }
}

练习-复制Mp3

package IO.ByteStream;

import java.io.*;

//G:\film and television\hiphop F:\Java\Program Code\Helloworld\demo.txt
public class CopyMp3Test {
    public static void main(String[] args) throws IOException {


        copy_4();


    }

//  千万不要用,效率没有!

    public static void copy_4() throws IOException {
        FileInputStream fis = new FileInputStream("c:\\0.mp3");

        FileOutputStream fos = new FileOutputStream("c:\\4.mp3");


        int ch = 0;


        while ((ch = fis.read()) != -1) {
            fos.write(ch);

        }


        fos.close();

        fis.close();

    }


    //不建议。 fis.available()  字节如果大的话,byte可能会溢出,而且缓冲满

    public static void copy_3() throws IOException {
        FileInputStream fis = new FileInputStream("c:\\0.mp3");

        FileOutputStream fos = new FileOutputStream("c:\\3.mp3");


        byte[] buf = new byte[fis.available()];

        fis.read(buf);

        fos.write(buf);

        fos.close();

        fis.close();

    }


    public static void copy_2() throws IOException {


        FileInputStream fis = new FileInputStream("G:\\film and television\\hiphop\\pg one -都是你.mp3");
        BufferedInputStream bufis = new BufferedInputStream(fis);
        FileOutputStream fos = new FileOutputStream("F:\\Java\\Program Code\\Helloworld\\pgMusic.mp3");
        BufferedOutputStream bufos = new BufferedOutputStream(fos);


        int ch = 0;


        while ((ch = bufis.read()) != -1) {
            bufos.write(ch);

        }


        bufos.close();

        bufis.close();

    }


    public static void copy_1() throws IOException {


        FileInputStream fis = new FileInputStream("G:\\film and television\\hiphop\\pg one -都是你.mp3");

        FileOutputStream fos = new FileOutputStream("F:\\Java\\Program Code\\Helloworld\\pgMusic.mp3");


        byte[] buf = new byte[1024];


        int len = 0;


        while ((len = fis.read(buf)) != -1) {
            fos.write(buf, 0, len);

        }


        fos.close();

        fis.close();

    }
}

转换流-前奏

演示键盘录入

image-20221120222547259

这个系统流是不需要关的,如果要是其他流对象我关了以后,我再new一个,又可以用,这个标准的流不行,从系统获取的这个流对象就一个,你把这个系统流对象一关,这个流对象就再也用不了了,你再通过系统获取流对象就没有,所以你这个东西吧就不要关,随着系统的消失而消失,随着系统的出现而出现
默认的输入设备,和默认的输出设备都不需要关,,你关了就再也获取不了了,除非你重新启动

练习-读取键盘录入

package IO.TransStream;

/*获取用户键盘录入的数据,

 * 并将数据变成大写显示在控制台上,

 * 如果用户输入的是over,结束键盘录入。
 */

import java.io.IOException;
import java.io.InputStream;

public class ReadKey {

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

        readKey();
//        System.out.println((int)'\r');
//        System.out.println((int)'\n');
    }

    private static void readKey() throws IOException {


        //定义一个容器
        StringBuilder sb = new StringBuilder();

        //获取键盘读取流
        InputStream in = System.in;

        //定义变量记录读取到的字节,并循环获取。
        int ch = 0;

        while((ch = in.read())!=-1) {
            //在存储之前需要判断是否是换行标记,因为换行不存储。
            if(ch  == '\r')
                continue;
            if (ch == '\n'){
                String temp = sb.toString();
                if("over".equals(temp))
                    break;
                sb.delete(0,sb.length());
                //集合清空是clear,StringBuilderStringBuffer清空是Delete。
                System.out.println(temp.toUpperCase());
            }else
            //将读取到的字节存储到StringBuilder中。
            sb.append((char)ch);
//            System.out.println(ch);
        }
    }
}

转换流键盘录入

image-20221121153043307

  public static void main(String[] args) throws IOException {
        BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));

        BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));

        String line = null;

        while ((line = bufr.readLine())!= null){
            if ("over".equals(line))
                break;
            bufw.write(line.toUpperCase());
            bufw.newLine();
            bufw.flush();
        }
    }

转换流-基本演示

package IO.TransStream;

import java.io.*;

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

        /**
         * 1.需求:将键盘录入的数据写入到一个文件中。
         *
         * 2.需求:将一个文本文件内容显示到控制台上。
         *
         * 3.需求:将一个文件中的内容复制到另一个文件中。
         */
//        需求1:
        BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));

        BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("a.txt")));
        //需求2:
//        BufferedReader bufr = new BufferedReader(new InputStreamReader(new FileInputStream("a.txt")));
//
//        BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));
        //需求3:
//        BufferedReader bufr = new BufferedReader(new InputStreamReader(new FileInputStream("a.txt")));
//
//        BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("b.txt")));

        String line = null;

        while ((line = bufr.readLine()) != null) {
            if ("over".equals(line))
                break;
            bufw.write(line.toUpperCase());
            bufw.newLine();
            bufw.flush();
        }
	   bufr.close();
        bufw.close();
    }
}

转换流

流--操作基本规律

转换流:

InputStreamReader :字节到字符的桥梁。解码。

OutputStreamWriter:字符到字节的桥梁。编码。

流对象:其实很简单,就是读取和写入。

之所以要弄清楚这个规律,是因为流对象太多,开发时不知道用哪个对象合适。

想要知道开发时用到哪些对象。只要通过四个明确即可。

流的操作规律:

  1. 明确源和目的(汇)。

    • 数据源:就是需要读取,可以使用两个体系:InputStream、Reader;

    • 数据汇:就是需要写入,可以使用两个体系:OutputStream、Writer;

  2. 操作的数据是否是纯文本数据?

    • 源:

      • 是纯文本:Reader
      • 否:InputStream
    • 目的(汇):

      • 是纯文本 Writer

      • 否:OutputStream

    • 如果不是:

      • 数据源:InputStream

      • 数据汇:OutputStream

  3. 虽然确定了一个体系,但是该体系中有太多的对象,到底用哪个呢?

    • 明确操作的数据设备。

      • 数据源对应的设备:

        • 硬盘:File
        • 内存:数组
        • 键盘:System.in
        • 网络:Socket流
      • 数据汇对应的设备:

        • 硬盘:File
        • 内存:数组
        • 控制台:System.out
        • 网络:Socket流。
  4. 需要在基本操作上附加其他功能吗?

    1. 是否需要高效(缓冲区);

      是,就加上buffer.

    2. 转换。

//例子
读取键盘录入数据,显示在控制台上
1,明确源和目的:
    源:InputStream Reader
    汇:OutputStream Writer
2,是否是纯文本?
    是:
    源:Reader
    目的:Writer
3,明确设备:
    源:
    	键盘:System.in
    汇:
    	控制台:Systeam.out
    InputStream in = System.in;
	OutputStream out = System.out;
4,明确额外功能?
    需要转换:因为都是字节流,但是操作的确实文本数据,
    所以使用字符流操作起来更为便捷。
	InputStreamReader isr = new InputStreamReader(System.in);
	OutputStreamWriter osw = new OutputStreamWriter(System.out);

	为了将其高效。
	BufferedReader bufr = new BufferedReader(InputStreamReader(System.in));
	BufferedWriter bufw = new BufferedWriter(InputStreamWriter(System.out));

转换流的编码解码

Java内置的是uncode码表,对于字符类型的数据是用的uncode,,任何java识别的数据全都是uncode,但是字符串是用的gbk

image-20221123000819253

//将一个中文字符串数据按照指定的编码表写入到一个文本文件中。
 /*
 1,目的:OutputStream,Writer
 2,是纯文本,Writer
 3,设备:硬盘,File
 */

 FileWriter fw = new FileWriter("a.txt");
 fw.write("你好");

注意:既然需求中已经明确了指定编码表的动作。
    那就不可以使用FileWriter,因为FileWriter内部是使用默认的本地码表。
    只能使用其父类。OutputStreamWriter,
    OutputStreamWriter接受一个字节输出流对象,既然是操作文件,那么该对象应该是FileOutputStream

OutputStreamWriter osw = new OuteputStreamWriter(new FileOutStream("a.txt"),"UTF-8");

需要高效吗?
BufferedWriter bufw = new BufferedWriter(new OuteputStreamWriter(new FileOutStream("a.txt"),"UTF-8"));

转换流的具体作用

转换流特有功能:转换流可以将字节转成字符,原因在于,将获取到的字节通过查编码表获取到指定对应字符。

转换流的最强功能就是基于 字节流 + 编码表 。没有转换,没有字符流。

发现转换流有一个子类就是操作文件的字符流对象:

  • InputStreamReader

    • FileReader
  • OutputStreamWriter

    • FileWrier

想要操作文本文件,必须要进行编码转换,而编码转换动作转换流都完成了。所以操作文件的流对象只要继承自转换流就可以读取一个字符了。

但是子类有一个局限性,就是子类中使用的编码是固定的,是本机默认的编码表,对于简体中文版的系统默认码表是GBK。

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

InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"),"gbk");

//以上两句代码功能一致,

如果仅仅使用平台默认码表,就使用FileReader fr = new FileReader("a.txt"); //因为简化。

如果需要制定码表,必须用转换流。

  • 转换流 = 字节流+编码表。

  • 转换流的子类File = 字节流 + 默认编码表。

什么时候使用转换流?

  1. 源或者目的对应的设备是字节流,但是操作的确实文本数据,可以使用转换作为桥梁。

    提高对文本操作的便捷。

  2. 凡是操作设备上的文本数据,涉及编码转换,必须使用转换流。

File类

File类:将文件系统中的文件和文件夹封装成了对象。提供了更多的属性和行为可以对这些文件和文件夹进行操作。这些是流对象办不到的,因为流只操作数据。

File类常见方法:

1. 创建与删除

//创建:
boolean createNewFile();//在指定目录下创建文件,如果该文件已存在,则不创建。
//而对操作文件的输出流而言,输出流对象已建立,就会创建文件,如果文件已存在,会覆盖。除非续写。

boolean mkdir();//创建此抽象路径名指定的目录。//make dircectory

boolean mkdirs();//根据File对象的名称,创建多级目录。 

//删除:
boolean delete();//删除此抽象路径名表示的文件或目录。

void deleteOnExit();//在虚拟机退出时删除。

/*注意:在删除文件夹时,必须保证这个文件夹中没有任何内容,才可以将该文件夹用delete删除。

window的删除动作,是从里往外删。注意:java删除文件不走回收站。要慎用。

2. 获取

long length();//获取文件大小。如果此路径名表示目录,则返回值未指定。 

String getName();//返回由此抽象路径名表示的文件或目录的名称。

String getPath();//将此抽象路径名转换为一个路径名字符串。

String getAbsolutePath();//返回此抽象路径名的绝对路径名字符串。

String getParent();//返回 此抽象路径名父目录的抽象路径名,如果此路径名没有指定父目录,则返回 null。

long lastModified();//返回此抽象路径名表示的文件最后一次被修改的时间。

File.pathSeparator;//返回当前系统默认的路径分隔符,windows默认为 “;”。

File.Separator;//返回当前系统默认的目录分隔符,windows默认为 “\”。

3. 判断

boolean exists();//判断文件或者文件夹是否存在。  最好判断是否存在再判断其他的

boolean isDirectory();//测试此抽象路径名表示的文件是否是一个目录。

boolean isFile();//测试此抽象路径名表示的文件是否是一个标准文件。

boolean isHidden();//测试此抽象路径名指定的文件是否是一个隐藏文件。

boolean isAbsolute();//测试此抽象路径名是否为绝对路径名。

4. 重命名

boolean renameTo(File dest);//可以实现移动的效果。剪切+重命名。

5. 系统根目录和容量获取

String[] list();//列出指定目录下的当前的文件和文件夹的名称。包含隐藏文件。

File listFiles();//获取指定目录下当前的所有文件夹或者文件对象。
/*
* 调用list方法的File对象中封装的必须是目录
* 否则会发生NullPointerException
* 如果访问的系统级目录 也会发生空指针异常。
* 
* 如果目录存在但没有内容,会返回一个数组,但是长度为0;
*/
//ex
File[] files = File.listRoots();
for(File file : files){
    System.out.println(file);
}

如果调用list方法的File 对象中封装的是一个文件,那么list方法返回数组为null。

如果封装的对象不存在也会返回null。只有封装的对象存在并且是文件夹时,这个方法才有效。

过滤器

image-20221123172138048

image-20221123172217419

image-20221123172556051

image-20221123170809791

import java.io.File;
import java.io.File;
import java.io.FilenameFilter;

public class FileListDemo {

    public static void main(String[] args) {
        File file = new File("c:\\dagda\\定时关机Dos.txt");

        String[] str = file.list(new SuffixFilter(".txt"));

        System.out.println(file.length());
        for (String s : str){
            System.out.println(s);
        }
    }
}

public class SuffixFilter implements FilenameFilter {

    private String suffix;
    SuffixFilter(String suffix){
        super();
        this.suffix = suffix;
    }
    @Override
    public boolean accept(File dir, String name) {
        return name.endsWith(suffix);//测试此字符串是否以指定的后缀结尾。 
    }
}

递归-深度遍历

image-20221123235904777

image-20221123235939496

递归:就是函数自身调用自身。

什么时候用递归呢?

当一个功能被重复使用,而每一次使用该功能时的参数不确定,都由上次的功能元素结果来确定。

简单说:功能内部又用到该功能,但是传递的参数值不确定。(每次功能参与运算的未知内容不确定)。

递归的注意事项:

  1. 一定要定义递归的条件。

  2. 递归的次数不要过多。容易出现 StackOverflowError 栈内存溢出错误。

其实递归就是在栈内存中不断的加载同一个函数。

练习-删除目录

package IO.File;

import java.io.File;

public class RemoveDirTest {
    public static void main(String[] args) {
        File dir = new File("D:\\浏览器下载位置\\后台挂尔雅浏览器v1.99");

        removeDir(dir);
    }

    private static void removeDir(File dir) {

        File[] files = dir.listFiles();

        for (File file : files) {

            if (file.isDirectory()){
                removeDir(file);
            }else
                System.out.println(file.getName()+":"+file.delete());
        }
        System.out.println(dir+":"+dir.delete());
    }
}

Properties集合

image-20221125171319154

基本功能

Java.util.Properties:一个可以将键值进行持久化存储的对象。Map——Hashtable的子类。

Map

  • Hashtable
    • Properties:用于属性配置文件,键和值都是字符串类型。

特点:

  1. 可以持久化存储数据。(说白了就是让信息保存的时间长一点)
  2. 键值都是字符串。
  3. 集合中的数据可以保存到流中,或者从流获取。
    • 一般用于配置文件。
    • 是集合中和IO技术相结合的集合容器。

基本方法

  • load():将流中的数据加载进集合。

    • 原理:其实就是将读取流和指定文件相关联,并读取一行数据,因为数据是规则的key=value,所以获取一行后,通过 = 对该行数据进行切割,左边就是键,右边就是值,将键、值存储到properties集合中。
  • store():写入各个项后,刷新输出流。

  • list():将集合的键值数据列出到指定的目的地。

.list方法

将集合的键值数据列出到指定的目的地。

image-20221125171841711

public static void main(String[] args) throws IOException {
    Properties prop = new Properties();

    //存储元素
    prop.setProperty("shishuo","15");
    prop.setProperty("chenyufan","80");
    prop.setProperty("shiying","16");
    prop.setProperty("chaiyun","18");



    prop.list(System.out);
}

.store方法

image-20221125174229250

image-20221125174204312

public static void main(String[] args) throws IOException {
        Properties prop = new Properties();

        //存储元素,不要存中文
        prop.setProperty("shishuo","15");
        prop.setProperty("chenyufan","80");
        prop.setProperty("shiying","16");
        prop.setProperty("chaiyun","18");

        //想要将这些集合中的字符串键值信息持久化存储到文件中。
        //需要关联输出流。 
        FileOutputStream fos = new FileOutputStream("a.txt");

        //将集合中数据存储到文件中,使用store方法。"info"是属性列表的描述。 
        prop.store(fos,"info");
        fos.close();
}

练习-修改配置信息

     private static void myLoad() throws IOException{
        //读取这个文件
        File file = new File("a.txt");
        if (!file.exists()){
            file.createNewFile();
        }
        FileReader fr = new FileReader(file);

        //创建集合存储配置信息。
        Properties properties = new Properties();

        //将流中信息存储到集合中。
        properties.load(fr);

        properties.setProperty("shiying","12");

        FileWriter fw = new FileWriter(file);

//        properties.list(System.out);
        properties.store(fw,"info");
        fr.close();
    }

练习-限制程序运行次数

* 获取指定目录下,指定扩展名的文件(包含子目录中的)

* 这些文件的绝对路径写入到一个文本文件中。

* 简单说,就是建立一个指定扩展名的文件的列表。

image-20221126131024097

image-20221126131149102

练习(综合)-文件清单列表

获取指定目录下,指定扩展名(包含子目录中的)
    这些文件的绝对路径写入到一个文本文件中。
    
    简单说,就是建立一个指定扩展名的文件的列表。

数据类型在内存的字节数

image-20221126200157424

打印流

IO包中扩展功能的流对象:基本都是装饰设计模式。

PrintStream-字节打印流

Java.io.outputstream.PrintStream:打印流

    1:提供了更多的功能,比如打印方法。可以直接打印任意类型的数据。

    2:它有一个自动刷新机制,创建该对象,指定参数,对于指定方法可以自动刷新。

    3:它使用的本机默认的字符编码.

    4:该流的print方法不抛出IOException。

该对象的构造函数。

    PrintStream(File file);//创建具有指定文件且不带自动行刷新的新打印流。 

    PrintStream(File file, String csn);//创建具有指定文件名称和字符集且不带自动行刷新的新打印流。 

    PrintStream(OutputStream out);//创建新的打印流。 

    PrintStream(OutputStream out, boolean autoFlush);//创建新的打印流。 

    PrintStream(OutputStream out, boolean autoFlush, String encoding);//创建新的打印流。 

    PrintStream(String fileName);//创建具有指定文件名称且不带自动行刷新的新打印流。 

    PrintStream(String fileName, String csn);
  1. 提供了打印方法可以对多种数据类型值进行打印。并保持数据的表示形式。

  2. 它不抛IOException.

构造函数,接收三种类型的值:

  1. File对象。File

  2. 字符串路径。String

  3. 字节输出流。OutputStream

 前两个都JDK1.5版本才出现。而且在操作文本文件时,可指定字符编码了。

    当目的是一个字节输出流时,如果使用的println方法,可以在printStream对象上加入一个true参数。这样对于println方法可以进行自动的刷新,而不是等待缓冲区满了再刷新。最终print方法都将具体的数据转成字符串,而且都对IO异常进行了内部处理。

  既然操作的数据都转成了字符串,那么使用PrintWriter更好一些。因为PrintWrite是字符流的子类,可以直接操作字符数据,同时也可以指定具体的编码。.

图:数字97占4个字节。out.write()只写最低八位。所以out.write(610); 输出:b

image-20221127012855402

    public static void main(String[] args) throws IOException {
        PrintStream out = new PrintStream("Pritnt.txt");

        out.write(610); //只写最低八位。输出:b

        out.print(97);//将97变成字符保持原样将数据打印到目的地。

        out.close();
    }

PrintWriter-字符打印流

image-20221127020055162

    • | Constructor | 描述 |
      | --------------------------------------------------- | ------------------------------------------------------------ |
      | PrintWriter(File file) | 使用指定的文件创建一个新的PrintWriter,而不需要自动的线路刷新。 |
      | PrintWriter(File file, String csn) | 使用指定的文件和字符集创建一个新的PrintWriter,而不需要自动进行线条刷新。 |
      | PrintWriter(OutputStream out) | 从现有的OutputStream创建一个新的PrintWriter,而不需要自动线路刷新。 |
      | PrintWriter(OutputStream out, boolean autoFlush) | 从现有的OutputStream创建一个新的PrintWriter。 |
      | PrintWriter(Writer out) | 创建一个新的PrintWriter,没有自动线冲洗。 |
      | PrintWriter(Writer out, boolean autoFlush) | 创建一个新的PrintWriter。 |
      | PrintWriter(String fileName) | 使用指定的文件名创建一个新的PrintWriter,而不需要自动执行行刷新。 |
      | PrintWriter(String fileName, String csn) | 使用指定的文件名和字符集创建一个新的PrintWriter,而不需要自动线路刷新。 |

image-20221127020325664

具备了PrintStream的特点同时,还有自身特点:

该对象的目的地有四个:

  1. File对象。

  2. 字符串路径。

  3. 字节输出流。

  4. 字符输出流。

开发时尽量使用PrintWriter。

方法中直接操作文件的第二参数是编码表。

直接操作输出流的,第二参数是自动刷新。

 public static void main(String[] args) throws IOException {    
	//读取键盘录入将数据转成大写显示在控制台.

  	BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));//源:键盘输入

    //目的:把数据写到文件中,还想自动刷新。

   PrintWriter out = new PrintWriter(new FileWriter("out.txt"),true);//设置true后自动刷新

   String line = null;

   while((line=bufr.readLine())!=null){

      if("over".equals(line))

      break;

      out.println(line.toUpperCase());//转大写输出

    }
/*注意**:System.in,System.out这两个标准的输入输出流,在jvm启动时已经存在了。
随时可以使用。当jvm结束了,这两个流就结束了。但是,当使用了显示的close方法关闭时,这两个流在提前结束了。*/

    out.close();

    bufr.close();
 }

   

序列流

SequenceInputStream:序列流,作用就是将多个读取流合并成一个读取流。实现数据合并。

image-20221127171819875

​ SequenceInputStream:序列流表示其他输入流的逻辑串联。它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。 

​ 表示其他输入流的逻辑串联。它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。

​ 这样做,可以更方便的操作多个读取流,其实这个序列流内部会有一个有序的集合容器,用于存储多个读取流对象。

​ 该对象的构造函数参数是枚举,想要获取枚举,需要有Vector集合,但不高效。需用ArrayList,但ArrayList中没有枚举,只有自己去创建枚举对象。

​ 但是方法怎么实现呢?因为枚举操作的是具体集合中的元素,所以无法具体实现,但是枚举和迭代器是功能一样的,所以,可以用迭代替代枚举。

    public static void main(String[] args) throws IOException {
        /**
         * 需求:将1.txt 2.txt 3.txt文件中的数据合并到一个文件中
         */

//        Vector<FileInputStream> v = new Vector<FileInputStream>();
//          v.add(new FileInputStream("1.txt"));
//          v.add(new FileInputStream("2.txt"));
//          v.add(new FileInputStream("3.txt"));
//        Enumeration<FileInputStream> en = v.elements();

        ArrayList<FileInputStream> al = new ArrayList<FileInputStream>();
        for (int i = 0; i < 3; i++) {
            al.add(new FileInputStream("1.txt"));
        }
        //Collections.enumeration()返回一个指定Collection上的枚举。
        Enumeration<FileInputStream> en = Collections.enumeration(al);
        SequenceInputStream sis = new SequenceInputStream(en);

        FileOutputStream fos = new FileOutputStream("4.txt");

        byte[] buf = new byte[1024];

        int len = 0;
        while((len=sis.read(buf))!=-1)
        {
            fos.write(buf,0,len);
        }

        fos.close();
        sis.close();
    } 

文件的切割与合并

切割原理:一个读取流对应多个输出流。

合并原理:多个读取流对应一个输出流。

切割:切割的方式有两种,一种是按大小,另一种是按文件个数切

    
//文件的切割:
	private static final int SIZE = 1024*1024;
	public static void main(String[] args) throws IOException {

        	File file = new File("c:\\0.bmp");

//        meger(dir);
        margeFile(dir);

    }
	public static void splitFile(File file) throws IOException {

        //用读取流关联源文件。
        FileInputStream fis = new FileInputStream(file);

        //定义一个1M的缓冲区
        byte[] buf = new byte[SIZE];

        //创建目的。
        FileOutputStream fos = null;

        int len = 0;
        int count = 1;

        File dir = new File("c:\\partFiles");
        if (!dir.exists()) {
            dir.mkdirs();//如果没有partFiles目录,创建partFiles目录
        }
        while ((len = fis.read(buf)) != -1) {

//            从父抽象路径名和子路径名字符串创建新的 File实例。
            fos = new FileOutputStream(new File(dir, (count++) + ".part"));
            fos.write(buf, 0, len);
        }
        fos.close();
        fis.close();
    }

合并:

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

        File dir = new File("c:\\partfiles");

//        meger(dir);
        margeFile(dir);

    }
    //数据的合并。
    public static void margeFile(File dir) throws IOException {

        ArrayList<FileInputStream> al = new ArrayList<FileInputStream>();

        for (int x = 0; x < 3; x++) {
            al.add(new FileInputStream(new File(dir,x+".part")));

        }

        Enumeration<FileInputStream> en = Collections.enumeration(al);
        SequenceInputStream sis = new SequenceInputStream(en);

        FileOutputStream fos = new FileOutputStream(new File(dir,"1.bmp"));

        byte[] buf = new byte[1024];

        int len = 0;
        while ((len = sis.read(buf))!=-1){
            fos.write(buf,0,len);
        }
        fos.close();
        sis.close();
    }

文件切割合并+配置文件

package IO.SequenceInputStream;


import java.io.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Properties;

public class SplitFileDemo {

    private static final String CFG = ".properties";

    private static final String SP = ".part";


    public static void main(String[] args) throws IOException {
        File file = new File("c:\\pgMusic.mp3");

        File dir = new File("c:\\partfiles");

        splitFile(file);
        meger(dir);
//        margeFile(dir);

    }

//数据的合并。

    public static void meger(File dir) throws IOException {
        if (!(dir.exists() && dir.isDirectory()))

            throw new RuntimeException("指定的目录不存在,或者不是正确的目录");

        File[] files = dir.listFiles(new SuffixFilter(CFG));

        if (files.length != 1)

            throw new RuntimeException("扩展名.proerpties的文件不存在或不唯一");

//获取到配置文件

        File config = files[0];

//获取配置文件的信息。

        Properties prop = new Properties();

        FileInputStream fis = new FileInputStream(config);

        prop.load(fis);

        String fileName = prop.getProperty("filename");

        int partcount = Integer.parseInt(prop.getProperty("partcount"));

//—————————--------------------------------------------

        File[] partFiles = dir.listFiles(new SuffixFilter(SP));

        if (partFiles.length != partcount)

            throw new RuntimeException("缺少碎片文件");

//—————————--------------------------------------------

        ArrayList<FileInputStream> al = new ArrayList<FileInputStream>();

        for (int x = 0; x < partcount; x++) {
            al.add(new FileInputStream(new File(dir, x + SP)));
        }
        Enumeration<FileInputStream> en = Collections.enumeration(al);
        SequenceInputStream sis = new SequenceInputStream(en);
        File file = new File(dir, fileName);

        FileOutputStream fos = new FileOutputStream(file);
        byte[] buf = new byte[1024];

        int len = 0;

        while ((len = sis.read(buf)) != -1) {
            fos.write(buf, 0, len);

        }

        fos.close();

        sis.close();

    }


//带有配置信息的数据切割。

    public static void splitFile(File file) throws IOException {
//用一个读取流和文件关联。

        FileInputStream fis = new FileInputStream(file);

//创建目的地。因为有多个。所以先创建引用。

        FileOutputStream fos = null;

//指定碎片的位置。

        File dir = new File("c:\\partfiles");

        if (!dir.exists())

            dir.mkdir();

//碎片文件大小引用。

        File f = null;

        byte[] buf = new byte[1024 * 1024];

//因为切割完的文件通常都有规律的。为了简单标记规律使用计数器。

        int count = 0;

        int len = 0;

        while ((len = fis.read(buf)) != -1) {
            f = new File(dir, (count++) + ".part");

            fos = new FileOutputStream(f);

            fos.write(buf, 0, len);

            fos.close();

        }

//碎片文件生成后,还需要定义配置文件记录生成的碎片文件个数。以及被切割文件的名称。

//定义简单的键值信息,可是用Properties。

        String filename = file.getName();

        Properties prop = new Properties();

        prop.setProperty("filename", filename);

        prop.setProperty("partcount", count + "");

        File config = new File(dir, count + ".properties");

        fos = new FileOutputStream(config);

        prop.store(fos, "");

        fos.close();

        fis.close();

    }

    static class SuffixFilter implements FileFilter {
        private String suffix;

        SuffixFilter(String suffix) {
            this.suffix = suffix;

        }

        public boolean accept(File file) {
            return file.getName().endsWith(suffix);

        }
    }
}

对象的序列化

ObjectOutputStream

序列化:其实就是进行有序的进行排序

目的:将一个具体的对象进行持久化,写入到硬盘上。

Serializable:用于启动对象的序列化功能,可以强制让指定类具备序列化功能,该接口中没有成员,这是一个标记接口。这个标记接口用于给序列化类提供UID。这个uid是依据类中的成员的数字签名进行运行获取的。如果不需要自动获取一个uid,可以在类中,手动指定一个名称为serialVersionUID id号。依据编译器的不同,或者对信息的高度敏感性。最好每一个序列化的类都进行手动显示的UID的指定。

我们想的仅仅是把堆内存中的对象的生命周期延长,放到硬盘上进行持久化,我再要这些数据的时候,我不用再new了,直接从硬盘中读就可以了啊,所以这数据就让你保存用,没有让你去解析吧,解析有特定的方式所以也扩展名也不用。txt,用.object

package IO.ObjectStream;


import java.io.*;

public class ObjectStreamDemo {

    public static void main(String[] args) throws IOException,ClassNotFoundException {

        writeObj();
        readObj();
    }

    private static void readObj() throws IOException,ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.object"));
        //对象的反序列化
        Person p = (Person)ois.readObject();//读取一个对象。

        System.out.println(p.getName()+":"+p.getAge());

        ois.close();
    }

    public static void writeObj() throws IOException{

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.object"));
        //对象序列化。被序列化的对象必须实现Serializable接口。
        oos.writeObject(new Person("石硕",19)); //写入一个对象。
        oos.close();
    }
}

package IO.ObjectStream;

import java.io.Serializable;

/**
 * Serializble:用于给被序列化的类加入ID号
 * 用于判断类和对象是同一版本
 */
public class Person implements Serializable /*标记接口*/{

    private static final long serialVersionUID = 9527l;
	private transient String name;//用transient修饰后name将不会进行序列化
    private int age;

    public Person(String name, int age) {
        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;
    }
}

序列化接口-Serializable

image-20221129145628948

就是上面我们做的例子也,使用的是private String name,现在我们把这个改成了public String name来进行测试,反序列化,那个obj.object文件还是原来的private String name的方式,现在我们直接进行反序列化,就会出在下面的异常

image-20221129150116209

大家都知道编译器是不是分版本的啊,显示声明出来的好处不同版本的编译器很有可能会导致同一个类编译完的id号不一样,所以啊会导致那个对象取不出来,为了保证尽量一致,不因为编译的不同算的结果不一致,所以也显示声明出来,现在如果我们再进行测试就没有问题了

image-20221129150249169

有些对象是需要序列化的,服务器它就会对你的对象进行临时本地存储它怕服务器崩了以后,你的会话都被消失了,所以也它保存在了硬盘上,你重新启动服务器,就重新恢复你的会话,恢复那个对象,你当时的数据都在,服务器想要往硬盘上写,没有这个id号是不是很麻烦啊

transient关键字

如何将非静态的数据不进行序列化?用transient 关键字修饰此变量即可。

静态的数据,是放在静态区的,没有放在堆内存中,所以被保存在obj.object文件中去,

Transient(短暂的意思),就是在序列化的时候写不到硬盘上去而已

 /*
 * transient:非静态数据不想被序列化可以使用这个关键字修饰。

 */
public class Person implements Serializable /*标记接口*/{

    private static final long serialVersionUID = 9527l;
    private transient String name;
    private int age;
}

RandomAccessFile

image-20221129233539030

RandomAccessFile:

  特点:

    1:该对象即可读取,又可写入。

    2:该对象中的定义了一个大型的byte数组,通过定义指针来操作这个数组。

    3:可以通过该对象的getFilePointer()获取指针的位置,通过seek()方法设置指针的位置。

    4:该对象操作的源和目的必须是文件。

    5:其实该对象内部封装了字节读取流和字节写入流。

    注意:实现随机访问,最好是数据有规律。

手机视频,断点续传,使用的就是randomAccessFile

image-20221130135017727

class RandomAccessFileDemo{
    public static void main(String[] args) throws IOException{
      write();

      read();

      randomWrite();

    }

    //随机写入数据,可以实现已有数据的修改。

    public static void randomWrite()throws IOException{
      RandomAccessFile raf = new RandomAccessFile("random.txt","rw");

      raf.seek(8*4);

      System.out.println("pos :"+raf.getFilePointer());

      raf.write("王武".getBytes());

      raf.writeInt(102);

      raf.close();

    }

    public static void read()throws IOException{
      RandomAccessFile raf = new RandomAccessFile("random.txt","r");//只读模式。

      //指定指针的位置。

      raf.seek(8*1);//实现随机读取文件中的数据。注意:数据最好有规律。

      System.out.println("pos1 :"+raf.getFilePointer());

      byte[] buf = new byte[4];

      raf.read(buf);

      String name = new String(buf);

      int age = raf.readInt();

      System.out.println(name+"::"+age);

      System.out.println("pos2 :"+raf.getFilePointer());

      raf.close();

    }

    public static void write()throws IOException{
      //rw:当这个文件不存在,会创建该文件。当文件已存在,不会创建。所以不会像输出流一样覆盖。

      RandomAccessFile raf = new RandomAccessFile("random.txt","rw");//rw读写模式

      //往文件中写入人的基本信息,姓名,年龄。

      raf.write("张三".getBytes());

      raf.writeInt(97);

      raf.close();

    }

  }

管道流

image-20221130135140924

image-20221129171435735

管道流:管道读取流和管道写入流可以像管道一样对接上,管道读取流就可以读取管道写入流写入的数据。

  注意:需要加入多线程技术,因为单线程,先执行read,会发生死锁,因为read方法是阻塞式的,没有数据的read方法会让线程等待。


public class PipedStreamDemo {
    public static void main(String[] args) throws IOException {
        PipedInputStream input = new PipedInputStream();
        PipedOutputStream output = new PipedOutputStream();
        input.connect(output);

        new Thread(new Input(input)).start();
        new Thread(new Output(output)).start();
    }
}
class Input implements Runnable{

    private PipedInputStream in;

    public Input(PipedInputStream in) {
        this.in = in;
    }
    public void run(){

        try{
            byte[] buf = new byte[1024];
            int len = in.read(buf);

            String s = new String(buf,0,len);
            System.out.println("s=" + s);
            in.close();
        }catch (Exception e){

        }
    }
}class Output implements Runnable{

    private PipedOutputStream out;

    public Output(PipedOutputStream out) {
        this.out = out;
    }

    public void run(){
        try {
            Thread.sleep(5000);//将线程睡5秒,5秒后数据才会显示
            out.write("hi,管道来了".getBytes());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

操作基本数据类型的流对象

DataOutputStream、DataInputStream:专门用于操作基本数据类型数据的对象。

  

public static void writeData() throws IOException{
    
   DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));

  dos.writeUTF("你好");

  dos.close();
}
 public static void readData() throws IOException{
  DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));

	String str = dis.readUTF();

  System.out.println(str);

  dis.close();
 }

操作数组的流

image-20221130191537532

ByteArrayInputStream:源:内存

ByteArrayOutputStream:目的:内存。

  • 这两个流对象不涉及底层资源调用,操作的都是内存中数组,所以不需要关闭。
  • 直接操作字节数组就可以了,为什么还要把数组封装到流对象中呢?因为数组本身没有方法,只有一个length属性。为了便于数组的操作,将数组进行封装,对外提供方法操作数组中的元素。

对于数组元素操作无非两种操作:设置(写)和获取(读),而这两操作正好对应流的读写操作。这两个对象就是使用了流的读写思想来操作数组。

  //创建源:

  ByteArrayInputStream bis = new ByteArrayInputStream("abcdef".getBytes());

  //创建目的:

  ByteArrayOutputStream bos = new ByteArrayOutputStream();

  int ch = 0;

  while((ch=bis.read())!=—1){

    bos.write(ch);

  }

  System.out.println(bos.toString());

操作字符与字符串的流

image-20221130223518926

编码表

概述

image-20221201013121945

image-20221201013209146

UTF-8的编码规范

image-20221201170442524

简单的编码解码

“你好”用UTF-8编码为六个字节

image-20221201154741113

“你好”用GBK编码为4个字节,解出来也乱码

image-20221201154911680

编码解码问题

默认的解码表,解不出来后,可以将解的码,重新编码,用自己的编码表解码。

image-20221201163140313

image-20221201162822599

Tomcat服务器只认iso8859-1编码表,所以要用自己的编码表去适配解码。

image-20221201162906798

问题二

UTF-8用-17 -65 -67 表示未知字节,所以重新编码不行。

image-20221201165929385

image-20221201170050610

posted on 2023-02-10 10:36  cwhat  阅读(21)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3