摘要
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项目-根目录-重命名
如何判断文件路径是相对路径还是绝对路径?
判断是否以盘符开始
如果是则是绝对路径,否则是相对路径:默认相对于当前项目的根目录
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表达式,数组和集合区别
三、递归
递归:指在当前方法内调用自己的这种现象。
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的和
分析
使用递归实现功能的步骤:
- 定义方法:确定返回值和参数列表
- 找递归的规律
- 找递归的出口
分析查找递归规律
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/…
编码和解码概述
编码:将文本转换为二进制数据的过程
解码:将二进制数据转换为文本的过程
计算机中数据的本质
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()用法:
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();
}
}
- 虽然write(int b) 参数为int类型四个字节,但是只会保留一个字节的信息写出。
- 流操作完毕后,必须释放系统资源,调⽤用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统一。
小结
注意事项小结
- 流关联的目标文件必须是普通文件,不能是文件夹
- 流一旦关闭之后就不能再读写数据了
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
- 虽然读取了一个字节,但是会自动提升为int类型。
- 流操作完毕后,必须释放系统资源,调用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⽥田
- 虽然参数为int类型四个字节,但是只会保留一个字符的信息写出。
- 未调用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)
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.