JavaWeb笔记10:I/O(File,文件遍历,字节流,字符流,序列化)

I/O

IO体系:
按照流的流向分:输入流和输出流;
按照操作单元分:字节流和字符流;
I/O体系
Java IO流共涉及40多个类,IO流中的40多个类都是从以下4个抽象类基类中派生出来的:

  • InputStream / Reader: 从外部输入到内存,所有的输入流的基类,前者是字节输入流,后者是字符输入流。
  • OutputStream Writer: 从内存输出到外部,所有输出流的基类,前者是字节输出流,后者是字符输出流。

按操作对象分类结构图:
在这里插入图片描述
图片来源:https://blog.csdn.net/ThinkWon/article/details/104390612

1 Java中描述文件的类——File

java.io 包之中,用File 类来对文件进行操作(创建、删除、取得信息等)。

描述一份文件的两类信息:
1) 元信息:文件名、路径、创建日期、权限等
2 )内容数据

(1)java.io.File 类常用到两个构造方法:

public File(String pathname) :创建指定路径文件对象
public File(String parent, String child) :可将路径分为父路径和子路径

(2)File类的常用方法

public boolean exists()指定路径中的文件或者目录是否存在
public boolean isDirectory() :判定一个文件是否是目录
public boolean isFile() :判定是否是文件
public boolean delete() :删除文件
public boolean createNewFile() throws IOException:创建一个新文件

public boolean mkdir() :创建一个空目录
public boolean mkdirs() :创建目录(无论有多少级父目录,都会创建)
public String getParent() :取得父路径
public File getParentFile() :取得父File对象

public long length() :取得文件大小(字节)
public long lastModified() :最后一次修改日期

public File[] listFiles()列出一个目录指定的全部组成

(3)File代码演示

1)传入路径,构建文件对象:

String path = "E:\\world\\hello.txt";
File file = new File(path);
System.out.println(file);//E:\world\hello.txt

2)将路径分为父路径和子路径:

String parent = "E:\\hello";
String path = "hello.txt"";
File file = new File(parent, path);
System.out.println(file);//E:\hello\hello.txt
// 获取常见的属性
System.out.println(file.exists());
System.out.println(file.getAbsolutePath());
System.out.println(file.getName());
System.out.println(file.getParent());
System.out.println(file.isAbsolute());//是否为绝对路径
System.out.println(file.isDirectory());
System.out.println(file.isFile());
System.out.println(file.isHidden());
System.out.println(file.canRead());
System.out.println(file.canWrite());
System.out.println(file.canExecute());

注意:

  • 路径可以是相对路径也可以是绝对路径,hello.txt不存在也可以构建出来,构建的是一个抽象的文件对象。

3)文件的创建:

// 演示文件的创建(普通文件)
String parent = "E:\\hello\\directory";
String filename = "hello.txt";
File file = new File(parent, filename);
System.out.println(file.exists());
boolean success = file.createNewFile();
if (success) {
    System.out.println(filename + " 创建成功");
} else {
    System.out.println(filename + " 已经存在");
}

4)创建文件夹

// 演示文件夹的创建过程
String parent = "E:\\hello\\world";
String filename = "noExits\\newDirectory";
File file = new File(parent, filename);
System.out.println(file.exists());
// mkdir : make directory
//boolean success = file.mkdir();
// mkdirs: 会把需要创建的中间文件夹一并创建出来
boolean success = file.mkdirs();
if (success) {
    System.out.println(filename + " 文件夹创建成功");
} else {
    System.out.println(filename + " 文件夹原来就存在");
}

注意:

  • 创建文件时,如果路径不存在会抛异常
  • mkdir创建文件夹时,如果路径不存在会返回false(file.mkdir()返回false)
  • mkdirs创建文件夹时,如果路径不存在会将中间缺失的路径文件夹补全

5)删除文件
file.delete()

String parent = "E:\\hello";
String filename = "hello.txt";
File file = new File(parent, filename);
System.out.println(file.exists());
// 只能删除一个空的文件夹
boolean delete = file.delete(); // 不是移动到回收站,而是直接删除
if (delete) {
    System.out.println("删除了");
} else {
    System.out.println("不存在");
}

file.deleteOnExit()

String parent = "E:\\world";
String filename = "hello.txt";
File file = new File(parent, filename);
System.out.println(file.exists());
file.deleteOnExit();// 也是把文件删掉,但不是马上删,而是等 JVM 进程结束后才删除
Scanner scanner = new Scanner(System.in);
System.out.println("只要不按回车,就不会删除");
scanner.nextLine();

注意:

  • delete()、deleteOnExit()只能删除空的文件夹
  • deleteOnExit():JVM结束运行后后才删除

6)获取文件列表

String path = "E:\\world\\hello";
File dir = new File(path);
File[] files = dir.listFiles();
if (files != null) {
    for (File file : files) {
        System.out.println(file.getName());
    }
}

注意:

  • files==null表示发生了路径错误等IO错误
  • files.length == 0表示文件列表为空
  • 实际项目部署环境可能与开发环境不同。windows 下使用的路径分隔符是\ ,而Unix/Linux 系统下使用的是/。所以在使用路径分隔符时都会采用File类的一个常量public static final String separator 来描述 System.out.println(File.separator); \ 。
  • 文件部分异常,常见为IOException
(4)文件夹的遍历(树)

深度优先的遍历(前中后序遍历): 用栈(手动栈,递归栈)
广度优先的遍历(层序遍历): 用队列

1)深度优先遍历
深度遍历

    //使用递归栈实现深度优先队列
    private static void travelDepth(File node) {
        System.out.println(node.getAbsolutePath());
        // 判断 node 是不是目录
        if (node.isDirectory()) {
            File[] files = node.listFiles();
            if (files != null) {
                for (File file : files) {
                    travelDepth(file);
                }
            }
        }
    }

是叶子节点直接打印,不是叶子节点继续递归调用。

2)广度优先遍历:
广度遍历

//队列先进先出,实现广度优先遍历
    private static void travelBroad(File root) {
        Queue<File> queue = new LinkedList<>();
        queue.add(root);
        while (!queue.isEmpty()) {
            File front = queue.remove();
            System.out.println(front.getAbsolutePath());
            if (front.isDirectory()) {
                File[] files = front.listFiles();
                if (files != null) {
                    for (File file : files) {
                        queue.add(file);
                    }
                }
            }
        }
    }
import java.io.File;
import java.util.LinkedList;
import java.util.Queue;

public class HowScanDirectory {
    public static void main(String[] args) {
        File root = new File("E:\\HelloWorld\\hello");
        travelDepth(root);
        travelBroad(root);
    }
}
(5)相对路径:进程启动时的路径

绝对路径:从根目录出发
相对路径:以当前文件夹出发,相对与某个基准目录的路径。
相对路径其实是相对于工作路径

import java.io.File;
import java.io.IOException;
public class RelativePathDemo {
public static void main(String[] args) throws IOException {
		//获取工作路径
        String workingDirectory = System.getProperty("user.dir");
        System.out.println("工作路径,就是进程启动时所在路径");
        System.out.println(workingDirectory);
        String filename = "hello.txt";
        File file = new File(filename);
        System.out.println(file.getAbsolutePath());
        file.createNewFile();
    }
}
2 流

流: 在 Java中所有数据都是使用流读写的。流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。

流的作用: 为数据源和目的地建立一个输送通道。

按照流向: 输入流;输出流
输入流InputStream/Reader: 从各种输入设备,以二进制流(字节流)的形式,输入到内存中。
输出流OutputStream/Writer: 从内存中,以二进制流(字节流)的形式,输出到各种外设中。

按照处理数据的单位: 字节流(8位的字节);字符流(16位的字节)
字节流: 数据流中最小的数据单元是字节。InputStream、OutputStream。
字符流: 数据流中最小的数据单元是字符,Java中的字符是Unicode编码,一个字符占用两个字节。Reader、Writer。

IO体系

(1)字节流

InputStream ——输入字节流
OutputStream ——输出字节流

1)输入字节流

public  class FileInputStream extends InputStream {}

FileInputStream

  • 从文件系统中的某个文件中获得输入字节。
  • 用于读取诸如图像数据之类的原始字节流。

FileInputStream(File file) :通过打开与实际文件的连接创建一个FileInputStream ,该文件由文件系统中的File 对象file 命名
FileInputStream(String name) :通过打开与实际文件的连接来创建一个FileInputStream ,该文件由文件系统中的路径名name 命名。

read()一次只读一个字节,当全部读完时返回一个-1

        InputStream is = new FileInputStream("用于读的文件.txt");
        int b;
        while ((b = is.read()) != -1) {     
            System.out.println(b);//ACSII码
        }
		is.close();

read()中传入数组进行读取:

    try(InputStream is = new FileInputStream("hello.txt")){
            byte[]  buf = new byte[1024];
            int len;
            while((len = is.read(buf))!=-1){
                for(int i=0;i<len;i++){
                    System.out.println(buf[i]);//ASCII码
                    char c =(char)buf[i];
                    System.out.println(c);
                }
            }
        }

read返回值:

 @return the total number of bytes read into the buffer, 
 or<code>-1</code> if there is no more data because the 
 end of the stream has been reached.

2)输出字节流
文件输出流是用于将数据写入到输出流File 或一个FileDescriptor

public class FileOutputStream extends OutputStream

FileOutputStream(File file) :创建文件输出流以写入由指定的File 对象表示的文件。
FileOutputStream(String name):创建文件输出流以指定的名称写入文件

一次写入一个字节,及以字节数组的形式写入:

try(OutputStream os = new FileOutputStream(“输出.txt”)){
	os.write('h');
	os.write('\r');
	os.write('\n');
	String s ="world\r\n";
	byte[] bytes = s.getBytes();
	os.write(bytes);
	os.flush();
}

注意:

  • 在进行写入时,会覆盖文件中原有的内容。
  • 输出流为了减少IO的次数可能进行了缓冲,即没有直接写到硬盘中,而是写到内存中。所以用flush()保证了所有的缓存数据都写到内存中。 在执行写操作时,必须使用flush()。
  • 在调用os.close();时内部也会调用os.flush()
  • \r回到行首 Carriage Return CR; \n换行 Line Feed LF
  • 在Windox上,CRLF合起来才是换行; Linux上,LF就是换行; Java中直接采用%n, JVM会根据不同的操作系统进行调整。
(2)字符和字节转换

字节——字符(解码)
字符——字节(编码)

输入字节流转字符需要解码,通过传入合适的字符集,对字节进行解码。 String s = new String(buf, 0, len, charset); //这里对文件中的字节进行解码,charset为指定字符集

utf8.txt:为utf8编码的文档
gb18030.txt:为gb18030编码的文档

public class InputStreamDemo {
    public static void main(String[] args) throws IOException {
        try (InputStream is = new FileInputStream("utf8.txt")) {
            readContent(is, "UTF-8"); 
        }
        try (InputStream is = new FileInputStream("gb18030.txt")) {
            readContent(is, "GB18030");
        }
    }

    private static void readContent(InputStream is, String charset) throws IOException {
        byte[] buf = new byte[8192];
        int len;
        while ((len = is.read(buf)) != -1) {
            for (int i = 0; i < len; i++) {
                System.out.println(buf[i]);
            }
            String s = new String(buf, 0, len, charset); //这里对文件中的字节进行解码,charset为指定字符集
            System.out.println(s);
        }
    }
}

输出字符转字节需要编码,通过传入合适的字符集,对字符进行编码。 byte[] bytes = s.getBytes(charset);//将字符编码

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class OutputStream {
    public static void main(String[] args) throws IOException {
        String s = "hello";
        try (OutputStream os = new FileOutputStream("utf8.txt")) {
            writeContent(os, s, "UTF-8");
        }
        try (OutputStream os = new FileOutputStream("gb18030.txt")) {
            writeContent(os, s, "GB18030");
        }
    }

    private static void writeContent(OutputStream os, String s, String charset) throws IOException {
        byte[] bytes = s.getBytes(charset);//将字符编码
        System.out.println(charset);
        System.out.println(bytes.length);
        os.write(bytes);
        os.flush();
    }
}
(3)字符流

1)输入字符流
InputStreamReader是Reader的子类,将输入的字节流转换成字符流

public class InputStreamReader extends Reader

InputStreamReader(InputStream in):创建一个使用默认字符集的InputStreamReader。
InputStreamReader(InputStream in,Charset cs):创建一个使用给定字符集的InputStreamReader

InputStreamReader isReader = new InputStreamReader(is, "UTF-8"))
isReader.read();
char[] bur=new char[1024];
isReader.read(buf); 

isReader.read();返回的是字符。

下面给出一种更常用的用法,将流传入Scanner中:

 public static void main() throws IOException {
        try (InputStream is = new FileInputStream("hello.txt")) {
            try (InputStreamReader isReader = new InputStreamReader(is, "UTF-8")) {
                try (Scanner scanner = new Scanner(isReader)) {
                    // 按行读
                    while (scanner.hasNextLine()) {
                        String line = scanner.nextLine();
                    }
                }
            }
        }
    }

2)输出字符流
OutputStreamWriter是Writer的子类,将输出的字符流转换成字节流。

public class OutputStreamWriter extends Writer

OutputStreamWriter(OutputStream out)创建一个使用默认字符编码的OutputStreamWriter。
OutputStreamWriter(OutputStream out,Charset cs)创建一个使用给定字符集的OutputStreamWriter。

将流传入到PrintWriter中,需要传入一个OutputStreamWriter对象

    // 写的文本
    private static void writeTest() throws IOException {
        try (OutputStream os = new FileOutputStream("某篇文章.txt")) {
            try (OutputStreamWriter osWriter = new OutputStreamWriter(os, "UTF-8")) {
                try (PrintWriter printWriter = new PrintWriter(osWriter)) {
                    printWriter.println("你好世界");
                    printWriter.printf("哈哈");
                    printWriter.print("哈");
                    printWriter.format(" ");
                    printWriter.flush();
                }
            }
        }
    }
}

PrintWriter printWriter = new PrintWriter(osWriter,true)可以自动刷新,代替printWriter.flush();

字节流对比字符流
1、 字节流操作的基本单元是字节;字符流操作的基本单元为Unicode码元。
2、 字节流在操作的时候本身不会用到缓冲区的,是与文件本身直接操作的;而字符流在操作的时候使用到缓冲区的。
3、 所有文件的存储都是字节(byte)的存储,在磁盘上保留的是字节。
4、 在使用字节流操作中,即使没有关闭资源(close方法),也能输出;而字符流不使用close方法的话,不会输出任何内容。

注意:

  • 图片等二进制文件,需要用InputStream和OutputStream(传入数组);
  • 文本等字符形式,需要用InputStreamReader和OutputStreamWriter,其中读用Scanner的方式,写用PrintWriter的方式。
3 序列化(Serialize)与反序列化

序列化:把对象转换为字节序列的过程称为对象的序列化。
反序列化:把字节序列恢复为对象的过程称为对象的反序列化。

什么时候需要序列化?
1、把内存中的对象状态保存到一个文件中或者数据库中时候;
2、用套接字在网络上传送对象的时候;

实现序列化的方式:
1,简单方案,放在一行以逗号分割
CSV Comma Seperate Value以逗号分割

2.JSON——JavaScript ObJect Notation
键值对的形式存储

3.XML eXtensionable Markup Language

<person>
<person name=”hot” age = 16></person>
</person>

4.JAVA中的序列化,二进制格式的序列:占用空间更小,存储更小,传输更快要想序列化,需要实现Serializable接口

ObjectOutputStream 代表对象输出流:它的writeObject(Object obj) 方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
ObjectInputStream 代表对象输入流:它的readObject() 方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。

import java.io.*;
public class SerializeDemo {
    // 声明实现 Serializable 接口
    // 声明之后,就拥有了可以序列号和反序列化的功能了
    static class Person implements Serializable {
        String name;
        int age;
    }
}

写对象
ObjectOutputStream oos = new ObjectOutputStream(os)oos.writeObject(p)

//写对象
    public static void main(String[] args) throws IOException {
        Person p = new Person();
        p.name = "小明"; p.age = 84;
        Person q = new Person();
        q.name = "小红"; q.age = 8;

        try (OutputStream os = new FileOutputStream("找到的卧底的名单.binary")) {
            try (ObjectOutputStream oos = new ObjectOutputStream(os)) {
                oos.writeObject(p);
                oos.writeObject(q);

                oos.flush();
            }
        }
    }

读对象
ObjectInputStream ois = new ObjectInputStream(is)
Person p = (Person) ois.readObject();

//读对象
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        try (InputStream is = new FileInputStream("找到的卧底的名单.binary")) {
            try (ObjectInputStream ois = new ObjectInputStream(is)) {
                Person p = (Person) ois.readObject();
                System.out.println(p.name);
                System.out.println(p.age);
                System.out.println("======================");
                Person q = (Person) ois.readObject();
                System.out.println(q.name);
                System.out.println(q.age);
            }
        }
    }

注意:

  • 类中的静态变量的值是不会被进行序列化的,transient 修饰的属性,是不会被序列化的。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值