IO流
File
File类概述和构造方法
File:他是文件和目录路径名的抽象表示
- 文件和目录是可以通过File封装成对象的
- 对于File而言,其封装的并不是一个真正存在的文件,仅仅是一个路径名而已。他可以使存在的,也可以是不存在的。
将来是要通过具体的操作吧这个路径的内容转换为具体存在的
方法名 | 说明 |
---|---|
File(String pathname) | 通过给定的路径名字符串转换为抽象路径名来创建新的File实例 |
File(String parent,String child) | 从父路径名字符串和子路径名字符串创建新的File实例 |
File(File parent,String child) | 从父抽象路径名和子路径名字符串创建新的File实例 |
代码测试:
package com.luli.test01;
import java.io.File;
public class FileDemo01 {
public static void main(String[] args) {
File file01 = new File("D:\\JavaSE\\IHaveADream.txt");
//File file01 = new File("D:\\JavaSE\\IHaveADream.txt");
System.out.println(file01);
File file02 = new File("D:\\JavaSE","IHaveADream.txt");
System.out.println(file02);
File file03 = new File("D:\\JavaSE");
File file04 = new File(file03,"IHaveADream.txt");
System.out.println(file04);
}
}
运行结果:
File类创建功能
方法名 | 说明 |
---|---|
public boolean creatNewFile() | 当具有该名成文件不存在是,创建一个由该抽象路径名命名的新空文件 |
public boolean mkdir() | 创建由此抽象路径名命名的目录 |
public boolean mkdirs() | 创建由此抽象路径名命名的目录,包括任何必须但不存在的父目录 |
代码测试:
package com.luli.test01;
import java.io.File;
import java.io.IOException;
public class FileDemo02 {
public static void main(String[] args) throws IOException { //IO需要抛出异常
//需求1:我要在D:\\JavaSE目录下创建一个文件java.txt
File f1 = new File("D:\\JavaSE\\java.txt");
//如果文件不存在则创建并返回true;如果文件存在则不创建并返回false
System.out.println(f1.createNewFile());
//需求2:我要在D:\\JavaSE目录下创建一个目录dream
File f2 = new File("D:\\JavaSE\\dream");
System.out.println(f2.mkdir());
//需求3:我要在D:\\JavaSE目录下创建一个多级目录Mydream\\had
File f3 = new File("D:\\JavaSE\\Mydream\\had");
System.out.println(f3.mkdirs());
}
}
运行结果:
File类判断和获取功能
代码测试:
package com.luli.test01;
import java.io.File;
public class FileDemo03 {
public static void main(String[] args) {
File file1 = new File("muFile\\java.txt");
//测试次抽象路径名表示的File是否为目录
System.out.println(file1.isDirectory());
//测试此抽象路径名表示的FIle是否为文件
System.out.println(file1.isFile());
//测试此抽象路径名表示的File是否存在
System.out.println(file1.exists());
System.out.println("========");
//返回此抽象路径名的绝对路径名字符串
System.out.println(file1.getAbsolutePath());
//将此抽象路径名转换为路径名字符串
System.out.println(file1.getPath());
//返回由此抽象路径名表示的文件或目录名称
System.out.println(file1.getName());
System.out.println("========");
File f2 = new File("D:\\JavaSE");
//返回此抽象路径名表示的目录中的文件和目录的名称字符串数组
String[] strArray = f2.list();
for (String str : strArray){
System.out.println(str);
}
System.out.println("========");
//返回此抽象路径名表示的目录中的文件和目录的File对象数组
File[] files = f2.listFiles();
for (File file : files){
if (file.isFile()){
System.out.println(file.getName());
}
}
}
}
运行结果:
File类删除功能
方法名 | 说明 |
---|---|
public boolean delete() | 删除此抽象路名表示的文件或目录 |
代码测试:
package com.luli.test01;
import java.io.File;
public class FileDemo04 {
public static void main(String[] args) {
File f = new File("D:\\JavaSE\\java.txt");
System.out.println(f.delete());
}
}
运行结果:
递归
递归概述:以编程角度来看,递归指的是方法定义中调用方法本身的现象
递归解决问题的思路:
把一个复杂的问题层层转化为一个与原问题相似的规模较小的问题求解
递归策略只需要少量的程序就可以描述出解题过程所需要的多次重复计算
递归解决问题要找到的两个内容:
- 递归出口:否则会出现内存溢出
- 递归规则:与原问题相似的规模较小的问题
代码测试:
package com.DiGui;
public class DiGuiDemo {
public static void main(String[] args) {
int sum = add(3,1,1); //要求的月份;第一个月的数量;第二个月的数量
System.out.println(sum);
}
public static int add(int flag,int num1,int num2){
int num3 = num1 + num2;
flag++;
if (flag <= 20){ //月份等于20再求下去则为需要的数量
return add(flag,num2,num3);
}else {
return num3;
}
}
}
运行结果:
案例:递归求阶乘
代码测试:
package com.DiGui;
public class DiGuiDemo02 {
public static void main(String[] args) {
System.out.println(jiecheng(5));
}
public static int jiecheng(int num){
if(num == 1){
return num;
}
return num*jiecheng(num-1);
}
}
运行结果:
案例:遍历目录
需求:给定一个路径,通过递归完成遍历该目录下所有的内容,并把所有文件的绝对路径输出
代码测试:
package com.DiGui;
import java.io.File;
public class DiGuiDemo03 {
public static void main(String[] args) {
File file = new File("D:\\JavaSE");
Digui(file);
}
public static void Digui(File file){
File[] files = file.listFiles();
if(files != null){
for (File file1 : files){
if(file1.isDirectory()){
Digui(file1);
}else {
System.out.println(file1.getAbsoluteFile());
}
}
}
}
}
运行结果:
字节流
IO流概述与分类
IO流的概述:
- IO:输入/输出(input/output)
- 流:是一种抽象概念,是对数据传输的总称。也就是说数据在设备间的传输成为流,流的本质是数据传输。
- IO流就是用来处理设备间数据传输问题的
常见的应用:文件复制,文件上传,文件下载
IO流的分类:
- 按照数据流向
输入流:读数据
输出流:写数据 - 按照数据类型来分
字节流 字节输入流;字节输出流
字符流 字符输入流;字符输出流
一般来说,我们说IO流的分类是按照数据类型来分的
那么这两种流都在什么情况下使用呢?
- 如果数据通过Windows自带的记事本软件打开,我们还可以读懂里面的内容,就是用字符流
- 否则使用字节流。不知道该使用哪种类型的流,就是用字节流
字节流写数据
字节流抽象基类
- InputStream:这个抽象类是表示字节输入流的所有类的超类
- OutputStream:这个抽象类是表示字节输出流的所有类的超类
- 子类名特点:子类名称都是以父类名作为子类名的后缀
FileOutputStream:文件输出流用于将数据写入File
- FileOutputStream(String name):创建文件输出流以指定的名称写入文件
使用字节输出流写数据的步骤:
- 创建字节输出流对象(调用系统功能创建了文件,创建字节输出流对象,让字节输出流对象指向文件)
- 调用字节输出流对象的写数据方法
- 释放资源(关闭次文件输出流并释放与此流相关联的任何系统资源)
代码测试:
package com.luli.test01;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileOutStreamDemo01{
public static void main(String[] args) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream("D:\\JavaSE\\test1.txt");
/*
做了三件事情
1.调用系统功能创建了文件
2.创建了字节输出流对象
3.让字节输出流对象之乡创建好的文件
*/
//将指定的字节写入次文件输出流
fileOutputStream.write(98);
//释放资源 关闭此文件输出流,释放与此流相关联的任何系统资源
fileOutputStream.close();
}
}
运行结果:
字节流写数据的3种方式
代码测试:
package com.luli.FileOut;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class FileOutputStreamDemo01 {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("D:\\JavaSE\\test02.txt");
// new File(name) 底层相当于new了一个File文件
// FileOutputStream fos2 = new FileOutputStream(new File("JavaSE\\test01.txt"));
// write写字符
fos.write(97);
fos.write(98);
fos.write(99);
fos.write(100);
// write写入字符数组
byte[] bytes = {101,102,103,104};
fos.write(bytes);
// String getBytes() 获取字符串对应的字节数组
String str = new String("===This is a String");
byte[] bytes1 = str.getBytes();
fos.write(bytes1);
// 测试直接用字符串转,一行代码实现三行代码的功能
fos.write("====Test for String".getBytes());
fos.write(bytes1,1,4);
fos.close();
}
}
运行结果:
字节流写数据的两个小问题
- 字节流写数据如何实现换行?
写入换行符? - 字节流写数据如何实现追加写入?
new FileOutputStream(“D:\JavaSE\test03.txt”,true); 构造方法第二个参数传入true,则为追加写入。
代码测试:
package com.luli.FileOut;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class FileOutoutStreamDemo02 {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("D:\\JavaSE\\test03.txt");
fos.write("test01".getBytes());
fos.write(10); // 换行
fos.write("\r\n".getBytes()); // \n的asc码就是10吧?
fos.write("test02".getBytes());
fos.close(); // 第一个已关闭
// 追加写入 构造方法第二个参数传入true 则为追加写入
FileOutputStream fos2 = new FileOutputStream("D:\\JavaSE\\test03.txt",true);
fos2.write(10);
fos2.write("追加内容".getBytes(StandardCharsets.UTF_8));
fos2.close();
}
}
运行结果:
字节流写数据加异常处理
finally:在异常处理是提供finally块来执行所有清除操作。比如说IO流中的释放资源
标准的异常处理方法:
代码测试:
package com.luli.FileOut;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileOutputStreamDemo03 {
public static void main(String[] args) {
FileOutputStream fos = null;
try{
fos=new FileOutputStream("Z:\\JavaSE\\test04.txt");
fos.write("hello".getBytes());
// fos.close();
}catch (IOException e){
System.out.println(e);
}finally {
try {
// if(fos!=null){
fos.close();
// }
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
运行结果:
代码修改:
package com.luli.FileOut;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileOutputStreamDemo03 {
public static void main(String[] args) {
FileOutputStream fos = null;
try{
fos=new FileOutputStream("Z:\\JavaSE\\test04.txt");
fos.write("hello".getBytes());
// fos.close();
}catch (IOException e){
System.out.println(e);
}finally {
if (fos != null){ //杜绝空指针异常
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
运行结果:
不再有红色字体实际报错。
字节流读数据
需求:把文件IHaveADream.txt中的内容读取出来在控制台输出
FileInputStream:从文件系统的文件获取输入字节
- FileInputStream(String name):通过打开与实际文件的链接来创建一个FileInputStream,该文件有文件系统中的路径名name命名
使用字节输入流读数据的步骤:
1:创建字节输入流对象
2:调用字节输入流对象的读取方法
3:释放资源
代码测试:
package com.luli.FileOut;
import java.io.FileInputStream;
import java.io.IOException;
public class FileInputStreamDemo01 {
public static void main(String[] args) throws IOException {
// 创建字节输入流的对象
FileInputStream fis = new FileInputStream("D:\\JavaSE\\IHaveADream.txt");
// 调用字节输入流的读书句方法
// int read(),从该输入流读取一个字节的数据
// 第一次读取数据
int a = fis.read();
System.out.println(a+"转换后:"+(char)a);
// 第二次读取数据
int b = fis.read();
System.out.println(b+"转换后:"+(char)b);
// 如果到达文件末尾,则返回-1
// 释放资源
fis.close();
}
}
运行结果:
如果字节过多,则需要使用循环:
代码测试:
package com.luli.FileOut;
import java.io.FileInputStream;
import java.io.IOException;
public class FileInputStreamDemo01 {
public static void main(String[] args) throws IOException {
// 创建字节输入流的对象
FileInputStream fis = new FileInputStream("D:\\JavaSE\\IHaveADream.txt");
/*
// 如果到达文件末尾,则返回-1
int c = fis.read();
while (c!=-1){
System.out.print((char)c);
c = fis.read();
}
*/
// 优化上面的程序
int c;
while ((c=fis.read())!=-1){
System.out.print((char)c);
}
// 释放资源
fis.close();
}
}
运行结果:
字节流复制文本文件
案例:复制文本文件
需求:吧“D:\JavaSE\IHaveADream.txt”复制到模块目录下的“IHaveADream.txt”下
代码测试:
package com.luli.FileOut;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class CopyTxtDemo {
public static void main(String[] args) throws IOException {
// 根据数据源创建字节输入流对象
FileInputStream fis = new FileInputStream("D:\\JavaSE\\IHaveADream.txt");
// 根据目的地创建字节输出流对象
FileOutputStream fos = new FileOutputStream("D:\\JavaSE\\IHaveADream2.txt");
//读写数据
int by;
while ((by = fis.read()) != -1){
fos.write(by);
}
}
}
运行结果:
字节流读数据(一次读一个字节数组数据)
需求:把文件中的内容读取出来在控制台输出
使用字节输入流读数据的步骤:
1:创建字节输入流对象
2:调用字节输入流对象的读取方法
3:释放资源
代码测试:
package com.luli.FileOut;
import java.io.FileInputStream;
import java.io.IOException;
public class CopyTxtDemo02 {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("D:\\JavaSE\\IHaveADream.txt");
byte[] bytes = new byte[5];
// 第一次读数据
int len = fis.read(bytes); // 返回的实际的数据长度 不是一开始给定的数组的长度
System.out.println(len +":"+new String(bytes));
// 第二次读数据
len = fis.read(bytes);
System.out.println(len +":"+new String(bytes));
// 第三次读数据
len = fis.read(bytes);
System.out.println(len +":"+new String(bytes,0,len)); //实际有多少字节,则转多少字节为字符串
// 释放资源
fis.close();
}
}
运行结果:
依然可以用循环来解决这个问题:
代码测试:
package com.luli.FileOut;
import java.io.FileInputStream;
import java.io.IOException;
public class CopyTxtDemo02 {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("D:\\JavaSE\\IHaveADream.txt");
byte[] bytes = new byte[5];
/* // 第一次读数据
int len = fis.read(bytes); // 返回的实际的数据长度 不是一开始给定的数组的长度
System.out.println(len +":"+new String(bytes));
// 第二次读数据
len = fis.read(bytes);
System.out.println(len +":"+new String(bytes));
// 第三次读数据
len = fis.read(bytes);
System.out.println(len +":"+new String(bytes,0,len)); //实际有多少字节,则转多少字节为字符串
*/
int len;
while ((len = fis.read(bytes)) != -1){
System.out.println(len +":"+new String(bytes,0,len));
}
// 释放资源
fis.close();
}
}
运行结果:
案例:复制图片
需求:复制一个图片文件
代码测试:
package com.luli.FileOut;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class CopyJpgDemo01 {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("D:\\JavaSE\\testjpg01.jpg");
FileOutputStream fos = new FileOutputStream("D:\\JavaSE\\testjpg02.jpg");
byte[] bytes = new byte[1024];
int a;
while ((a=fis.read(bytes)) != -1){
fos.write(bytes);
}
fis.close();
fos.close();
}
}
运行结果:
字节缓冲流
字节缓冲流:
- BufferedOutputStream:该类实现缓冲输出流。通过设置这样的输出流,应用程序可以向底层输出流写入字节,而不必为写入的每个字节导致底层系统的调用
- BufferedInputStream:创建BufferedInputStream将创建一个内部缓冲区数组。当从流中读取或跳过字节时,内部缓冲区将根据需要从包含的输入流中重新填充,一次很多字节
构造方法:
- 字节缓冲输出流:BufferedOutputStream(OutputStream out)
- 字节缓冲输入流:BufferedInputStream(InputStream in)
为什么构造方法需要的是字节流,而不是具体的文件或路径呢?
- 因为字节缓冲流 仅仅提供缓冲区,而真正的读写数据害得依靠基本的字节流对象进行操作
代码测试:
package com.luli.FileOut;
import java.io.*;
import java.nio.charset.StandardCharsets;
public class BufferedDemo01 {
public static void main(String[] args) throws IOException {
// 字节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\JavaSE\\bufferTest.txt"));
bos.write("hello\r\n".getBytes());
bos.write("world\r\n".getBytes());
bos.close();
// 字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\JavaSE\\bufferTest.txt"));
// 一次读取一个字节数据
/* int by;
while ((by = bis.read()) != -1){
System.out.print((char)by);
}
bis.close();
*/
// 一次读取一个字节数组数据
byte[] by = new byte[1024];
int len;
while ((len = bis.read(by)) != -1){
System.out.print(new String(by,0,len));
}
bis.close();
}
}
运行结果:
字符流
为什么会出现字符流
由于字节流操作中文不是特别的方便,所以Java就提供字符流
- 字符流 = 字节流 + 编码表
用字节流复制文本文件时,文本文件也会有中文,但是没有问题,因为最终底层操作会自动进行字节拼接成中文。
代码测试:
package com.luli;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
/*
* 一个汉字存储:
* 如果是GBK编码,占用2个字节
* 如果是UTF编码,占用3个字节
* 字节流读取中文文本非常不方便
* */
public class FileInputStreamDemo01 {
public static void main(String[] args) throws UnsupportedEncodingException {
// 英文字母测试字节
String str1 = "abcd";
System.out.println(Arrays.toString(str1.getBytes()));
// 汉字测试字节
String str2 = "长余佩之";
System.out.println(Arrays.toString(str2.getBytes())); // 默认编码
System.out.println(Arrays.toString(str2.getBytes("UTF-8"))); // UTF-8编码
System.out.println(Arrays.toString(str2.getBytes("GBK"))); // GBK编码
}
}
运行结果:
编码表
基础知识:
- 计算机中储存的信息都是用二进制数表示的,我们在屏幕上看到的英文,汉字等字符都是二进制数转换后的结果
- 按照某种规则,将字符存储到计算机中,成为编码。反之,将存储在计算机中的二进制数按照某种规则解析显示出来,成为解码。这里强调一下:按照A编码存储,必须按照A编码解析,这样才能显示正确的文本符号。否则就会导致乱码现象
字符编码:就是一套自然语言的字符与二进制数之间的对应规则
字符集:
- 是一个系统支持的所有字符的集合,包括个国家文字、标点符号、图形符号、数字等
- 计算机要准确的存储和识别各种字符集符号,就需要进行字符变码,一套字符集必然至少有一套字符编码。常见字符集有ASCII字符集、GBXXX字符集、Unicode字符集等
ASCII字符集:
- ASCII(American Standard Code for Information Interchange,美国信息交换标准代码):是基于拉丁字母的一套电脑编码系统,用于显示现代英语,主要包括控制字符(回车、退格、换行键等)和可显示字符(应为大小写字符、阿拉伯数字和西文符号)
GBXXX字符集:
- GB2312:简体中文码表。一个小于127的祝福的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,这样大约可以组合了包含7000多个简体字,此外数学符号、罗马希腊的字母、日文的假名等都编进去了,连在ASCII里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的“全角”字符,而原来在127号以下的那些就叫“半角”字符了
- GBK:最常用的中文编码表。是在GB2312标准基础上的扩展规范没使用了双字节编码方案,共收录了21003个汉字,完全兼容GB2312标准,同时支持繁体汉字以及日韩文字等
- GB18030:最新的中文码表。收录汉字70244个,采用多字节编码,每个字可以有1个、2个或4个字节组成。支持中国国内少数民族的文字,同时支持繁体字汉字以及日韩汉字等
Unicode字符集:
- 为表达任意语言的任意字符而设计,是业界的一种标准,也称为统一码、标准万国码。他最多使用4个字节的数据来表达每个字母、符号、或者文字。有三种解决方案:UTF-8、UTF-16和UTF-32。最为常用的是UTF-8编码
- UTF-8编码:可以用来表示Unicode标准中任意字符,它是电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。它使用1-4个字节为每个字符编码
编码规则:- 128个US-ASCII字符,只需要1个字节编码
- 拉丁文等字符,需要二个字节编码
- 大部分常用字(含中文),使用三个字节编码
- 其他极少使用的Unicode辅助字符,使用四字节编码
字符串中的编码解码问题
编码:
- byte[] getBytes():使用平台的默认字符集将该String编码为统一系列字节,将结果存储到新的字节数组中
- byte[] getBytes(String charsetName):使用指定的字符集将该String编码为一系列字节,将结果存储到新的字节shuzuzhong
解码:
- String(byte[] bytes):通过使用平台的默认字符集解码指定的字节数组来构造新的String
- String(byte[] bytes,String charsetName):通过指定的字符集解码 指定的字节数组来构造新的String
代码测试:
package com.luli;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
public class StringDemo {
public static void main(String[] args) throws UnsupportedEncodingException {
// 定义一个字符串
String str = "长余佩之";
// 编码
byte[] bytes01 = str.getBytes();
System.out.println(Arrays.toString(bytes01));
byte[] bytes02 = str.getBytes("UTF-8");
System.out.println(Arrays.toString(bytes02));
byte[] bytes03 = str.getBytes("GBK");
System.out.println(Arrays.toString(bytes03));
// 解码
System.out.println(new String(bytes01));
System.out.println(new String(bytes02));
System.out.println(new String(bytes03)); //使用默认的编码会导致文字混乱
System.out.println(new String(bytes03,"GBK"));
}
}
运行结果:
字符流中的编码解码问题
字符流抽象基类
- Reader:字符输入流的抽象类
- Writer:字符输出流的抽象类
字符流中和编码解码问题相关的两个类:
- InputStreamReader
- OutputStreamWriter