IO文件流

IO流的含义:

Java的IO流是实现输入和输出的基础,可以方便的实现数据的输入和输出操作。在java中把不同的输入/输出源(键盘,文件,网络连接等)抽象表述为“流”(stream)。

流的分类

按照 流的文件类型:字节流(InputStream、OutputStream)和字符流(Reader,Writer) 按照 流的流向:输入流和输出流 按照 功能 :节点流 和 过滤流

212932.png

所有文件数据(文本、图片、视频等)在存储时,都是二进制数字的形式,也就是一个一个的字节,传输时一样如此。所以,字节流可以传输任意文件数据。 绝对路径:一个完整的路径,以盘符开头,例如F://123.txt 相对路径:一个简化的路径,不以盘符开头,例如//123.txt//b.txt。

常用API

字符流

输入流创建

// 使用File对象创建流对象
        File file = new File("xxx.txt");
        FileReader reader= new FileReader(file);
      
        // 使用文件名称创建流对象
        FileReader reader = new FileReader("a.txt");

读取字符数据

// 使用文件名称创建流对象
       	FileReader reader = new FileReader("xxx.txt");
      	// 定义变量,保存数据
        int len = -1;
        // 循环读取
        while ((len = reader .read())!=-1) {
            System.out.println((char)len);
        }
		// 记得关闭资源
        reader .close();

输出流类比一下

获取功能

getAbsolutePath() :返回此File的绝对路径名字符串。 getPath() :将此File转换为路径名字符串。 getName() :返回由此File表示的文件或目录的名称。 length() :返回由此File表示的文件的长度(字节)

如果File是目录,则返回值未指定。

判断功能

exists() :此File表示的文件或目录是否存在。 isDirectory() :此File表示的是否为 目录。 isFile() :此File表示的是否为 文件。

创建、删除功能

createNewFile() :文件不存在,创建一个新的空文件并返回true,文件存在,不创建文件并返回false。 delete() :删除由此File表示的文件或目录。 mkdir() :创建由此File表示的目录。如果父类不存在,则创建失败 mkdirs():创建由此File表示的目录,同时创建不存在的父级目录。 所以一般选用 mkdirs()

遍历目录

public static List<File> searchFiles(File directory, Predicate<File> predicate) {
        //返回存储文件的容器
        List<File> files = searchFiles(directory);
File dir = new File("G:\光标");
		//造存放file的容器
		File[] files = dir.listFiles();
        //获取当前目录下的文件以及文件夹对象
        for (File file : files) {
            System.out.println(file);
        }
    }

listFiles指定目录必须存在,且必须指定目录,指定文件会发生空指针异常

  File file=new File("D:\\XXX");
List<File> list = Arrays.asList(file.listFiles());
		//判空
        if (file != null) {
            //遍历list
            for (File f : list) {
                if (f.isDirectory()) searchFiles(f);
                else list.add(f);

            }
        }

那么什么时候用字节流什么时用字符流呢 在遍历文件时候,一般选择字符流 而对文件的内容进行读写操作时一般用字节流 无论使用什么样的流对象,底层传输的始终为二进制数据。但在用字符流读写文件内容时,规定的字符容器可能会大于需要读写的的内容(字节)

字节流

字节输、出流(OutputStream)

构造方法

public FileOutputStream(File file):以File对象为参数创建对象。 public FileOutputStream(String name): 以名称字符串为参数创建对象。

开发常用第二种,指定路径字符串

注意:创建输出流对象的时候,如果路径不存在,系统会自动去对应位置创建对应文件 输入流对象的时候,文件不存在则会报FileNotFoundException异常

写出字节、字节数组、指定长度字节数组:

 // 使用文件名称创建流对象
        FileOutputStream out = new FileOutputStream("XXX.系统自己创建的.txt");     
      	// 向这个文件里写出数据
      	out .write(97); // 一次只写出一个字节
      	out .write(98); //输出的是ASCII字节码 
      	out .write(99); //结果为abc
      	// 记得关闭资源
        fos.close();
// 如果想要一些写出多个字节  可以使用字节数组
      	byte[] b = "麻麻我想吃烤山药".getBytes();
      	// 写出字节数组数据
      	out .write(b);
      	out.write(b,0,2);//从下标0开始 写出2个字节
		
      	// 关闭资源
        fos.close();

new FileOutputStream(“xxx.txt”,boolean b);

当我向有数据的文件中写出字节时,可以传入第二个参数Boolean boolean为ture时,表示追加数据,不会对元数据进行覆盖。 false 表示不追加 也就是清空原有数据。

字节输入流(InputStream)

构造方法类比输出流 同样推荐第二种狗构造方法

当你创建一个流对象时,传入的路径下,该文件,会抛出

FileNotFoundException 。

关于使用字节输入流,读取字节数组时,可能出现的问题:

// 使用文件名称创建流对象.
   	FileInputStream in = new FileInputStream("read.txt"); // 假定内容为abcde
  	// 定义变量,作为有效个数
    int len = -1;
    // 定义字节数组,作为装字节数据的容器   
    byte[] b = new byte[2];//输出结果 不正常的问题所在
    // 循环读取
    while (( len= in .read(b))!=-1) {
       	// 每次读取后,把数组变成字符串打印
        System.out.println(new String(b));
    }

输出结果为 ab cd ed 为何:因为长度为2的数组,每次读取2个字符,下次读取时,另外两个字符将原字符覆盖 第一次:ab 第二次:c覆盖a,d覆盖b 结果为cd 第三次:e覆盖d,原来的d没有被覆盖,所以结果为 ed

close() :关闭流 flush() :强制缓冲的输出字节写出。 write(byte[] b):将Byte数组 .length()个字节数组写入输出流。 write(byte[] b, int off, int len) :从偏移量 off开始输出到此输出流,读取len个字符 write(int b) :将指定的字节输出流。

缓冲流(掌握即可)

创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率。

构造方法:

public BufferedInputStream(InputStream in) :创建一个新的缓冲输入流,注意参数类型为InputStream。 字节输出流一样。 举例:

BufferedInputStream bis = new BufferedInputStream(new FileInputStream(“xxx.txt”));

字符缓冲流:

BufferedReader,BufferedWriter

字符缓冲流特有方法:

 // 创建流对象
        BufferedReader br = new BufferedReader(new FileReader("a.txt"));
		// 定义字符串,保存读取的一行文字
        String line  = null;
      	// 循环读取,读取到最后返回null
        while ((line = br.readLine())!=null) {//读一行数据
            System.out.print(line);
            System.out.println("------");
        }
		// 关流
        br.close();

字节与字符的转换:

编码:字符(能看懂的) –> 字节(看不懂的) 解码:字节(看不懂的) –> 字符(能看懂的)

FileReader 读取项目中的文本文件。由于IDEA的设置,都是默认的UTF-8编码 但是,由于Windows系统的默认是GBK编码,读取Windows系统中创建的文本文件时,就会出现乱码。

FileReader reader = new FileReader("C:\\xxx.txt");//写点中文
        int len = -1;
        while ((len = reader .read()) != -1) {
            System.out.print((char)len );
        }
        reader .close();

序列化与反序列化

Java 提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该对象的数据、对象的类型和对象中存储的属性等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息

1.该类必须实现java.io.Serializable 接口,Serializable 是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException 。 2.该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用transient 关键字修饰。

public class User implements Serializable {


    private static final long serialVersionUID = -6180688742475305117L;

    private Integer id ;

    private String username ;

    /**
     *  transient : 瞬时,短暂的
     *
     *  用来定义 不需要进行 序列化的 字段
     *
     */
    private transient String password ;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

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

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("User{");
        sb.append("id=").append(id);
        sb.append(", username='").append(username).append('\'');
        sb.append(", password='").append(password).append('\'');
        sb.append('}');
        return sb.toString();
    }
}
public class UserSerializer {

    @Test
    public void test() throws Exception {

        /**
         * 将 User 对象,存储到磁盘的过程,被称为 对象的序列化操作
         *
         * 如果想要将一个对象,进行序列化,则 必须让该对象 实现 Serializable 接口
         *
         */
        // 创建一个序列化对象的流
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("D:/user.txt"));


        // 创建一个 User
        User user = new User();
        user.setId(1);
        user.setUsername("张三");
        user.setPassword("1234567");


        // 将 user 对象,存储到磁盘中
        out.writeObject(user);


        // 关闭流
        out.close();

    }

    @Test
    public void test2() throws Exception{

        ObjectInputStream in = new ObjectInputStream(new FileInputStream("D:/user.txt"));

        // 将 User 从磁盘读入到内存
        Object o = in.readObject();

        if (o instanceof User) {

            User user = (User) o ;

            System.out.println(user);

        }

    }
}

序列化练习题:

public class Test2 {

    @Test
    public void test(){

        Goods goods = new Goods("001","奔驰",50.0,1);

        //序列化
        System.out.println(JSONArray.toJSON(goods));


        //反序列化
        String josn = "{\"Id\":001,\"Name\":奔驰,\"Num\":1,\"Price\":50.0";

        Goods goods1 = JSONArray.parseObject(josn,Goods.class);

        System.out.println(goods1);
	}

}
@AllArgsConstructor
@NoArgsConstructor
class Goods{
    private String Id;
    private String Name;
    private Double Price;
    private Integer Num;


    @Override
    public String toString() {
        final StringBuffer sb = new StringBuffer("Goods{");
        sb.append("Id='").append(Id).append('\'');
        sb.append(", Name='").append(Name).append('\'');
        sb.append(", Price=").append(Price);
        sb.append(", Num=").append(Num);
        sb.append('}');
        return sb.toString();
    }

    public String getId() {
        return Id;
    }

    public void setId(String id) {
        Id = id;
    }

    public String getName() {
        return Name;
    }

    public void setName(String name) {
        Name = name;
    }

    public Double getPrice() {
        return Price;
    }

    public void setPrice(Double price) {
        Price = price;
    }

    public Integer getNum() {
        return Num;
    }

    public void setNum(Integer num) {
        Num = num;
    }
}

装饰模式(掌握即可)

一种类与类交互的模式

甜品抽象类:

public abstract class Sweet {

    /**
     * 售卖甜点
     */

    public abstract int sell() ;

    /**
     * 售卖的价格
     * @return
     */
    public abstract int sellPrice() ;
}

蛋糕类

public class CakeSweet extends Sweet{
    @Override
    public int sell() {

        System.out.println("普通蛋糕");

        return sellPrice();
    }

    @Override
    public int sellPrice() {
        return 50;
    }
}

装饰(加工)类

public class CakeDecrator extends  Sweet{

    //维护一个 被装饰的 蛋糕对象
    private Sweet sweet ;

    private String fruit ;

    public CakeDecrator(Sweet sweet, String fruit) {
        this.sweet = sweet ;
        this.fruit = fruit ;
    }

    public void sellFruit() {
        System.out.println("本次出售的是" + fruit);
    }


    @Override
    public int sell() {
        int price = sellPrice();

        // 调用普通蛋糕价钱
        sweet.sell();

        System.out.println("本次甜点添加了" + fruit);

        return price ;
    }

    @Override
    public int sellPrice() {
        //加了啥  多多少钱
        int price = switch (fruit) {

            case "苹果" -> 3 ;
            case "葡萄" -> 6 ;
            default -> 1 ;
        };
        //买蛋糕的钱 + 装饰调料的钱
        return sweet.sellPrice() + price;
    }
}