微生成网站建设网站需要什么设施?

当前位置: 首页 > news >正文

微生成网站,建设网站需要什么设施?,清河网站建设设计,云网站建设文章目录 文件操作和IO文件相关概念Java操作文件文件系统操作文件内容操作字节流FileOutputStreamFileInputStream代码演示 字符流FileWriterFileReader代码演示 缓冲流转换流 案例练习 文件操作和IO 文件相关概念 文件 通常指的是包含用户数据的文件#xff0c;如文本文件、… 文章目录 文件操作和IO文件相关概念Java操作文件文件系统操作文件内容操作字节流FileOutputStreamFileInputStream代码演示 字符流FileWriterFileReader代码演示 缓冲流转换流 案例练习 文件操作和IO 文件相关概念 文件 通常指的是包含用户数据的文件如文本文件、图像文件、音频文件等。这些文件有具体的扩展名存储实际的数据内容。 如下图
上面描述的其实是狭义的文件更广义的文件可以指代任何可以通过文件系统接口进行操作的资源“一切皆文件”在此描述下例如设备文件、管道、套接字等都被视为文件。 但是我们这里涉及的讨论主要还是针对狭义的文件。接下来我们会介绍两个后续不断会提到的概念目录 和 路径。 目录 概念目录是文件系统的组织结构用于存储文件和其他目录。目录其实就是文件夹。目录是文件吗 目录被视为文件只不过它的内容是目录项而不是用户数据目录实际上是一个包含目录项的特殊文件。 路径 概念用于描述文件系统中从根目录或当前目录到某个特定文件或目录的地址。路径可以用来区分或识别文件例如某个路径C:\Program Files\Java\jdk-17 分类路径分为绝对路径 和 相对路径 两种类型。 绝对路径从根目录开始到目标文件或目录的完整路径。 一个文件的绝对路径是唯一的在大多数操作系统中绝对路径总是以根目录如Windows中的驱动器字母开始。例如C:\Users\dell\text.txt就是text.txt文件的绝对路径 相对路径相对路径是相对于当前工作目录的路径它不从根目录开始。 准确来说相对路径需要一个基准路径只不过这个基准路径通常是当前工作目录的路径正因为如此相对路径是非唯一的。 举个例子详细解释一下相对路径C:\Users\dell\text.txt中text.txt的相对路径是什么这要取决于基准路径的选择 .表示当前所在的目录位置..表示上一级目录 基准路径是 C:\Users\dell 相对路径text.txt 或者 .\text.txt解释从C:\Users\dell开始text.txt就在当前目录下 基准路径是 C:\Users 相对路径dell\text.txt 或者 .\dell\text.txt解释从 C:\Users 开始需要进入 dell 子目录才能找到 text.txt 基准路径是 C:\ 相对路径Users\dell\text.txt 或者 .\Users\dell\text.txt解释从C:\开始需要依次进入Users、dell才能找到text.txt 基准路径是 C:\Users\dell\subdir 相对路径..\text.txt解释从 C:\Users\dell\subdir 开始需要返回上一级目录 C:\Users\dell 才能找到 text.txt 有关路径分隔符使用/斜杠还是\反斜杠的问题 /是通用的大多数操作系统都将它作为路径分隔符 \是Windows操作系统特有的并且通常是默认的作者笔记本就是Windows操作系统所以上面的介绍都采用了\。Windows。 同时Windows也是支持/。 为什么Windows会默认将\作为默认路径分隔符呢 这其实是一个历史遗留问题Windows的前身是DOSDOS采用\作为路径分隔符所以这个习惯就被保留下来了。 我们平时描述路径时尽量还是使用/斜杠作为路径分隔符因为这是通用的同时在许多现代编程语言中\作为转义字符\才能表示反斜杠这是不方便的。 从开发的角度看文件可以简单分为 文本文件 和 二进制文件。 所有的文件最终都是以二进制形式存储的。在这些文件中有些文件的二进制数据可以按照特定的字符编码标准如ASCII、UTF-8、UTF-16等被解释为字符。当二进制数据恰好符合这些字符编码标准时这些文件被称为文本文件剩下的文件中的二进制数据不能直接被解释为字符或者即使能被解释为字符这些字符也没有意义这些文件就是所谓的二进制文件。 两者具体的对比 文本文件二进制文件可读性人类可读可以直接用记事本查看和编辑人类不可读需要特定的程序或工具来解析文件大小通常较大因为字符编码占用空间通常较小因为直接存储原始数据效率较低需要更多的字节表示相同的信息较高因为减少了编码解码的步骤用途源代码文件如.c、.java、日志文件如.log、配置文件如.ini等编译后的文件如.class、.exe、库文件如.so、数据文件如.bin等 Java操作文件 在介绍文件操作前我们必须先简单理解几个概念及其它们的关系这些概念包括IO、流、 IO 是一个比较广泛的概念指计算机的输入和输出操作。流 是处理数据传输的一种抽象机制分为字节流 和 字符流。流实际上是对IO的进一步抽象。 字节流是用于处理二进制数据的流。字节流每次读取一个字节适用于处理图片、音频等二进制文件。字符集不涉及字符编码转换因此可以处理任何形式的二进制数据。字符流是用于处理字符数据的流。每次读写一个字符通常是Unicode字符适用于处理文本文件。字符流自动处理字符编码的转换使得处理文本数据十分方便。 字节流和字符流并不严格对应二进制文件和文本文件而是根据数据的处理方式区分的。 例如要复制一个文本文件在这个场景下我们并不关心文本文件的内容因此直接使用字节流完成复制任务即可。 文件只是IO的一个应用领域Java中有关文件的操作涉及到两个包java.io和java.nio它们适合不同的应用场景我们要介绍的是java.io中的一些常用组件。 文件系统操作 文件系统操作指的是创建文件、删除文件、移动文件、对文件重命名等不涉及文件内容的操作。File类是java.io包中唯一代表磁盘文件本身的组件它定义了一系列文件系统操作方法。因此这一部分围绕 File 类展开。 构造方法 常见构造方法 构造方法说明File(String pathname)根据文件路径创建一个File实例路径可以是绝对路径也可以是相对路径File(String parent, String child)将父路径字符串和子路径字符串组合起来创建一个File实例File(File parent, String child)将抽象的父路径和子路径字符串组合创建一个File实例 通过上述构造方法只是实例化了一个代表某个文件的File实例并不会创建文件。 实例化File对象时这个文件可以存在也可以不存在 //绝对路径创建File实例 File file1 new File(C:/Demo/test1.txt); //相对路径创建File实例 File file2 new File(./test2.txt);根据相对路径创建的File实例默认选中当前工作目录当前工作目录是由启动程序的方式决定的。 例如如果采用IDEA启动后续通过代码创建的文件会在项目目录中。 常用方法 方法说明String getParent()返回 File 对象的父目录文件路径String getName()返回 File 对象的纯文件名词String getPath()返回 File 对象的文件路径String getAbsolutePath()返回 File 对象的绝对路径String getCanonicalPath()返回 File 对象的修饰过的路径——————————分隔线boolean exists()判断 File 对象描述的文件是否真实存在boolean isDirectory()判断 File 对象代表的文件是否是一个已存在目录boolean isFile()判断 File 对象代表的文件是否是一个已存在的普通文件boolean createNewFile()根据 File 对象创建一个文件。成功创建后返回true如果文件已经存在返回falseboolean delete()根据 File 对象删除文件或空目录。如果目录中包含文件或其他子目录delete() 方法将返回 false表示删除操作失败。void deleteOnExit()根据 File 对象标注文件将被删除删除行为会在JVM运行结束时进行File createTempFile(String prefix, String suffix)根据prefix前缀字符串和suffix后缀字符串创建一个新的空文件该文件在默认临时文件目录中。需要手动删除——————————分隔线String[] list()返回 File 对象代表的目录下的所有文件名如果代表的不是目录返回nullString[] list(FilenameFilter filter)返回指定目录中符合 FilenameFilter 过滤条件的所有文件名。File[] listFiles()返回 File 对象代表的目录下的所有文件的File对象如果代表的不是目录返回nullFile[] listFiles(FilenameFilter filter)返回指定目录中符合 FilenameFilter 过滤条件的所有文件的File对象。boolean mkdir()创建 File 对象代表的目录如果代表的不是目录返回false代表创建失败boolean mkdirs()创建 File 对象代表的目录如果必要会创建中间目录boolean renameTo(File dest)对 File 对象代表的文件重命名dest代表新的文件或目录的 File 对象类似于剪切、粘贴操作boolean canRead()判断用户是否对文件有可读权限boolean canWrite()判断用户是否对文件有可写权限 如表格演示将常用方法分成三部分演示时也会分成三部分。 【第一部分演示】 public class Demo5 {public static void main(String[] args) throws IOException {File file1 new File(C:/Demo/test1.txt);File file2 new File(./test2.txt);System.out.println(fil1的打印演示);System.out.println(file1.getParent());System.out.println(file1.getName());System.out.println(file1.getPath());System.out.println(file1.getAbsolutePath());System.out.println(file1.getCanonicalPath());System.out.println();System.out.println(file2的打印演示);System.out.println(file2.getParent());System.out.println(file2.getName());System.out.println(file2.getPath());System.out.println(file2.getAbsolutePath());System.out.println(file2.getCanonicalPath());} } 通过打印结果可以看出 getParent()返回的父路径形式与构造File对象时的参数有关如file2的返回值是.getName()返回的就是文件名getPath()返回的结果也与构造File对象的参数有关getAbsolutePath()返回绝对路径但仍可能存在符号链接、相对路径以及.、..等getCanonicalPath()返回规范路径消除了符号链接、相对路径、.、..等同时如果文件不存在或路径无效可能会抛出 IOException异常。 【第二部分演示】 public class Demo6 {public static void main(String[] args) throws IOException {File file new File(C:/Demo/test.txt);//判断是否真实存在System.out.println(file.exists());//创建出来file.createNewFile();//判断类型System.out.println(file.isFile());System.out.println(file.isDirectory());//删除并判断是否成功file.delete();System.out.println(file.exists());} }delete()方法只能删除普通文件和空目录如果不是空目录将无法删除。 如果想要删除一个非空目录必须递归逐个删除。 private static void deleteDir(File dir) {if(!dir.isDirectory()) {return;}File[] files dir.listFiles();//空目录if(files null) {dir.delete();}else {for(int i 0; i files.length; i) {if(files[i].isFile()) {files[i].delete();}else {deleteDir(files[i]);}}}//删除自己dir.delete();}deleteOnExit()方法 和 createTempFile(String prefix, String suffix)通常配合使用以确保临时文件在程序退出时自动删除。 public class Demo8 {public static void main(String[] args) throws IOException {//创建临时文件File tempFile File.createTempFile(test, .txt);//查看是否创建成功System.out.println(tempFile.exists());//设置JVM运行结束后删除tempFile.deleteOnExit();//判断JVM结束前deleteOnExit()后是否存在System.out.println(tempFile.exists());} }【第三部分演示】 list()、listFiles()以及它们的重载版本都不会递归地返回子目录的内容只返回当前目录下的文件和子目录的列表。 list()和listFiles()的带参数的重载版本list(FilenameFilter filter)和listFiles(FilenameFilter filter)中的FilenameFilter是一个函数式接口称为文件过滤器。只有一个抽象方法boolean accept(File dir, String name)用来指定文件名的过滤规则符合条件的规则下返回true文件名将被保留不符合过滤条件的规则下返回false的文件将被过滤掉带FilenameFilter文件过滤器类型参数的list()或listFiles()方法最终会返回符合过滤条件的所有文件名或者文件的File对象。 public class Demo9 {public static void main(String[] args) {File file new File(C:/Demo);//使用没有文件过滤器的list方法返回C:/Demo目录下的所有文件名String[] files1 file.list();//使用带有文件过滤器的list方法过滤出名字包含test的文件名String[] files2 file.list((dir, name) - name.contains(test));//打印查看效果System.out.println(Arrays.toString(files1));System.out.println(Arrays.toString(files2));} }前面的删除非空目录的代码中其实已经包含了遍历目录中所有文件的操作就是利用listFiles()拿到所有的文件对象数组然后遍历对象数组如果是目录就递归遍历这个目录整体就是一个递归方法这里不再演示。 public class Demo10 {public static void main(String[] args) {File dir new File(C:/Demo/newSubDemo1);//这个File实例代表的文件的中间目录Demo已经存在File dirs new File(C:/Demo/newSubDemo2/newSubDemo3);//这个File实例代表的文件的中间目录newSubDemo2不存在System.out.println(Demo目录是否存在 dir.getParentFile().exists());System.out.println(newSubDemo2目录是否存在 dirs.getParentFile().exists());System.out.println(newSubDemo1目录是否存在 dir.exists());System.out.println(newSubDemo3目录是否存在 dirs.exists());System.out.println(使用mkdir创建中间目录均存在的目录是否成功 dir.mkdir());System.out.println(使用mkdir创建中间目录不存在的目录是否成功 dirs.mkdir());System.out.println(使用mkdirs创建中间目录不存在的目录是否成功 dirs.mkdirs());} }上述代码验证了mkdir()和mkdirs()方法均能创建目录但mkdir()方法不能创建中间目录不存在的目录而mkdirs()方法在必要时能够创建中间目录。 public class Demo11 {public static void main(String[] args) {File srcFile new File(C:/Demo/demo1.txt);File dstFile new File(C:/Demo/newDemo1.txt);System.out.println(srcFile.exists());System.out.println(dstFile.exists());System.out.println(srcFile.renameTo(dstFile));} }renameTo(File dest)方法的调用者代表的文件 和 参数代表的文件的关系 调用者表示要被重命名的源文件或目录。 参数表示新的文件名或者路径。 如果调用者代表的文件不存在则方法直接返回false 如果参数代表的文件已经存在在Windows系统下不会覆盖已有文件即方法返回false 明确renameTo()方法不只是重命名它有类似于剪切粘贴的功能 文件内容操作 文件内容操作即对文件的内容进行操作包括读写文件。对文件内容的操作是通过流实现的可以通过字节流 或 字符流操作文件内容同时使用缓冲流提高读写效率还可以使用转换流实现字节流和字符流的转换。因此我们将讨论java.io中与这四种流相关的组件。 在开始介绍前我们得先理解什么是输入什么是输出输入和输出是站在CPU的角度考虑的而不是站在我们的角度因此 读文件是输入写文件是输出 字节流 InputStream 和 OutputStream 类是 java.io 包中与字节流相关的所有组件的基类。这两个类是抽象类提供了处理字节流的基本方法和框架。与文件相关的字节流类主要是 FileInputStream 和 FileOutputStream。这两个类分别用于从文件中读取字节和将字节写入文件。 为了方便演示我们会在介绍完FileInputStream和FileOutputStream后一起演示。 FileOutputStream 常用构造方法 构造方法说明FileOutputStream(String name)根据字符串构造默认的覆盖写入模式FileOutputStream(String name, boolean append)根据字符串构造可以指定模式true追加模式false覆盖写入模式FileOutputStream(File file)根据File对象构造默认的覆盖写入模式FileOutputStream(File file, boolean append)根据File对象构造可以指定模式true追加模式false覆盖写入模式 如果用于构造FileOutputStream的字符串或File对象所指向的文件不存在那么在第一次尝试写入时Java 会自动创建这个文件。如果文件已经存在并且是覆盖写入模式那么文件中的原有内容将被删除新的内容将从头开始写入如果是追加模式那么新写入的数据将会被添加到文件的末尾原有的内容将被保留。如果试图使用 FileOutputStream 写入一个目录而不是一个文件Java 将抛出一个 FileNotFoundException 异常。 常用方法 方法说明void write(int b)写入最低的8位即最低的一个字节void write(byte b[])将 b 字节数组中的数据全部写入void write(byte b[], int off, intlen)将 b 字节数组中从 off 开始的 len 个数据写入void close()关闭字节流void flush()立即刷新输出流将缓冲区中的数据立即写入 完成文件操作后一定要调用close()方法释放资源否则可能会出现文件资源泄露的问题。 实际情况中很少使用flush()方法flush()方法一般在close()时自动调用。 FileOutputStream类包括后面的FileInputStream等类代表字节流但为什么write(int b)需要一个int类型的参数 表示范围和返回值 采用int类型可以确保方法能够处理更大的范围尽管write(int b)只会写入最低位的一个字节。同时某些情况下write方法可能需要返回值来表示写入操作的结果尽管实际上write方法没有返回值但这种设计为未来的扩展留下了空间 历史遗留 Java的I/O流设计借鉴了C语言的I/O库。C语言习惯使用int类型。 write(byte b[])比write(int b)更常用因为前者的字节数组一次可以写入多个字节减少了系统调用次数提高了写入效率字节数组作为缓存批量写入数据减少了文件I/O次数提高了I/O操作的性能处理更复杂的数据例如从网络接收的数据通常都是以字节数组的方式存在的。 浅谈文件资源泄露问题 每个进程都会有一个PCB来描述进程中的某些属性其中就包含文件描述符表。每当打开一个文件都会申请一个表项如果我们打开了大量的文件但是不关闭释放资源时文件描述符表就会爆满进而发生错误即出现了严重的文件资源泄露问题。 tip 文件描述符表不会自动扩容。这是因为 操作系统有资源限制不允许单个进程申请过多的资源。文件描述符表是操作系统内核的一部分内核空间的内存管理十分严格不允许随意扩展。另外如果允许一个文件描述符表过大那么整个表的查找和管理性能就会大打折扣。 FileInputStream 常用构造方法 构造方法说明FileInputStream(String name)根据文件路径构造文件输入流FileInputStream(File file)根据 File 对象构造文件输入流 如果所指向的文件不存在 或者 文件存在但是一个目录Java将会抛出FileNotFoundException异常 常用方法 方法说明int read()从输入流中读取一个字节的数据返回 -1 表示读完int read(byte b[])从输入流中最多读取 b.length 个字节的数据到字节数组b中返回实际读取的数量 -1 代表读完int read(byte b[], int off, int len)从输入流中读取最多 len 个字节的数据读到的数据存放到字符数组b中从off位置开始存方法返回实际读取的数量-1 代表读完void close()关闭字节流 有参数的read方法读到的数据都存放在参数byte b[]字符数组中即b数组既作为参数又用于“返回值”这种参数我们称之为 输出型参数。之所以可以这么做是因为数组类型实际上是一个引用类型。 可以这么理解输出型参数字符数组想象成一个饭盒read方法想象成餐厅我们将饭盒给餐厅打饭阿姨餐厅就会还给我们一个盛满饭的饭盒。 一次读取多个字节的read方法会更常用原因是字节数组可以作为缓存数组同上介绍。 读操作执行完毕后及时close() 代码演示 Java 7引入了 try-with-resources 语法旨在简化资源管理特别是对于那些必须显式关闭以防止资源泄漏的对象如我们接下来要讲的文件输入输出流。 try-with-resources 语法特性 使用方法try后的括号内实例化资源对象仍可以使用catch捕获异常以及finally语句。同时try括号内可以声明多个资源每个资源之间用分号隔开。 try (ResourceType resource new ResourceType()) {// 使用资源的代码 } catch (ExceptionType1 e1) {// 处理异常 } finally {// 可选的finally块 }使用条件 资源对象必须在try括号内初始化创建必须实现 AutoCloseable 接口意味着包括所有实现了 Closeable 接口的类 使用优势自动关闭资源而不需要手动调用close方法资源对象会在try代码块结束时自动关闭不论是否发生异常。这减少了由于关闭资源而造成的潜在错误或内存泄漏风险使得程序员可以更专注于业务逻辑。 public class Demo14 {public static void main(String[] args) throws FileNotFoundException {//写文件try(FileOutputStream outputStream new FileOutputStream(D:/DemoFile/INNER/demo.txt)) {outputStream.write(97);outputStream.write(98);outputStream.write(B);outputStream.write(new byte[]{a, b, c});outputStream.write(难);} catch (IOException e) {e.printStackTrace();}//读文件try(FileInputStream inputStream new FileInputStream(D:/DemoFile/INNER/demo.txt)) {while(true) {byte[] buf new byte[1024];int r inputStream.read(buf);if(r -1) {break;}for(int i 0; i r; i) {System.out.println(buf[i]);}}} catch (IOException e) {e.printStackTrace();}} }这段代码使用 try-with-resources 语法同时演示了FileOutputStream和FileInputStream以覆盖写入的方式尝试写入97、98、‘B’、‘a’、‘b’、‘c’、‘难’读文件并打印结果如图。 前6行打印分别对应97、98、‘B’、‘a’、‘b’、‘c’而最后一行打印的结果是-66对应尝试写入’难’具体原因是 汉字字符通常占2~4个字节而write(int b)方法实际只会写入最低的8位一个字节这就导致汉字字符的数据写入不完整只写入了最低的一个字节因此打印时只打印出一个字节的内容-66就是汉字的最低8位的内容这块内容是没有任何实际意义的。 列举一种正确写入汉字字符的方法 public class Demo15 {public static void main(String[] args) {//向文件写入汉字字符try(FileOutputStream fileOutputStream new FileOutputStream(D:/DemoFile/INNER/demo.txt)) {String test 你好;byte[] data test.getBytes();fileOutputStream.write(data);} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}//读文件并以十六进制打印try(FileInputStream fileInputStream new FileInputStream(D:/DemoFile/INNER/demo.txt)) {while(true) {byte[] buf new byte[1024];int r fileInputStream.read(buf);if(r -1) {break;}for(int i 0; i r; i) {System.out.printf(%x\n, buf[i]);}}} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}} }针对汉字或包含汉字字符串使用getBytes()得到对应的字节数组再利用write(byte b[])方法写入文件此时就不会发生数据写入不完整的问题了打印时采用十六进制的方式打印结果如上图e4、bd、a0 代表 ‘你’e5、a5、bd 代表 ‘好’这里采用了UTF-8的编码方式我们可以从编码转换网站中验证一下 字符流 前面提到采用字节流的方式省去了编码解码的过程性能相对较快但是我们发现如果以字节流的方式读取文件得到的结果是不可读的需要手动查询如果想要程序返回可读结果我们还需要手动编写解码程序解码工作是相对复杂且难以理解的 并降低了开发效率。因此实际情况下我们更常用字符流的方式读文件解码和编码的转换工作会由底层封装的逻辑自动进行。 接下来我们开始介绍字符流相关APIWriter 和 Reader 类是 java.io 包中与字符流相关的所有组件的基类。这两个类是抽象类提供了处理字符流的基本方法和框架。与文件相关的字符流类主要是 FileReader 和 FileWriter。这两个类用于以字符为单位进行文件读写操作。 FileWriter 常用构造方法 构造方法说明FileWriter(String fileName)根据字符串构造默认的覆盖写入模式FileWriter(String fileName, boolean append)根据字符串构造可以指定模式true追加模式false覆盖写入模式FileWriter(File file)根据File对象构造默认的覆盖写入模式FileWriter(File file, boolean append)根据File对象构造可以指定模式true追加模式false覆盖写入模式 注意问题与FileOutputStream类相同FileWriter 内部使用了 FileOutputStream自动创建、追加模式和覆盖写入模式、异常的抛出。 常用方法 方法说明void write(int c)接受一个 int 类型将其转换为字符后写入文件void write(String str)将整个字符串写入文件void write(char[] cbuf)将字符数组中的所有元素写入文件void write(String str, int off, int len)从 off 位置开始将字符串 str 写入文件共写入 len 个字符void write(char[] cbuf, int off, int len)从 off 位置开始将字符数组写入文件共写入 len 个元素字符void close()关闭字符流void flush()立即刷新输出流将缓冲区中的数据立即写入 同样的注意问题 及时close释放资源避免文件资源泄露通常使用一次性写入多个字符的write方法原因就在于减少系统调用次数并有缓存作用 FileReader 常用构造方法 构造方法说明FileReader(String fileName)根据文件路径构造文件输入流FileReader(File file)根据 File 对象构造文件输入流 常用方法 方法说明int read()从输入流中读取一个字符并返回其Unicode编码形式的 int 值如果到达文件末尾则返回 -1int read(char[] cbuf)从输入流中读取字符并尽量填满 cbuf 数组方法返回实际读取的数量到达文件末尾返回 -1int read(CharBuffer target)从输入流中读取字符放进 target 缓冲区中返回实际读取的字符数量到达文件末尾时返回 -1int read(char[] cbuf, int off, int len)从输入流中最多读入 len 个字符读取到的字符从 cbuf 的 off 位置开始放返回实际读取的字符数量到达文件末尾时返回 -1void close()关闭字符流 CharBuffer类相当于对char[]进行了封装可以保存字符数据实际上是一个缓冲区。及时close释放资源 代码演示 public class Demo15 {public static void main(String[] args) throws IOException {//写文件try(FileWriter writer new FileWriter(D:/DemoFile/INNER/newDemo.txt)) {writer.write(你好字符流);writer.write(new char[]{h, e, l, l, o});}//读文件try(FileReader reader new FileReader(D:/DemoFile/INNER/newDemo.txt)) {while(true) {char[] cBuf new char[100];int r reader.read(cBuf);if(r -1) {break;}for(int i 0; i r; i) {System.out.print(cBuf[i]);}}}} }前面按照字节流读取一个汉字是3个字节而现在却“变成”2个字节其实这两种方式都是正确的。 字节流读取的是原始数据即3个字节而字符流在读取的时候会根据文件的内容编码格式进行解析返回时针对3个字节进行了转码用这三个字节查询到其指代的汉字又将汉字的unicode编码值查询出来最终将编码值返回到char变量2个字节中。 缓冲流 缓冲流 是一种用于提高输入输出操作效率的流。缓冲流通过在内存中维护一个缓冲区减少了对底层系统调用的次数从而提高了 I/O 操作的性能。Java 提供了多种缓冲流包括字节缓冲流BufferedInputStream、BufferedOutputStream和字符缓冲流BufferedReader、BufferedWriter。 缓冲流可以显著提高 Java 程序在处理大量数据或频繁进行 I/O 操作时的性能但是对于小文件就没有必要使用缓冲流了因为性能提升效果不明显甚至可能引入额外的开销。 四个缓冲流相关的类上面已经提到分别对应到上面介绍的四个类那具体如何使用呢我们先介绍一下它们的构造方法四个类的构造方法十分相似我们放在一个表格里 构造方法说明BufferedInputStream(InputStream in)创建一个新的字节缓冲输入流BufferedInputStream(InputStream in, int size)创建一个新的字节缓冲输入流并指定缓冲区大小BufferedOutputStream(OutputStream out)创建一个新的字节缓冲输出流BufferedOutputStream(OutputStream out, int size)创建一个新的字节缓冲输出流并指定缓冲区大小BufferedReader(Reader in)创建一个新的字符缓冲输入流BufferedReader(Reader in, int sz)创建一个新的字符缓冲输入流并指定缓冲区大小BufferedWriter(Writer out)创建一个新的字符缓冲输出流BufferedWriter(Writer out, int sz)创建一个新的字符缓冲输出流并指定缓冲区大小 若想要使用缓冲流对文件执行读写操作读写方法不变只是需要套个壳而已 即将FileInputStream、FileOutputStream、FileReader、FileWriter这四个类的对象传入对应的缓冲流构造方法构造出一个缓冲流对象用这个对象调用之前的读或写方法。 不过需要注意的是缓冲流也需要及时关闭释放资源即需要将构造对象放到try括号里。 【具体演示】 public class Demo16 {public static void main(String[] args) {//写文件try(FileOutputStream outputStream new FileOutputStream(D:/DemoFile/INNER/IN/demo1.txt);BufferedOutputStream bufferedOutput new BufferedOutputStream(outputStream)) {for(int i 97; i 122; i) {bufferedOutput.write(i);}} catch (IOException e) {e.printStackTrace();}//读文件try(FileReader reader new FileReader(D:/DemoFile/INNER/IN/demo1.txt);BufferedReader bufferedReader new BufferedReader(reader)) {while(true) {char[] cBuf new char[100];int r bufferedReader.read(cBuf);if(r -1) {break;}for(int i 0; i r; i) {System.out.print(cBuf[i]);}}} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}} }代码中使用了缓冲流的方式读写文件但是后续的案例练习不会使用缓冲流这是因为我们不涉及大文件如果使用也就直接套个壳即可。 转换流 转换流InputStreamReader 和 OutputStreamWriter在 Java 中用于在字节流和字符流之间进行转换。它们特别适用于处理涉及字符编码的输入输出操作。 转换流的使用场景 文件操作在处理文本文件时特别是需要指定字符编码的场景下使用转换流是常见的做法。 举例解释当文件编码与平台默认编码不一致此时采用FileReader读取文件会出现乱码。 网络通信在网络通信中为了确保数据的正确性和一致性使用转换流是必要的。 多语言和国际化在处理多语言和国际化数据时使用转换流是必不可少的。 如果平台默认编码与文件编码不一致FileOutputStream、FileWriter、FileInputStream、FileReader都会出现乱码情况。 FileWriter使用平台默认的字符编码来写入文件。如果文件需要使用特定的字符编码例如 UTF-8而平台默认编码不同例如 GBK则会导致乱码。FileOutputStream写入字节数据。如果你直接写入字符串而不指定字符编码也会导致乱码。String.getBytes() 方法在没有指定字符编码时会使用平台默认编码。FileReader使用平台默认的字符编码来读取文件。如果文件的实际编码与平台默认编码不同读取时会出现乱码。FileInputStream读取的是字节数据不会直接处理字符编码问题。但如果你直接将字节数据转换为字符串而不指定字符编码也会导致乱码。 基于这样的场景我们就需要转换流来指定需要的编码方式。 根据使用场景不难理解对于我们初学者/学生来说很少用到转换流关于两个类 InputStreamReader将字节输入流转换为字符输入流OutputStreamWriter将字符输出流转换为字节输出流 【构造方法】 构造方法说明InputStreamReader(InputStream in)OutputStreamWriter(OutputStream out)使用平台默认字符编码InputStreamReader(InputStream in, Charset cs)OutputStreamWriter(OutputStream out, Charset cs)使用指定的字符编码InputStreamReader(InputStream in, String charsetName)OutputStreamWriter(OutputStream out, String charsetName)使用指定的字符编码名称 当采用只有一个参数构造方法时效果和直接使用FileReader等类一致都使用平台默认编码。 这里只演示了构造方法具体如何使用与缓冲流类似也是套个壳然后指定一下字符编码即可具体参照接下来的演示代码理解。 【具体演示】 文件读写场景 读取文本文件特别是当文件的编码不是平台默认编码时使用 InputStreamReader 可以确保正确读取文件内容。写入文本文件同样当需要写入特定编码的文件时使用 OutputStreamWriter 可以确保数据的正确性。 // 写文件指定 UTF-8 编码 try (FileOutputStream fos new FileOutputStream(example.txt);OutputStreamWriter osw new OutputStreamWriter(fos, StandardCharsets.UTF_8);BufferedWriter bw new BufferedWriter(osw)) {String content Hello, World!;bw.write(content); } catch (IOException e) {e.printStackTrace(); }// 读文件指定 UTF-8 编码 try (FileInputStream fis new FileInputStream(example.txt);InputStreamReader isr new InputStreamReader(fis, StandardCharsets.UTF_8);BufferedReader br new BufferedReader(isr)) {String line;while ((line br.readLine()) ! null) {System.out.println(line);} } catch (IOException e) {e.printStackTrace(); }代码举例演示了以UTF-8的方式读写文件。 总之当平台默认编码与文件编码不一致时为了避免乱码问题你需要确保在读取和写入文件时使用与文件实际编码相同的编码。使用转换流 案例练习 结束了文件系统操作和文件内容操作后我们实现几个小案例巩固一下。 【案例一】 扫描指定目录并找到名称中包含指定字符的所有普通文件不包含目录并且后续询问用户是否要删除该文件 public class Demo2 {public static void main(String[] args) {Scanner scanner new Scanner(System.in);System.out.println(请输入您要扫描的目录);String scanDir scanner.next();File dir new File(scanDir);if(!dir.isDirectory()) {System.out.println(您输入的不是目录);return;}System.out.println(请输入您要查找的关键字);String keyWord scanner.next();scanFiles(dir, keyWord);}private static void scanFiles(File scanDir, String keyWord) {File[] files scanDir.listFiles();for(int i 0; i files.length; i) {if(files[i].isFile()) {if(files[i].getName().contains(keyWord)) {dealFile(files[i]);}}else {scanFiles(files[i], keyWord);}}}private static void dealFile(File file) {System.out.println(找到文件 file.getAbsolutePath() 是否删除(y/n));Scanner scanner new Scanner(System.in);while(true) {String input scanner.next();if(input.equals(y)) {file.delete();return;}else if(input.equals(n)) {return;}else {System.out.println(输入非法请输入y/n);}}} }与之前递归删除非空目录的思想差不多列出目录下的所有文件遍历如果是普通文件判断是否包含指定字符串如果是目录则递归。 【案例二】 进行普通文件的复制注意是复制而不是剪切粘贴 public class Demo3 {public static void main(String[] args) {Scanner scanner new Scanner(System.in);System.out.println(请输入源文件路径);String srcPath scanner.next();File src new File(srcPath);if(!src.isFile()) {System.out.println(路径错误非文件);return;}System.out.println(请输入目标文件路径);String dstPath scanner.next();File dst new File(dstPath);if(!new File(dst.getParent()).isDirectory()) {System.out.println(目标路径错误);return;}try(InputStream inputStream new FileInputStream(src); OutputStream outputStream new FileOutputStream(dst)) {byte[] buf new byte[1024];while(true) {int n inputStream.read(buf);if(n -1) {break;}outputStream.write(buf, 0, n);}} catch (IOException e) {e.printStackTrace();}} }判断输入合法文件内容操作边读边写 【案例三】 扫描指定目录并找到名称或者内容中包含指定字符的所有普通文件不包含目录 public class Demo4 {public static void main(String[] args) {Scanner scanner new Scanner(System.in);System.out.println(请输入要搜索的路径: );String rootPath scanner.next();File rootFile new File(rootPath);if (!rootFile.isDirectory()) {System.out.println(输入的路径不是目录!);return;}System.out.println(请输入要搜索的关键字: );String keyword scanner.next();scanDir(rootFile, keyword);}private static void scanDir(File rootFile, String keyword) {// 1. 列出当前目录下所有的内容File[] files rootFile.listFiles();if (files null) {// 当前目录为空, 直接返回return;}// 2. 遍历当前目录下所有的文件for (File file : files) {if (file.isFile()) {// 是普通文件dealFile(file, keyword);} else {// 是目录, 递归调用scanDir(file, keyword);}}}private static void dealFile(File file, String keyword) {// 1. 判定文件名是否包含关键字if (file.getName().contains(keyword)) {// 包含关键字, 打印文件名System.out.println(文件名包含关键字: file.getAbsolutePath());return;}// 2. 判定文件内容是否包含. 由于 keyword 是字符串. 就按照字符流的方式来处理.StringBuilder stringBuilder new StringBuilder();try (Reader reader new FileReader(file)) {while (true) {char[] chars new char[1024];int n reader.read(chars);if (n -1) {break;}stringBuilder.append(chars, 0, n);}} catch (IOException e) {e.printStackTrace();}// 3. 判定 stringBuilder 是否包含关键字if (stringBuilder.indexOf(keyword) 0) {// 包含关键字System.out.println(文件内容包含关键字: file.getAbsolutePath());}return;} }注意现在的方案性能较差尽量不要在太复杂的目录下或者大文件下实现 完