Java13-IO流

NiuMT 2020-06-03 20:58:30
Java

File类的使用

java.io.File 类: 文件和文件目录路径的抽象表示形式 ,与平台无关。File的一个对象,代表一个文件或文件目录。

File 能新建、删除、重命名文件和目录,但 File ==不能访问文件内容==本身。如果需要访问文件内容本身,则需要使用输入输出流 。

想要在 Java 程序中表示一个真实存在的文件或目录,那么必须有一个 File 对象 ,但是 Java 程序中的一个 File 对象,可能没有一个真实存在的文件或目录 。

File 对象可以作为参数传递给流的构造器。

// 创建File实例
public File(String pathname);// 以pathname 为路径创建 File 对象,可以是 绝对路径或者相对路径 ,如果pathname 是相对路径,则默认的当前路径在系统属性 user.dir 中存储。

// 方式1
public static final String separator; // File 类提供了一个常量,根据操作系统,动态的提供分隔符。
File file2 = new File(File(" d:"+ File.separator + " atguigu"+ separator + "info.txt");

// 方式2
public File(String parent,String child); //以parent 为父路径, child 为子路径创建 File 对象 。

// 方式3
public File(File parent,String child);//根据一个父File 对象和子文件路径创建 File 对象

常用函数:

public String getAbsolutePath(); // 获取绝对路径
public String getPath(); // 获取传入的路径
public String getName(); // 获取文件名称
public String getParent(); // 获取上层文件目录路径。 若无,返回null
public long length(); // 获取文件长度,即:字节数。 不能获取目录的长度。
public long lastModified(); // 获取最后一次的修改时间,毫秒值

public String[] list(); // 获取指定目录下的所有文件或者文件目录的名称数组,仅包含当前文件夹下的文件夹和文件
public File[] listFiles(); // 获取指定目录下的所有文件或者文件目录的File数组,仅包含当前文件夹下的文件夹和文件

public boolean renameTo(File dest); // 把文件重命名为指定的文件路径
// file1.renameTo(file2); 需要保证file1存在,file2不存在。若为True,move(非copy)的同时rename

public boolean isDirectory(); // 判断是否是文件目录
public boolean isFile(); // 判断是否是文件
public boolean exists(); // 判断是否存在
public boolean canRead(); // 判断是否可读
public boolean canWrite(); // 判断是否可写
public boolean isHidden(); // 判断是否隐藏

/*
文件确实存在硬盘中存在,创建File对象时,各个属性会显式赋值;文件不存在时,除了指定的目录和路劲之外,其余属性取成员变量的默认值。
*/

public boolean createNewFile(); // 创建文件。 若文件存在,则不创建,返回false
public boolean mkdir(); // 创建文件目录 。 如果此文件目录存在,就不创建了。
public boolean mkdirs(); // 创建文件目录 。 如果上层文件目录不存在一并创建
// 注意事项:如果你创建文件或者文件目录没有写盘符路径 那么默认在项目路径下。

public boolean delete(); // 删除文件或者文件夹,删除文件夹只能删空文件夹!
// 删除注意事项:Java中的删除不走回收站。要删除一个文件目录请注意该文件目录内不能包含文件或者文件目录

IO 流原理及流的分类

java.io 包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过 标准的方法 输入或输出数据。

(抽象基类) 字节流 字符流
输入流 InputStream Reader
输出流 outputStram Writer

image-20201026173126603

image-20201026185116912

image-20201026230805141

InputStream
int read(); // 从输入流中读取数据的下一个字节。返回 0 到 255 范围内的 int 字节值 。 如果因为已经到达流末尾而没有可用的字节 则返回值 1
int read(byte[] b); // 从此输入流中将最多b.length 个字节的数据读入一个 byte 数组中 。 如果因为已经到达流末尾而没有可用的字节, 则返回值 1。 否则以整数形式返回实际读取的字节数 。
int read(byte[] b, int off,int len); // 将输入流中最多len 个数据字节读入 byte 数组 。 尝试读取 len 个字节, 但读取的字节也可能小于该值。 以整数形式返回实际读取的字节数。 如果因为流位于文件末尾而没有可用的字节, 则返回值 1 。
public void close() throws IOException; // 关闭此输入流并释放与该流关联的所有系统资源 。


Reader
int read(); // 读取单个字符。作为整数读取的字符, 范围在 0 到 65535 之间 0 x 00-0 xffff) 2 个字节的 Unicode 码, 如果已到达流的末尾, 则返回 1
int read(char[] cbuf); // 将字符读入数组。如果已到达流的末尾, 则返回 1 。 否则返回本次读取的字符数。
int read(char[] cbuf,int off,int len); // 将字符读入数组的某一部分。 存到数组 cbuf 中, 从 off 处开始存储, 最多读 len 个符 。 如果已到达流的末尾, 则返回 1。 否则返回本次读取的字符数。
public void close() throws IOException; // 关闭此输入流并释放与该流关联的所有系统资源 。


OutputStream
void write(int b); // 将指定的字节写入此输出流。 write 的常规协定是:向输出流写入一个字节。 要写入的字节是参数 b 的八个低位 。 b 的 24 个高位将被忽略。 即写入 0-255 范围 的。
void write(byte[] b); // 将b length 个字节从指定的 byte 数组写入此输出流 。 write(b) 的常规协定是:应该与调用 write(b, 0, b.length) 的效果完全相同。
void write(byte[] b,int off,int len); // 将指定byte数组中从偏移量 off 开始的 len 个字节写入此输出流 。
public void flush()throws IOException; // 刷新此输出流并强制写出所有缓冲的输出字节 调用此方法指示应将这些字节立即写入它们预期的目标。
public void close() throws IOException; // 关闭此输出流并释放与该流关联的所有系统资源。


Writer
void write(int c);// 写入单个字符。 要写入的字符包含在给定整数值的 16 个低位中, 16 高位被忽略。 即写入 0 到 65535 之间的 Unicode 码 。
void write(char[] cbuf); // 写入字符数组。
void write(char[] cbuf,int off,int len); // 写入字符数组的某一部分。 从 off 开始写入 len 个字符
void write(String str); // 写入字符串。
void write(String str,int off,int len); // 写入字符串的某一部分。
void flush(); // 刷新该流的缓冲则立即将它们写入预期目标 。
public void close() throws IOException; // 关闭此输出流并释放与该流关联的所有系统资源。

节点流或文件流

FileReader

FileWriter

FileWriter fw = new FileWriter(new File("Test.txt"));
/*File对应的硬盘中的文件如果不存在,在输出的过程中,会自动创建此文件。
File对应的硬盘中的文件如果存在:
    如果流使用的构造器是:FileWriter(file,false) 或 FileWriter(file):对原有文件的覆盖
    如果流使用的构造器是:FileWriter(file,true):不会对原有文件覆盖,而是在原有文件基础上追加内容*/
@Test
public void testFileReaderFileWriter() {
    FileReader fr = null;
    FileWriter fw = null;
    try {
        //1.创建File类的对象,指明读入和写出的文件
        File srcFile = new File("hello.txt");
        File destFile = new File("hello2.txt");

        //不能使用字符流来处理图片等字节数据
//      File srcFile = new File("爱情与友情.jpg");
//      File destFile = new File("爱情与友情1.jpg");
        //2.创建输入流和输出流的对象
        fr = new FileReader(srcFile);
        fw = new FileWriter(destFile);

        //3.数据的读入和写出操作
        char[] cbuf = new char[5];
        int len;//记录每次读入到cbuf数组中的字符的个数
        while((len = fr.read(cbuf)) != -1){
            //每次写出len个字符
            fw.write(cbuf,0,len);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        //4.关闭流资源
        //方式一:
//        try {
//            if(fw != null)
//                fw.close();
//        } catch (IOException e) {
//            e.printStackTrace();
//        }finally{
//            try {
//                if(fr != null)
//                    fr.close();
//            } catch (IOException e) {
//                e.printStackTrace();
//            }
//        }
        //方式二:
        try {
            if(fw != null)
                fw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            if(fr != null)
                fr.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

缓冲流

为了提高数据读写的速度 Java API 提供了带缓冲功能的流类,在使用这些流类时,会创建一个内部缓冲区数组,缺省使用 8192 个 字节 (8Kb) 的缓冲区 。

当读取数据时,数据按块读入缓冲区,其后的读操作则直接访问缓冲区。当使用 BufferedInputStream 读取字节文件时 BufferedInputStream 会一次性从文件中读取 8192 个 (8Kb),存在缓冲区中直到缓冲区装满了, 才重新从文件中读取下一个 8192 个字节数组 。向流中写入字节时不会直接写到文件,先写到缓冲区中,直到缓冲区写满BufferedOutputStream 才会把缓冲区中的数据一次性写到文件里 。==使用方法flush() 可以强制将缓冲区的内容全部写入输出流==。如果是带缓冲区的流对象的 close() 方法,不但会关闭,流 还会在关闭流之前刷新缓冲区,关闭后不能再写出。

缓冲 流要“套接”在相应的节点流之上,根据数据操作单位可以把缓冲流分为:

//造流
//1 造节点流
FileInputStream fis = new FileInputStream((srcFile));
FileOutputStream fos = new FileOutputStream(destFile);
//2 造缓冲流
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);
//资源关闭
//要求:先关闭外层的流,再关闭内层的流
//说明:关闭外层流的同时,内层流也会自动的进行关闭。关于内层流的关闭,我们可以省略.
//创建文件和相应的流
br = new BufferedReader(new FileReader(new File("dbcp.txt")));
bw = new BufferedWriter(new FileWriter(new File("dbcp1.txt")));
//读写操作
//方式一:使用char[]数组
//char[] cbuf = new char[1024];
//int len;
//while((len = br.read(cbuf)) != -1){
//bw.write(cbuf,0,len);
//    //bw.flush();
//}

//方式二:使用String
String data;
while((data = br.readLine()) != null){
//方法一:
//bw.write(data + "\n");//data中不包含换行符
//方法二:
bw.write(data);//data中不包含换行符
bw.newLine();//提供换行的操作
}

转换流

转换流提供了在字节流和字符流之间的转换

Java API 提供了两个转换流:

字节流中的数据都是字符时,转成字符流操作更高效 。很多时候我们使用转换流来处理文件乱码问题。实现编码和解码的功能。

InputStreamReader

构造器:需要 和 InputStream “套接”。

OutputStreamWriter:

构造器:需要 和 OutputStream “套接”。

常见的编码表

  1. ASCII:美国标准信息交换码。用一个字节的 7 位可以表示。
  2. ISO8859-1:拉丁码表。欧洲码表,用一个字节的 8 位表示。
  3. GB2312:中国的中文编码表 。最多两个字节编码所有字符
  4. GBK:中国的中文编码表升级,融合了更多的中文文字符号。最多两个字节编码
  5. Unicode:国际标准码,融合了目前人类使用的所有字符。为每个字符分配唯一的字符码。所有的文字都用两个字节来表示。
  6. UTF-8:变长的编码方式,可用 1-4个字节 来表示一个字符。
  7. GBK 等双字节编码方式,用最高位是 1 或 0 表示两个字节和一个字节 。

image-20201027103741717

image-20201027104824617

标准输入 、 输出流

System.out.println("请输入信息,退出输入 e 或 exit):");
//把标准输入流(键盘输入)这个字节流包装成字符流,再包装成缓冲流
BufferedReader br = new BufferedReader( new InputStreamReader(System.in));
String s = null;
try{
    while ((s = br.readLine()) != null ) { // 读取用户输入的一行数据 ----> 阻塞程序
        if(" e".equalsIngoreCase(s) ) || " exit".equalsIngoreCase(s)){
            System.out.println("安全退出");
            break;
        }
        //将读取到的整行字符串转成大写输出
        System.out.println("---->:"+s.toUpperCase());
        System.out.println("继续输入信息");
    }
} catch (IOException e )
    e.printStackTrace();
}
finally{
    try{
        if (br != null ){
            br.close (); // 关闭过滤流时 会自动关闭它包装的底层节点流
        }
    }catch (IOException e){
        e.printStackTrace();
    }
}

打印流

实现将基本数据类型的数据格式转化为字符串输出。

打印流: PrintStream 和 PrintWriter

@Test
public void test2() {
    PrintStream ps = null;
    try {
        FileOutputStream fos = new FileOutputStream(new File("D:\\IO\\text.txt")); // 创建打印输出流,设置为自动刷新模式(写入换行符或字节 '\n' 时都会刷新输出缓冲区)
        ps = new PrintStream(fos, true);
        if (ps != null) {// 把标准输出流(控制台输出)改成输出到文件
            System.setOut(ps);
        }

        for (int i = 0; i <= 255; i++) { // 输出ASCII字符
            System.out.print((char) i);
            if (i % 50 == 0) { // 每50个数据一行
                System.out.println(); // 换行
            }
        }

    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } finally {
        if (ps != null) {
            ps.close();
        }
    }
}

数据流

为了方便地操作 Java 语言的基本数据类型和 String 的数据,可以使用数据流。

数据流有两个类:

对象流

ObjectInputStream 和 OjbectOutputSteam:用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把 Java 中的对象写入到数据源中,也能把对象从数据源中还原回来。

凡是实现 Serializable 接口的类都有一个表示序列化版本标识符的静态变量:

image-20201027144110541

谈谈你对java.io.Serializable 接口的理解,我们知道它用于序列化,是空方法接口,还有其它认识吗?

实现了 Serializable 接口的对象,可将它们转换成一系列字节,并可在以后完全恢复回原来的样子。 这一过程亦可通过网络进行。这意味着序列化机制能自动补偿操作系统间的差异。 换句话说,可以先在 Windows 机器上创建一个对象,对其序列化,然后通过网络发给一台 Unix 机器,然后在那里准确无误地重新“装配”。不必关心数据在不同机器上如何表示,也不必关心字节的顺序或者其他任何细节。

由于大部分作为参数的类如 String 、 Integer 等都实现了java.io.Serializable 的接口,也可以利用多态的性质,作为参数使接口更灵活。

随机存取文件流

构造器

创建 RandomAccessFile 类实例需要指定一个 mode 参数,该参数指
定 RandomAccessFile 的访问模式:

我们可以用RandomAccessFile 这个类,来实现一个 多线程断点下载 的功能,用过下载工具的朋友们都知道,下载前都会建立 两个临时文件 ,一个是与被下载文件大小相同的空文件,另一个是记录文件指针的位置文件,每次暂停的时候,都会保存上一次的指针,然后断点下载的时候,会继续从上一次的地方下载,从而实现断点下载或上传的功能,有兴趣的朋友们可以自己实现下。

NIO.2 中 Path 、 Paths 、Files 类的使用

Java API 中提供了两套 NIO 一套是针对标准输入输出 NIO 另一套就是网络编程 NIO 。

|----java.nio.channels.Channel
    |----FileChannel: 处理本地文件
    |----SocketChannel:TCP 网络编程的客户端的 Channel
    |----ServerSocketChannel:TCP 网络编程的服务器端的 Channel
    |----DatagramChannel:UDP 网络编程中发送端和接收端的 Channel

Path

早期 的 Java 只提供了一个 File 类来访问文件系统,但 File 类的功能比较有限,所提供的方法性能也不高。而且, 大多数方法在出错时仅返回失败,并不会提供异常信息。NIO. 2 为了弥补这种不足,引入了 Path 接口,代表一个平台无关的平台路径,描述了目录结构中文件的位置。 Path 可以看成是 File 类的升级版本,实际引用的资源也可以不存在。

Path 常用方法:

String toString(); 返回调用 Path 对象的字符串表示形式
boolean startsWith(String path); 判断是否以 path 路径开始
boolean endsWith(String path); 判断是否以 path 路径结束
boolean isAbsolute(); 判断是否是绝对路径
Path getParent(); 返回 Path 对象包含整个路径,不包含 Path 对象指定的文件路径
Path getRoot();返回调用 Path 对象的根路径
Path getFileName(); 返回与调用 Path 对象关联的文件名
int getNameCount(); 返回 Path 根目录后面元素的数量
Path getName(int idx); 返回指定索引位置 idx 的路径名称
Path toAbsolutePath(); 作为绝对路径返回调用 Path 对象
Path resolve(Path p); 合并两个路径,返回合并后的路径对应的 Path 对象
File toFile(); 将 Path 转化为 File 类的对象

Path与File类互换

File file = path.toFile();
Path path = file.toPath();

Paths、Files

同时, NIO.2 在 java.nio.file 包下还 提供了 Files 、 Paths 工具类, Files 包含
了大量静态的工具方法来操作文件; Paths 则包含了两个返回 Path 的静态工厂方法。

Paths 类提供的静态 get() 方法用来获取 Path 对象:

static Path get(String first, String … more); // 用于将多个字符串串连成路径
static Path get(URI uri); // 返回指定 uri 对应的 Path 路径

java.nio.file.Files 用于操作文件或目录的工具类。
Files 常用方法:

Path copy(Path src, Path dest, CopyOption … how); 文件的复制
Path createDirectory(Path path, FileAttribute<?> … attr); 创建一个目录
Path createFile(Path path, FileAttribute<?> … arr); 创建一个文件
void delete(Path path); 删除一个文件 目录,如果不存在,执行报错
void deleteIfExists(Path path); Path 对应的文件目录如果存在,执行删除
Path move(Path src, Path dest, CopyOption…how); 将 src 移动到 dest 位置
long size(Path path); 返回 path 指定文件的大小


boolean exists(Path path, LinkOption … opts); 判断文件是否存在
boolean isDirectory(Path path, LinkOption … opts); 判断是否是目录
boolean isRegularFile(Path path, LinkOption … opts); 判断是否是文件
boolean isHidden(Path path); 判断是否是隐藏文件
boolean isReadable(Path path); 判断文件是否可读
boolean isWritable(Path path); 判断文件是否可写
boolean notExists(Path path, LinkOption … opts); 判断文件是否不存在


SeekableByteChannel newByteChannel(Path path, OpenOption…how); 获取与指定文件的连
接, how 指定打开方式。
DirectoryStream<Path> newDirectoryStream(Path path); 打开 path 指定的目录
InputStream newInputStream(Path path, OpenOption…how); 获取 InputStream 对象
OutputStream newOutputStream(Path path, OpenOption…how); 获取 OutputStream 对象