Java基础加强重温_09:File、递归、IO流、字节输入流、字节输出流、字符输入流、字符输出流、图片复制案例、【字节、字符、字节数组、字符数组、字符串】

摘要

Java基础加强重温_09:
File类(绝对路径/相对路径、创建文件/文件夹对象、获取/判断/创建/删除/遍历)
递归(递归概念:方法自己调用自己)
IO流(字节流/字符流、输入流/输出流)
字节输入流(InputStream类、FileInputStream类、读取字节/循环读取字节/读取字节数组/有效读取字节转换)
字节输出流(OutStream类、FileInOutStream类、写出字节/写出字节数组/写出指定长度字节数组/写出数据追加/写出换行)
字符输入流(Reader类、FileReader类、读取字符/读取字符数组/有效读取字符转换)
字符输出流(Wrtiter类、FileWriter类、写出字符/写出字符数组/写出字符串/关闭和刷新)
字节、字符、字节数组、字符数组、字符串

一、File类

java.io.File 类是文件和目录路径名的抽象表示,主要用于文件和目录的创建、查找和删除等操作。

绝对路径

以盘符开始的路径,在系统中具有唯一性
比如说: c:/aaa/bbb/a.txt、d:/xxx/yyy/a.txt

相对路径

相对某个位置而已,不具有唯一性
比如:a.txt,默认相对于当前项目的根目录
比如:aaa/a.txt 默认相对于当前项目的根目录

当前项目的根目录

当前项目的根目录即为project目录
在这里插入图片描述

IDEA_IDEA项目-根目录-重命名
https://blog.csdn.net/u010003835/article/details/81705379

如何判断文件路径是相对路径还是绝对路径?
判断是否以盘符开始
如果是则是绝对路径,否则是相对路径:默认相对于当前项目的根目录

1、构造方法

public File(String pathname) 
//根据文件路径字符串创建文件对象。

public File(String parent, String child) 
//根据父路径字符串和子路径字符串创建文件对象(父子路径合并)

public File(File parent, String child) 
//根据父路径文件对象和子路径字符串创建文件对象(对象路径字符串路径合并)
构造方法示例
public class Demo02 {
    public static void main(String[] args) {
        // public File(String pathname) 根据文件路径字符串创建文件对象
        // 使用相对路径
        File f1 = new File("a.png");
        System.out.println(f1);

        // 使用绝对路径
        File f2 = new File("c:/a/b/c.txt");
        System.out.println(f2);

        // public File(String parent,String child)
        // 根据父路径字符串和子路径字符串创建文件对象
        File f3 = new File("d:/aaa/", "a.txt");
        System.out.println(f3);

        /*
        *public File(File parent,String child)
           根据父路径文件对象和子路径字符串创建文件对象
        * */
        File f4 = new File(new File("d:/aaa/"), "a.txt");
        System.out.println(f4);

    }
}

2、File类的静态成员变量(pathSeparator、separator)

File类两个常见的静态成员变量:pathSeparator、separator

 static String pathSeparator  路径分割符
    * 与系统有关的路径分隔符,为了方便,它被表示为一个字符串。
    * 不同的系统有不同的分隔符
    * windows系统是:;  (分号)
    * linux或mac系统是: :  (冒号)

 static String separator  默认名称分隔符
     * 与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串。
     * 不同的系统有不同的分隔符
     * windows系统是:\
     * linux或mac系统是: /
pathSeparator、separator使用示例
public class Demo04 {
    public static void main(String[] args)throws Exception {
        System.out.println(File.pathSeparator);
        System.out.println(File.separator);

        FileReader fr = new FileReader("aaa"+File.separator+"a.txt");
        fr.read();
        fr.close();
    }
}

二、File类常用方法

1、获取功能的方法(获取文件信息)

public String getAbsolutePath()
//获得文件的绝对路径字符串

public String getPath()
//获得创建文件对象时指定的路径字符串

public String getName()
//获得文件或文件夹名字

public long length()
//获得文件的大小,单位:字节
//只能获得文件的大小,不能获得文件夹的大小
//如果是文件夹则返回值是一个不确定的值:垃圾值

API中说明:length(),表示文件的长度。但是File对象表示目录,则返回值未指定。

代码示例
public class Demo031 {
    public static void main(String[] args) {
        // 创建文件对象
        File f = new File("/Users/pkxing/documents/124期");
        // 获得文件的绝对路径字符串
        System.out.println(f.getAbsolutePath());

        // 获得创建文件对象时指定的路径字符串
        System.out.println(f.getPath());

        // 获得文件名
        System.out.println(f.getName());

        // 获得文件大小
        System.out.println(f.length());
    }
}

2、判断功能的方法

boolean exists();
//判断文件或文件夹是否存在
//存在返回true,否则false

boolean isDirectory();
//判断文件对象关联的是否是文件夹
//是则返回true,否则false

boolean isFile();
//判断文件对象关联的是否是文件
//是返回true,否则false

代码示例

public class Demo032 {
    public static void main(String[] args) {
        // 创建文件对象
        File f = new File("/Users/pkxing/documents/xxx");
        // 判断文件是否存在
        System.out.println(f.exists());
        // 判断是否是文件夹
        System.out.println(f.isDirectory());
        // 判断是否是文件
        System.out.println(f.isFile());
    }
}

3、创建功能的方法(创建文件和文件夹)

boolean createNewFile();
    创建文件,只能创建文件不能创建文件夹
    创建成功返回true,失败返回false
    如果文件存在了,则不会再创建,返回false

boolean mkdir(); //mkdir=make directory
    创建单级文件夹,只能创建文件夹,不能创建文件
    创建成功返回true,失败返回false
    如果文件夹存在了,则不会再创建,返回false

boolean mkdirs();
    创建多级文件夹,只能创建文件夹,不能创建文件
    创建成功返回true,失败返回false
    如果文件夹存在了,则不会再创建,返回false

代码示例

public class Demo033 {
    public static void main(String[] args) throws IOException {
        // 创建文件对象
        File f = new File("eee.txt");
        // 创建文件
        // System.out.println(f.createNewFile());

		// 创建单级目录(文件夹)
		File f2= new File("newDir");
		System.out.println("是否存在:"+f2.exists());// false
		System.out.println("是否创建:"+f2.mkdir()); // true
		System.out.println("是否存在:"+f2.exists());// true
		
		// 创建多级目录(文件夹)
		File f4= new File("newDira\\newDirb");
		System.out.println(f4.mkdirs());// true
    }
}

4、删除功能的方法

boolean delete();
    删除文件或文件夹,删除成功返回true,否则false。
    如果是文件夹,只能删除空文件夹(包含文件/文件夹的文件夹不能删除)。

代码示例

public class Demo034 {
    public static void main(String[] args) {
        // 创建文件对象
        File f = new File("/Users/pkxing/documents/aaa.mp4");
        // 删除文件
        System.out.println(f.delete());

		//创建文件夹
		File f2 = new File("/Users/pkxing/documents/newDirc");
		System.out.println("是否存在:"+f2.exists());// false
		System.out.println("是否创建:"+f2.mkdir()); // true
		//删除文件夹
		System.out.println(f2.delete()); // true
    }
}

API中说明:delete方法,如果此File表示目录,则目录必须为空才能删除。

5、遍历功能的方法(目录的遍历)

目录即文件夹

String[] list();  
	用来获得当前文件夹下的所有文件和文件夹的名字,返回字符串数组
	文件名字:电脑显示后缀则包括后缀,不显示则不包括(与人自己看到的一样)

File[] listFiles();  
	用来获得当前文件夹下的所有文件和文件夹的绝对路径,返回文件对象数组(File[]//注意实现:使用上面两个方法时,文件对象关联的必须是文件夹

File类中的list()和listFiles()方法
https://www.jianshu.com/p/9c47e9a9ed84

代码示例1

public class FileFor {
	public static void main(String[] args) {
		File dir = new File("d:\\java_code");
		//获取当前目录下的文件以及文件夹的名称。
		String[] names = dir.list();
		for(String name : names){
			System.out.println(name);
		}

		//获取当前目录下的文件以及文件夹对象
		//只要拿到了文件对象,那么就可以获取更多信息
		File[] files = dir.listFiles();
		//数组不能直接在forEach中使用lambda表达式,需要转换成流才可以使用
		for (File file : files) {
			System.out.println(file);
		}
	}
}

调用listFiles方法的File对象,必须是实际存在的目录,否则返回null,无法进行遍历。

代码示例2

public class Demo034 {
  public static void main(String[] args) {
        long size = 0;
        // 创建文件对象
        File f = new File("/Users/pkxing/documents/aaaa");
        if (f.isDirectory()){
            // 用来获得当前文件夹下的所有文件
            File[] files = f.listFiles();
            
            // 增强for本质迭代器 files.iterator();
            for (File file : files) {
                // System.out.println(file.getName());
                if (file.isFile()){
                    size += file.length();
                }
            }
        }
        System.out.println(size);
    }
}

Java中forEach使用lambda表达式,数组和集合区别
https://blog.csdn.net/u010938610/article/details/82699321

三、递归

递归:指在当前方法内调用自己的这种现象。

public static void main(String[] args) {
	System.out.println("main");
	main(args);
}

递归的注意事项
1、递归必须有出口:结束递归的条件
2、递归次数不能太多

递归次数太多方法入栈消耗栈内存超过栈内存最大容量 或者 没有结束条件会一直执行递归导致异常,Exception in thread “main” java.lang.StackOverflowError:栈内存溢出

递归分类

直接递归

直接递归:方法A调用方法A (重点)

public class Demo051 {
    private static int index = 0;
    public static void main(String[] args) {
        System.out.println("main..." + index++); // 0
        testA();
        System.out.println("main..." + index++); // 7
    }

    public static void testA() {
        if (index >= 4) return;
        System.out.println("testA..." + index++); // 1
        testA();
        System.out.println("testA..." + index++); // 6
    }
}

间接递归

间接递归:方法A调用方法B,方法B调用方法C,方法C调用方法A (了解)

四、递归案例

1、递归累和

需求

使用递归求1到5的和

分析

使用递归实现功能的步骤:

  1. 定义方法:确定返回值和参数列表
  2. 找递归的规律
  3. 找递归的出口

分析查找递归规律

n = 5;
    sum(5) = 1 + 2 + 3 + 4 + 5 = sum(4) + 5 = 15
n = 4;
    sum(4) = 1 + 2 + 3 + 4 = sum(3) + 4 = 10
n = 3;
    sum(3) = 1 + 2 + 3 = sum(2) + 3 =  6
n = 2;
    sum(2) = 1 + 2 = sum(1) + 2 = 3
n = 1;
    sum(1) = 1

当n>1时:sum(n) = sum(n-1) + n;
当n=1时:sum(1) = 1;

代码实现

public class Demo061 {

    public static void main(String[] args) {
        System.out.println(sum(5));
    }
    //  1. 定义方法:确定返回值和参数列表
    public static int sum(int n){
        if (n == 1) return 1;
        return sum(n-1) + n;
    }
}

代码执行图解

在这里插入图片描述
递归一定要有条件限定,保证递归能够停止下来,次数不要太多,否则会发生栈内存溢出。

2、递归求阶乘

需求:

使用递归求5!

分析

阶乘:所有小于及等于该数的正整数的积。

n的阶乘:n! = n * (n-1) *...* 3 * 2 * 1 

推理得出:n! = n * (n-1)! 

与累和类似,不过换成了了乘法运算,需要注意阶乘值符合int类型的范围。

代码实现

public class DiGuiDemo {
	//计算n的阶乘,使用递归完成
	public static void main(String[] args) {
		int n = 3;
		// 调用求阶乘的方法
		int value = getValue(n);
		// 输出结果
		System.out.println("阶乘为:"+ value);
	}

	/*
	通过递归算法实现.
	参数列表:int
	返回值类型: int
	*/
	public static int getValue(int n) {
		// 1的阶乘为1
		if (n == 1) {
			return 1;
		}

		/*
		n不为1时,方法返回 n! = n*(n-1)!
		递归调用getValue⽅方法
		*/
		return n * getValue(n - 1);
	}
}

3、文件搜索(递归结合File类)

需求

搜索c:\abc目录中的.java文件。

代码实现

public class Demo063 {

    public static void main(String[] args) {
        // 创建文件对象:关联目标文件夹
        File dir = new File("/Users/pkxing/documents/124期");
        printJavaFile(dir);
    }

    //1. 定义方法:确定返回值和参数列表
	//打印指定目录下的所有Java文件名
    public static void printJavaFile(File dir){
        // 1. 获得当前目录下所有文件:文件数组
        File[] files = dir.listFiles();
        
        // 2. 遍历文件数组:获得每个文件对象
        for (File file : files) {
            // 3. 判断是否是文件夹
            if (file.isDirectory()){
                // 如果是则递归调用当前方法
                printJavaFile(file);
            } else {
                // 4. 判断是否是java文件,是则输出文件名
                if (file.getName().endsWith(".java")){
                    System.out.println(file.getName());
                }
            }
        }
    }
}

五、IO概述

1、什么是IO

生活中,你肯定经历过这样的场景。当你编辑一个文本文件,忘记了 ctrl+s ,可能文件就白白编辑了。当你电脑上插入一个U盘,可以把一个视频,拷贝到你的电脑硬盘里。那么数据都是在哪些设备上的呢?键盘、内存、硬盘、外接设备等等。
我们把这种数据的传输,可以看做是一种数据的流动,按照流动的方向,以内存为基准,分为 输入input 和 输出output ,即流向内存是输入流,流出内存是输出流。
Java中I/O操作主要是指使用 java.io 包下的内容,进行输入、输出操作。输入叫做读取数据,输出叫做作写出数据。

Input:输入:数据从文件到内存中
Output:输出:数据从内存到文件中

为什么要使用IO?

目前数据使用集合和数组,集合或数组保存在内存中的,内存是临时性存储,一旦电脑断电或程序重新启动,数据就没有了。
如果需要保证数据永久存储,就需要将数据存储到硬盘的文件中。通过IO流我们就可以将数据保存到文件中,也可以从文件中读取数据到程序中。

IO流的作用:

保存数据和读取数据,实现数据持久化(即保存数据到本地硬盘)
保存数据:将内存中的数据保存硬盘中文件
读取数据:将文件中的数据读取到内存中

2、IO的分类

根据数据的流向分为:输入流和输出流。

输入流 :把数据从 其他设备 上读取到 内存 中的流。
输出流 :把数据从 内存 中写出到 其他设备 上的流。

根据数据的类型分为:字节流和字符流。

字节流 :以字节为单位,读写数据的流。
字符流 :以字符为单位,读写数据的流。

根据数据类型和流向分类

字节输入流:以字节为单位读取数据 FileInputStream
字节输出流:以字节为单位输出数据 FileOutputStream

字符输入流:以字符为单位读取数据 FileReader
字符输出流:以字符为单位输出数据 FileWriter
在这里插入图片描述

如何判断流是字符流还是字节流?

根据类名判断,如果是Stream结尾的则都是字节流,其他都是字符流

3、IO的流向说明图解

在这里插入图片描述

数据的本质

数据的本质是二进制数字,就是0和1
计算机小常识
计算机只能识别0和1
所有的数据在计算机都是以0和1存储
所有数据在传输过程中都是0和1

编程语言分类
机器语言 通过0和1进行编程
汇编语言 mov a,b
高级语言 c/c++/java/php/…

编码和解码概述
编码:将文本转换为二进制数据的过程
解码:将二进制数据转换为文本的过程

计算机中数据的本质
https://www.cnblogs.com/delsav/p/9236509.html

4、IO流的顶级父类

在这里插入图片描述

六、字节流

一切文件数据(文本、图片、视频等)在存储时,都是以二进制数字的形式保存,都是一个一个的字节,传输时也是如此。所以,字节流可以传输任意文件数据。在操作流的时候,我们要时刻明确,无论使用么样的流对象,底层传输的始终为二进制数据。

1、字节输出流超类【OutputStream】

java.io.OutputStream 抽象类是所有字节输出流的超类(即父类),将指定的字节信息写出到目的地,以字节为单位输出数据。它定义了字节输出流的基本共性功能方法。

因为是一个抽象类:不能直接创建该类对象,只能创建子类对象

常用方法

public void write(byte[] b)
    输出字节数组的内容到目标文件中:输出字节数组的所有内容

public void write(byte[] b, int off, int len)
    输出字节数组的部分内容到目标文件中
    off:字节数组的起始索引
    len:要输出的字节个数

public abstract void write(int b)
    输出一个字节

public void close() 
 	关闭流释放资源

public void flush() 
	刷新缓冲区的数据强行输出

1、close方法,当完成流的操作时,必须调用此⽅方法,释放系统资源。
2、flush(),是把缓冲区的数据强行输出, 主要用在IO中,即清空缓冲区数据,一般在读写流(stream)的时候,数据是先被读到了内存中,再把数据写到文件中,当你数据读完的时候不代表你的数据已经写完了,因为还有一部分有可能会留在内存这个缓冲区中。这时候如果你调用了close()方法关闭了读写流,那么这部分数据就会丢失,所以应该在关闭读写流之前先flush()。
输出流flush()用法:https://blog.csdn.net/iteye_10289/article/details/82450523

OutputStream类常用子类

1、FileOutputStream(今天的重点)
2、BufferedOutputStream
3、ObjectOutputStream
4、PrintStream
在这里插入图片描述
全面整理Java IO流:https://www.it610.com/article/1659232.htm

2、文件输出流(FileOutputStream类)

java.io.FileOutputStream 类是文件输出流,用于将数据写出到文件。

构造方法

public FileOutputStream(File file) 
public FileOutputStream(String name) 
	* 根据文件路径字符串或文件对象创建字节输出流对象
	* 如果目标文件不存在,则会自动创建

public FileOutputStream(File file, boolean append) 
public FileOutputStream(String name, boolean append) 
	* 根据文件路径或文件对象创建字节输出流
	* append 为 true  表示追加输出数据
	* append 为 false  表示不追加输出数据,会先清空文件内容

代码示例

public class FileOutputStreamConstructor throws IOException {
	public static void main(String[] args) {
		// 使用File对象创建流对象
		File file = new File("a.txt");
		FileOutputStream fos = new FileOutputStream(file);
		
		// 使用文件名称创建流对象
		FileOutputStream fos = new FileOutputStream("b.txt");
	}
}

当你创建一个流对象时,必须传入一个文件路径。该路径下,如果没有这个文件,会创建该文件。如果有这个文件,会清空这个文件的数据。

3、文件输出流代码示例

写出字节数据

write(int b) 方法,每次可以写出一个字节数据

public class Demo102 {
    public static void main(String[] args) throws Exception{
        // 1. 创建字节输出流对象并关联目标文件
        OutputStream fos = new FileOutputStream("a.txt");
        
        // 2. 调用字节输出流对象的write方法输出数据
        // 97 ==> 编码 ==> 010101011
        // 010101011 ==> 解码 ==> 97 ==> ASCII表 ===> a
        fos.write(97);
        
        // 300 ==> 00000000 00000000 00000001 00101100
        // 00101100
        fos.write(300);
        fos.write(44);

        // 3. 调用字节输出流对象的close方法关闭流释放资源
        fos.close();
    }
}
  1. 虽然write(int b) 参数为int类型四个字节,但是只会保留一个字节的信息写出。
  2. 流操作完毕后,必须释放系统资源,调⽤用close⽅方法,千万记得。

写出字节数组

write(byte[] b) ,每次可以写出数组中的数据,代码使用演示:

public class Demo103 {
    public static void main(String[] args) throws Exception {
        // 1. 创建字节输出流对象并关联目标文件
        FileOutputStream fos = new FileOutputStream("b.txt");
        
        // 2.1 要输出的数据
        String str = "你好";
        
        // 2.2 将字符串转换为字节数组
        byte[] buf = str.getBytes();
        
        // 2.3 调用write方法输出数据
        fos.write(buf);

        // 输出字节数组的部分内容
        byte[] b = {97,98,99,100,101}; // abcde
        fos.write(b, 1, 3); // bcd

        // 3. 调用close方法关闭流释放资源
        // fos.close();
    }
}

写出指定长度字节数组

write(byte[] b, int off, int len) ,每次写出从off索引开始,到第len个索引的字节

public class FOSWrite {
	public static void main(String[] args) throws IOException {
		// 使用文件名称创建流对象
		FileOutputStream fos = new FileOutputStream("fos.txt");
		
		// 字符串转换为字节数组
		byte[] b = "abcde".getBytes();
		
		// 写出从索引2开始,2个字节。索引2是c,两个字节,也就是cd。
		fos.write(b,2,2);
		
		// 关闭资源
		fos.close();
	}
}

查看fos.txt输出结果:
cd

数据追加续写

以上的演示,每次程序运行,创建输出流对象,都会清空目标文件中的数据。
使用追究续写的构造方法创建的输出流对象,就可以指定是否追加续写。

public class FOSWrite {
	public static void main(String[] args) throws IOException {
		// 使用文件名称创建流对象
		FileOutputStream fos = new FileOutputStream("fos.txt",true);
		
		// 字符串转换为字节数组
		byte[] b = "abcde".getBytes();
		
		// 写出从索引2开始,2个字节。索引2是c,两个字节,也就是cd。
		fos.write(b);
		
		// 关闭资源
		fos.close();
	}
}

文件操作前:cd
文件操作后:cdabcde

写出换行

Windows系统里,换行符号是 \r\n。在内容后面输出一个换行符就可以实现换行输出
\r 回车符
\n 换行符
\r\n 换行符(推荐使用)

public class FOSWrite {
	public static void main(String[] args) throws IOException {
		// 使用文件名称创建流对象
		FileOutputStream fos = new FileOutputStream("fos.txt");
		
		// 定义字节数组
		byte[] words = {97,98,99,100,101};
		
		// 遍历数组
		for (int i = 0; i < words.length; i++) {
			// 写出一个字节
			fos.write(words[i]);
			// 写出一个换行, 换行符号转成数组写出
			fos.write("\r\n".getBytes());
		}
		// 关闭资源
		fos.close();
	}
}
输出结果:
a
b
c
d
e

回车符 \r 和换行符 \n :

  • 回车符:回到一行的开头(return)。
  • 换行符:下一行(newline)。

不同系统中的换行:

  • Windows系统里,每行结尾是 回车+换行 ,即 \r\n ;
  • Unix系统里,每行结尾只有 换行 ,即 \n ;
  • Mac系统里,每行结尾是 回车 ,即 \r 。从 Mac OS X开始与Linux统一。

小结

注意事项小结

  1. 流关联的目标文件必须是普通文件,不能是文件夹
  2. 流一旦关闭之后就不能再读写数据了

4、字节输入流超类【InputStream】

java.io.InputStream 抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。它定义了字节输入流的基本共性功能方法。

字节输入流:以字节为单位读取文件数据
是一个抽象类:不能直接创建对象,只能创建子类对象
是所有字节输入流的父类

常用方法

void close(); 关闭流释放资源

int	read()
    从流关联的目标文件中读取1个字节
    返回读取到的字节数
    如果读取到文件末尾,返回值-1

int	read(byte[] b) ==> 重点
    从流关联的目标文件中读取数据到指定的字节数组中
    返回实际读取到的字节个数
    如果读取到文件末尾,返回值-1

int	read(byte[] b, int off, int len)  ==> 了解
    从流关联的目标文件中读取数据到指定的字节数组中
    off:字节数组的起始索引
    len:存储字节的长度
    如果读取到文件末尾,返回值-1

close方法,当完成流的操作时,必须调用此方法,释放系统资源。

InputStream类常用子类

FileInputStream(今天重点)
BufferedInputStream
ObjectInputStream
在这里插入图片描述

5、文件输入流(FileInputStream类)

java.io.FileInputStream 类是文件输入流,从文件中读取字节。

继承InputStream
本质是字节输入流,以字节为单位读取数据

构造方法

FileInputStream(String path)
FileInputStream(File file)
	根据文件路径字符串或文件对象创建字节输入流对象

当你创建一个流对象时,必须传入一个文件路径。该路路径下,如果没有该文件,会抛出FileNotFoundException 。

代码示例

public class FileInputStreamConstructor throws IOException{
	public static void main(String[] args) {
		// 使用File对象创建流对象
		File file = new File("a.txt");
		FileInputStream fos = new FileInputStream(file);
		
		// 使用文件名称创建流对象
		FileInputStream fos = new FileInputStream("b.txt");
	}
}

6、字节输入流代码示例

读取单个字节数据

read 方法,每次可以读取一个字节的数据,提升为int类型,读取到文件末尾,返回 -1

public class FISRead {
	public static void main(String[] args) throws IOException{
		// 使用文件名称创建流对象
		FileInputStream fis = new FileInputStream("read.txt");
		//读取数据,返回一个字节
		int read = fis.read();
		System.out.println((char) read);
		read = fis.read();
		System.out.println((char) read);
		read = fis.read();
		System.out.println((char) read);
		read = fis.read();
		System.out.println((char) read);
		read = fis.read();
		System.out.println((char) read);
		//读取到末尾,返回-1
		read = fis.read();
		System.out.println( read);
		// 关闭资源
		fis.close();
	}
}

输出结果:
a
b
c
d
e
-1

循环改进读取单个数据

public class FISRead {
	public static void main(String[] args) throws IOException{
		//使用文件名称创建流对象
		FileInputStream fis = new FileInputStream("read.txt");
		//定义变量,保存数据
		int b ;
		
		// 循环读取
		while ((b = fis.read())!=-1) {
			System.out.println((char)b);
		}
		// 关闭资源
		fis.close();
	}
}

输出结果:
a
b
c
d
e
  1. 虽然读取了一个字节,但是会自动提升为int类型。
  2. 流操作完毕后,必须释放系统资源,调用close方法,千万记得。

使用字节数组读取示例1

read(byte[] b) ,每次读取b长度个数的字节到数组中,返回读取到的有效字节个数,读取到末尾时,返回 -1

public class FISRead {
	public static void main(String[] args) throws IOException{
		//使用文件名称创建流对象.
		FileInputStream fis = new FileInputStream("read.txt"); // 文件中为abcde
		
		// 定义字节数组,作为装字节数据的容器
		byte[] b = new byte[2];
		
		// 循环读取
		while (fis.read(b) != -1) {
			// 每次读取后,把数组变成字符串打印
			System.out.println(new String(b));
		}
		
		// 关闭资源
		fis.close();
	}
}

输出结果:
ab
cd
ed

ed 错误数据 d ,是由于最后一次读取时,只读取一个字节 e ,数组中上次读取的数据没有被完全替换。所以要通过 len 获取有效读取的字节,限定每次读取2个,但最后ed只读取了1个,所以有效字节是1个。读取多少个就转换多少个。
ed 只读取了e 1个,有效读取字节为1,但d还存在数组中。所以最后ed只转换1个字节就只转换出e,不会转换出d

有效字节改进字节数组读取

public class FISRead {
	public static void main(String[] args) throws IOException{
		// 使用文件名称创建流对象.
		FileInputStream fis = new FileInputStream("read.txt"); // ⽂文件中为abcde
		
		// 定义变:接收实际读取到的字节个数
		int len ;
		
		// 定义字节数组,作为装字节数据的容器
		byte[] b = new byte[2];
		
		// 循环读取
		while (( len= fis.read(b))!=-1) {
			// 每次读取后,把数组的有效字节部分,变成字符串打印
			System.out.println(new String(b,0,len));// len 每次读取的有效字节个数
		}
		
		// 关闭资源
		fis.close();
	}
}

输出结果:
ab
cd
e

使用数组读取,每次读取多个字节,减少了系统间的IO操作次数,从而提高了读写的效率,建议开发中使用。

使用字节数组读取示例2

采用1024的字节数组

public class Demo113 {
    public static void main(String[] args) throws Exception {
        // 1. 创建流关联目标文件: abcde
        FileInputStream fis = new FileInputStream("a.txt");
        // 2. 创建字节数组:用来存储读取到的数据
        byte[] buf = new byte[1024];
        // 定义变量变量:接收实际读取到的字节个数
        int len = -1;
        // 循环读取数据
        while((len = fis.read(buf)) != -1){ // 5
            // 读取多少给字节就转换多少字节为字符串  ab cd  e
            System.out.print(new String(buf,0,len));
        }
        // 4. 调用close方法关闭流释放资源
        fis.close();
    }


    // 没有循环优化的
    public static void test01() throws Exception {
        // 1. 创建流关联目标文件: abcde
        FileInputStream fis = new FileInputStream("a.txt");
        // 2. 创建字节数组:用来存储读取到的数据
        byte[] buf = new byte[2];
        // 3. 调用read方法读取数据:传递字节数组
        int len = fis.read(buf);
        System.out.println("len = " + len); // 2
        System.out.println("buf = " + new String(buf)); // ab

        len = fis.read(buf);
        System.out.println("len = " + len); // 2
        System.out.println("buf = " + new String(buf)); // cd

        len = fis.read(buf);
        System.out.println("len = " + len); // 1
        System.out.println("buf = " + new String(buf)); // ed

        len = fis.read(buf);
        System.out.println("len = " + len); // -1
        System.out.println("buf = " + new String(buf)); // ed

        // 4. 调用close方法关闭流释放资源
        fis.close();
    }
}

七、字节流练习:图片复制

复制原理图解

在这里插入图片描述

代码实现

public class Demo12 {
    public static void main(String[] args) throws Exception{
        // 创建文件对象:源文件
        File srcFile = new File("/Users/pkxing/documents/aaa.mp4");
        // 创建文件对象:目标文件
        File destFile = new File("/Users/pkxing/documents/bbb.mp4");
        copyFile(srcFile,destFile);
    }

    /**
     * 定义文件复制方法
     * @param srcFile 源文件
     * @param destFile 目标文件
     */
    public static void copyFile(File srcFile, File destFile) throws Exception{
        // 创建字节输入流关联源文件
        FileInputStream fis = new FileInputStream(srcFile);
        // 创建字节输出流关联目标文件
        FileOutputStream fos = new FileOutputStream(destFile);

        // 创建字节数组:用来存储读取到文件数据
        byte[] buf = new byte[1024];
        // 定义整形变量:接收实际读取到的字节个数
        int len = -1;
        // 循环读取源文件数据
        while ((len = fis.read(buf)) != -1){
            // 利用字节输出流数据到目标文件中
            fos.write(buf,0,len);
        }
        // 关闭流释放资源
        fis.close();
        fos.close();
    }
}

八、字符流

当使用字节流读取文本文件时,可能会有一个小问题。就是遇到中文字符时,可能不会显示完整的字符,那是因为一个中⽂文字符可能占用多个字节存储。所以Java提供一些字符流类,以字符为单位读写数据,专门用于处理文本文件。

1、字符输入流超类【Reader类】

java.io.Reader 抽象类是表示用于读取字符流的所有类的超类,可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法。

字符输入流:以字符为单位读取数据
是一个抽象类,只能创建子类对象
是所有字符输入流的父类

常用方法

void close() 
	关闭流释放资源
	
int read()
    从流关联的目标文件中读取一个字符  你 ==> 20320
    如果读取到文件末尾返回-1

int read(char[] cbuf)
    从流关联的目标文件中读取数据到字符数组中
    返回实际读取到的字符个数
    如果读取到文件末尾返回-1

int read(char[] cbuf, int off, int len)
    从流关联的目标文件中读取数据到字符数组中
    返回实际读取到的字符个数
    如果读取到文件末尾返回-1

Reader类常用子类

FileReader(今天重点)
BufferedReader
InputStreamReader

2、文件字符输入流(FileReader类)

java.io.FileReader 类是读取字符文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。

1、字符编码:字节与字符的对应规则。Windows系统的中文编码默认是GBK编码表。idea中是UTF-8
2、字节缓冲区:一个字节数组,用来临时存储字节数据。

构造方法

FileReader(String path)
FileReader(File file)
    根据文件路径字符串或文件对象创建字符输入流

代码示例

public class FileReaderConstructor throws IOException{
	public static void main(String[] args) {
		// 使用File对象创建流对象
		File file = new File("a.txt");
		FileReader fr = new FileReader(file);
		
		// 使用文件名称创建流对象
		FileReader fr = new FileReader("b.txt");
	}
}

3、字符输入流代码示例

读取单个字符数据

读取字符: read 方法
每次可以读取一个字符的数据,提升为int类型,读取到文件末尾,返回 -1 ,循环读取。

public class FRRead {
	public static void main(String[] args) throws IOException {
		// 使用文件名称创建流对象
		FileReader fr = new FileReader("read.txt");
	
		// 定义变量,保存数据
		int b ;
	
		// 循环读取
		while ((b = fr.read())!=-1) {
			System.out.println((char)b);
		}
		// 关闭资源
		fr.close();
	}
}

虽然读取了一个字符(Char),但是会自动提升为int类型。

使用字符数组读取示例1

字符数组读取: read(char[] cbuf)
每次读取b的长度个字符到数组中,返回读取到的有效字符个数,读取到末尾时,返回 -1

public class FileReaderDemo {
    public static void main(String[] args) throws Exception{
        // 使用文件名称创建流对象
        FileReader fr = new FileReader("aaa.txt");
        // 定义字符数组,作为装字符数据的容器
        char[] chs = new char[2];
        // 循环读取
        while (fr.read(chs) != -1){
            System.out.println(new String(chs));
        }
        fr.close();
    }
}

使用字符数组读取示例1 (有效读取字符转换改进)

public class FISRead {
	public static void main(String[] args) throws IOException {
		// 使用文件名称创建流对象
		FileReader fr = new FileReader("read.txt");
		
		// 定义变量,保存有效字符个数
		int len ;
		
		// 定义字符数组,作为装字符数据的容器器
		char[] cbuf = new char[2];
		
		// 循环读取
		while ((len = fr.read(cbuf))!=-1) {
			
			System.out.println(new String(cbuf,0,len));
		}
		
		// 关闭资源
		fr.close();
	}
}

使用字符数组读取示例2

public class Demo113 {
    public static void main(String[] args) throws Exception {
        // 1. 创建流关联目标文件: abcde
        FileInputStream fis = new FileInputStream("a.txt");
        // 2. 创建字节数组:用来存储读取到的数据
        byte[] buf = new byte[1024];
        // 定义变量变量:接收实际读取到的字节个数
        int len = -1;
        // 循环读取数据
        while((len = fis.read(buf)) != -1){ // 5
            // 读取多少给字节就转换多少字节为字符串  ab cd  e
            System.out.print(new String(buf,0,len));
        }
        // 4. 调用close方法关闭流释放资源
        fis.close();
    }


    // 没有循环优化的
    public static void test01() throws Exception {
        // 1. 创建流关联目标文件: abcde
        FileInputStream fis = new FileInputStream("a.txt");
        // 2. 创建字节数组:用来存储读取到的数据
        byte[] buf = new byte[2];
        // 3. 调用read方法读取数据:传递字节数组
        int len = fis.read(buf);
        System.out.println("len = " + len); // 2
        System.out.println("buf = " + new String(buf)); // ab

        len = fis.read(buf);
        System.out.println("len = " + len); // 2
        System.out.println("buf = " + new String(buf)); // cd

        len = fis.read(buf);
        System.out.println("len = " + len); // 1
        System.out.println("buf = " + new String(buf)); // ed

        len = fis.read(buf);
        System.out.println("len = " + len); // -1
        System.out.println("buf = " + new String(buf)); // ed

        // 4. 调用close方法关闭流释放资源
        fis.close();
    }
}

4、字符输出流【Writer类】

java.io.Writer 抽象类是表示用于写出字符流的所有类的超类,将指定的字符信息写出到目的地。它定义了字节输出流的基本共性功能方法。

字符输出流:以字符为单位输出数据
是一个抽象类,只能创建子类对象
是所有字符输出流的父类

常用方法

void close()
	关闭流

void write(char[] cbuf)
    输出字符数组的全部内容

void write(char[] cbuf, int off, int len)
    输出字符数组的一部分,从索引off开始,长度为len的部分

void write(int c)
    输出一个字符

void write(String str)
    输出一个字符串

void write(String str, int off, int len)
    输出一个字符串的一部分,从索引off开始,长度为len的部分

Writer类常用子类

FileWriter(今天重点)
BufferedWriter
OutputStreamWriter
PrintWriter

5、字符文件输出流(FileWriter类)

java.io.FileWriter 类是写出字符到文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。

字符输出流:以字符为单位输出数据
是一个抽象类,只能创建子类对象
是所有字符输出流的父类

构造方法

FileWriter(String path)

FileWriter(String path,boolean append)

FileWriter(File file)

FileWriter(File file,boolean append)

代码示例

public class FileWriterConstructor {
	public static void main(String[] args) throws IOException {
		// 使用File对象创建流对象
		File file = new File("a.txt");
		FileWriter fw = new FileWriter(file);
		
		// 使用文件名称创建流对象
		FileWriter fw = new FileWriter("b.txt");
	}
}

6、字符输出流代码示例

写出单个字符

方法:write(int b)
每次写出一个字符数据

public class FWWrite {
	public static void main(String[] args) throws IOException {
		// 使⽤用文件名称创建流对象
		FileWriter fw = new FileWriter("fw.txt");
		
		// 写出数据
		fw.write(97); // 写出第1个字符
		fw.write('b'); // 写出第2个字符
		fw.write('C'); // 写出第3个字符
		fw.write(30000); // 写出第4个字符,中文编码表中30000对应一个汉字。

		/*
		【注意】关闭资源时,与FileOutputStream不同。
		如果不关闭,数据只是保存到缓冲区,并未保存到文件。
		*/
		//fw.close();
	}
}

输出结果:
abC⽥田
  1. 虽然参数为int类型四个字节,但是只会保留一个字符的信息写出。
  2. 未调用close⽅方法,数据只是保存到了了缓冲区,并未写出到文件中。

关闭和刷新

因为内置缓冲区的原因,如果不关闭输出流,就无法写出字符到文件中。但是关闭流对象后又无法继续写出数据的。如果我们既想写出数据,又想继续使用流,就需要 flush 方法了。

flush :刷新缓冲区,流对象可以继续使用。
close :关闭流,释放系统资源。关闭前会刷新缓冲区。
flush方法和close方法的区别

flush:刷新缓存区,将缓存区中的数据输出到目标文件中,流还可以继续使用
close:关闭流释放资源,流不可再使用了,close方法内部会触发flush方法的调用

public class FWWrite {
	public static void main(String[] args) throws IOException {
		// 使用文件名称创建流对象
		FileWriter fw = new FileWriter("fw.txt");
		
		// 写出数据,通过flush
		fw.write('刷'); // 写出第1个字符
		fw.flush();
		fw.write('新'); // 继续写出第2个字符,写出成功
		fw.flush();
		
		// 写出数据,通过close
		fw.write('关'); // 写出第1个字符
		fw.close();
		fw.write('闭'); // 继续写出第2个字符,【报错】java.io.IOException: Stream closed
		fw.close();
	}
}

即便是flush方法写出了了数据,操作的最后还是要调用close方法,释放系统资源。

写出字符数组

写出字符数组 : write(char[] cbuf) 和 write(char[] cbuf, int off, int len)
每次可以写出字符数组中的数据,用法类似FileOutputStream

public class FWWrite {
	public static void main(String[] args) throws IOException {
		// 使用文件名称创建流对象
		FileWriter fw = new FileWriter("fw.txt");
		
		// 字符串转换为字节数组
		char[] chars = "黑马程序员".toCharArray();
		
		// 写出字符数组
		fw.write(chars); // 黑马程序员
		
		// 写出从索引2开始,2个字节。索引2是'程',两个字节,也就是'程序'。
		fw.write(chars,2,2); // 程序
		
		// 关闭资源
		fos.close();
	}
}

写出字符串

方法:write(String str) 和 write(String str, int off, int len)
每次可以写出字符串中的数据,更为方便

public class FWWrite {
	public static void main(String[] args) throws IOException {
		// 使用文件名称创建流对象
		FileWriter fw = new FileWriter("fw.txt");
		
		// 字符串
		String msg = "黑马程序员";
		
		// 写出字符数组
		fw.write(msg); //黑马程序员
		
		// 写出从索引2开始,2个字节。索引2是'程',两个字节,也就是'程序'。
		fw.write(msg,2,2); // 程序
		
		// 关闭资源
		fos.close();
	}
}

输出流的续写和换行

public class FWWrite {
	public static void main(String[] args) throws IOException {
		// 使用文件名称创建流对象,可以续写数据
		FileWriter fw = new FileWriter("fw.txt"true);
		
		// 写出字符串
		fw.write("⿊马");
		
		// 写出换行
		fw.write("\r\n");
		
		// 写出字符串
		fw.write("程序员");
		
		// 关闭资源
		fw.close();
	}
}
输出结果:
⿊马
程序员

字符流,只能操作文本文件,不能操作图片,视频等非文本文件。
当我们单纯读或者写文本文件时使用字符流,其他情况使用字节流
文本中的数据,必须是键值对形式,可以使用空格、等号、冒号等符号分隔。

九、概念对比

1、字符流和字节流用法

字符流只能操作文本文件,不能操作图片,视频等非文本文件。
其他情况使用字节流

2、字节、字符、数字概念

字节

字节(Byte)是存储数据的基本单位。常见的存储单位主要有bit(位)、B(字节)、KB(千字节)、MB(兆字节)、GB(千兆字节)。

1B=8bit、1KB=1024B、1MB=1024KB、1GB=1024MB。
其中 B 是 Byte 的缩写

二进制数只有0和1,1个二进制数(0或1)就是1bit。
即1个字节=8个二进制数

什么是字节(Byte)https://blog.csdn.net/wydyd110/article/details/84589541

1个数字=多少字节

十进制的数字需要换成二进制。

十进制0~9,最大的9换成二进制为1001。
也就是说一个数字最大为4个比特(bit),即0-9范围中两个数字为一个字节。
如果是任意数就不一定是多少个字节,比如256换成二进制就是1000000。1个“256”=7/8字节

字符

字符是什么?

字符包括:字母、数字、汉字和其他符号

字符char

字符型char是存一个字符变量的类型,字符变量用’ ‘概括表示,中间填入任意一个字符(也叫符号),例如’a’ ‘b’ ‘c’ 之类。
“1”、“我”、“!”、“w”、“¥"是字符,单个的元素组成
“111”、“ni”、“我的”、“!!!”都不是字符,都是字符串

字符和字节

字节转字符会根据编码表,数字对应字符。这个数字再转换成二进制变成字节

不同的字符所占的字节是不同的。不同的编码方式规定了字符所占的字节。
Java采用unicode编码来表示字符
1个字符型char是2个字节
1个汉字(含繁体)或字母都占2个字节
1个英文标点符号占1个字节
1个中文标点符号占2个字节。

采用其他编码方式,一个字符占用的字节数则各不相同。
GB2312 编码或 GBK 编码
1个英文字母占1个字节
1个汉字占2个字节。

UTF-8编码
1个字母占1个字节
1个汉字占3-4个字节。

UTF-16编码
1个字母占2个字节
1个汉字占3-4个字节(Unicode扩展区的一些汉字存储需要4个字节)。

UTF-32编码中
世界上任何字符的存储都需要4个字节。

3、字节数组和字符数组

理解:
字节数组 byte[] :存转换后对应字节的二进制数字
字符数组 char[] :存字符(字母、数字、汉字和其他符号)

4、字符串跟字符数组的区别

字符串就是字符数组,字符串可以转换成字符数组
而字符的本质就是整数(编码表通过数字查询对应的字符),而整数可以是一个或多个字节的组成的。

计算机基础ascii码,字符就是靠用整数来编码的,字符存储时就是整数,只不过显示给你看的时候是你以为的有意义的字符而已。
如字符‘A’和整数65是可以判断相等的,因为’A’的本质就是65.

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值