I/O流(万流齐发、万流归宗) 本章目标: 掌握 讲  解:★★★★★ http://kuaibao.qq.com/s/20200527A0LR3000?refer=spider 1.I/O流概

I/O流(万流齐发、万流归宗)
本章目标:
掌握

讲  解:★★★★★
http://kuaibao.qq.com/s/20200527A0LR3000?refer=spider
1.I/O流概述
所谓I/O,也就是Input与Output的缩写。
I => Input 输入—将数据源读到内存中;相当于读取硬盘上的文件
O => Output 输出—将内存数据写(保存)到介质(硬盘)里;相当于保存文件。

流的概念
流是一组有顺序的、有起点和终点的字节集合,是对数据传输的总称和抽象。
即数据在两个设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。

什么叫数据流?
“流”就像一根管道,数据在管道中的走向就叫数据流(也称IO流)。
流是有方向性的

作用:用来处理设备之间的数据传输。
(硬盘存放的文件,内存中的数据…硬盘和内存都是设备)。

电脑中文件的展现形式:文件和文件夹;
电脑中文件的操作方式:读 和 写;

2.IO流的分类
(1)根据数据类型不同分为:字节流、字符流
(2)根据数据流向不同分为:输入流、输出流
(3)根据使用方式不同分为:节点流、处理流

字符流和字节流
字节是数据最小的基本单位,字节流就是对字节进行操作的流对象。多个字节可以组成字符,所以字符流的本质是基于字节流读取时,根据数据编码的不同,查询对应的码表,从而对字符进行高效操作的流对象。

字节流和字符流的区别:
(a)读写单位不同:字节流以字节(8bit)为单位;字符流以字符为单位,根据码表映射字符,一次可能读取多个字节。0000 0000
(b)处理对象不同:字节流能处理所有类型的数据(如图片、视频等);字符流只能处理字符类型的数据。
©字节流在操作的时候本身是不会用到缓冲区的,是对文件本身的直接操作;字符流在操作的时候是会用到缓冲区的,是通过缓冲区来操作文件。

结论:优先选用字节流。因为硬盘上所有的文件都是以字节的形式进行传输或保存的,但是字符只会在内存中形成,所以在开发中,字节流使用广泛。

输入流和输出流
读入写出,即对输入流只能进行读操作,对输出流只能进行写操作,程序中需要根据待传输数据的不同特性而使用不同的流。

节点流和处理流
节点流是连接两个节点的最基本的流,处理流是处理节点流、增强其性能和可操作性的流。

3.I/O流的体系

流的体系因为功能不同,但是有共性内容,不断抽取,形成继承体系。该体系一共有四个基类,而且都是抽象类。
(1)字节流抽象类:InputStream、OutputStream
(2)字符流抽象类:Reader、Writer

java.io总共涉及40多个类,都是从上面四个抽象类中派生出来的。
4.File类
java.io.File类是对文件和文件夹进行操作的类。
在Java中,不管是文件还是文件夹都是叫做File对象,File类将文件系统中的文件和文件夹封装成了对象,并提供了对文件和文件夹的操作方法(这些是流对象办不到的,因为流只操作数据)。
而其他语言(如c#)对文件和文件夹的操作是分开的。

File类的作用:
对文件或文件夹(目录)进行操作(新建、删除、重命名)。
File类不能操作文件中的内容,如需要则使用字节流或字符流操作。

4.1.File类常见方法
构造方法:
方法 说明
File(File parent, String child) 从父抽象路径名和子路径名字符串创建新的 File实例。
File(String pathname) 通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。
File(String parent, String child) 从父路径名字符串和子路径名字符串创建新的 File实例。
File(URI uri) 通过将给定的 file: URI转换为抽象路径名来创建新的 File实例。
提示:parent指定路径(父目录),可以是File类对象也可以是字符串,child中也可以加入路径层级,但要注意,所用的路径必须存在,不存在的路径不会新建。

其他方法:
方法 说明
boolean createNewFile() 在指定目录下创建文件。
如果该文件已存在,则不创建。而对操作文件的输出流而言,输出流对象已建立,就会创建文件,如果文件已存在,会覆盖。除非续写。
boolean mkdir() 创建此抽象路径名指定的目录。
boolean mkdirs() 创建多级目录。
boolean delete() 删除此抽象路径名表示的文件或目录。
注1:在删除文件夹时,必须保证这个文件夹中没有任何内容,才可以将该文件夹用delete删除。
注2:window的删除动作,是从里往外删。java删除文件不走回收站。要慎用。
boolean equals(Object obj) 比较的文件名字相同为true,不同为false
void deleteOnExit() 在虚拟机退出时删除。
long length() 获取文件大小。
String getName() 获得文件名或目录名
String getPath() 获得文件的路径(包含目录与文件名)
String getParent() 获得文件的上一级父目录的名字,如果此路径名没有指定父目录,则返回 null。
File getParentFile() 获得文件的上一级父目录的对象,如果此路径名没有指定父目录,则返回 null。
String getAbsolutePath() 返回此抽象路径名的绝对路径名字符串(与getPath()类似)
long lastModified() 返回文件最后一次被修改的时间
boolean exists() 判断文件或者文件夹是否存在。
boolean isDirectory() 测试此抽象路径名表示的文件是否是一个目录。
boolean isFile() 测试此抽象路径名表示的文件是否是一个标准文件。
boolean isHidden() 测试此抽象路径名指定的文件是否是一个隐藏文件。
boolean isAbsolute() 测试此抽象路径名是否为绝对路径名。
boolean renameTo(File dest) 可以实现移动的效果。(剪切 + 重命名)。
String[] list() 列出指定目录下的当前的文件和文件夹的名称(包含隐藏文件)。
如果调用list方法的File 对象中封装的是一个文件,那么list方法返回数组为null。如果封装的对象不存在也会返回null。只有封装的对象存在并且是文件夹时,这个方法才有效。
File[] listFiles() 获得文件夹下的所有文件的对象
static File listRoots() 获得当前系统的所有盘符

File类静态属性:
File.pathSeparator 返回当前系统默认的路径分隔符,windows默认为 “;”
File.Separator 返回当前系统默认的目录分隔符,windows默认为 “\”,Linux默认为“/”

/abc/

重点提示:
如果对文件或文件夹操作,开发环境使用windows,生产环境是Linux,一定要注意路径中的\,因为windows系统中与Linux系统中表现不一样。
//其中,File.separator表示系统相关的分隔符,Linux下为:/ Windows下为:\
String path = File.separator + “home” + File.separator + “siu” +
File.separator + “work” + File.separator + “demo”;

File类的基本操作示例:
public class FileTest {
public static void main(String[] args){
//构造方法
File f1 = new File(“d:\abc\120.txt”);
File f2 = new File(“d:\abc”, “120.txt”);
File f3 = new File(“http://www.taobao.com/abc/120.txt”);

关于斜杠和反斜杠,只能如下生硬地先记住吧:
\:右手用两个,比如new File(“D:\abc\” + “Test.txt”);
/:左手用一个。比如new File(“D:/abc/” + “Test.txt”);

//创建文件
f1.createNewFile();

//创建文件夹(目录)
f1.mkdir();
f1.mkdirs();

//修改文件名
f1.renameTo(new File(“d:\abc\119.txt”));
//删除文件或文件夹
f1.delete();
//获得文件属性
f1.getName();
f1.getPath();
f1.lastModified();
f1.length();

//获得指定目录下所有文件对象
f1.listFiles();

}
}

5.递归
递归是一种应用非常广泛的算法(或者编程技巧)。递归求解问题的分解过程,去的过程叫“递”,回来的过程叫“归”。

应用场景:当某一功能要重复使用时。

递归的两个条件:
(1)可以通过递归调用来缩小问题规模,且新问题与原问题有着相同的形式。(自身调用)
(2)存在一种简单情境,可以使递归在简单情境下退出。(递归出口)

递归三要素:
(1)一定有一种可以退出程序的情况;
(2)是在尝试将一个问题化简到更小的规模;
(3)父问题与子问题不能有重叠的部分。

递归的分类:
递归分为两种,直接递归和间接递归。
(1)直接递归称为方法自身调用自己。
(2)间接递归可以A方法调用B方法,B方法调用C方法,C方法调用A方法。

递归算法的一般形式:
func( mode){
if(endCondition){ //递归出口 end;

}else{
func(mode_small) //调用本身,递归
}
}

题1:N级台阶(比如100级),每次可走1步或者2步,求总共有多少种走法?
分析:如果有大于2级的n级台阶,那么假如第一次跳一级台阶,剩下还有n-1级台阶,有f(n-1)种跳法,假如第一次跳2级台阶,剩下n-2级台阶,有f(n-2)种跳法。这就表示f(n)=f(n-1)+f(n-2),即斐波那契数列。假设只有一个台阶,那么只有一种跳法,那就是一次跳一级,f(1)=1;如果有两个台阶,那么有两种跳法,第一种跳法是一次跳一级,第二种跳法是一次跳两级,f(2)=2。
//斐波那契数列变形,求n个台阶的走法,递归方式
public int f(int n){
if(n<=2){
return n;
}
return f(n-1)+f(n+2);
}

编写递归代码的关键是,只要遇到递归,我们就把它抽象成一个递推公式,不用想一层层的调用关系,不要试图用人脑去分解递归的每个步骤。

递归代码要警惕重复计算
为了避免重复计算,我们可以通过一个数据结构(比如散列表)来保存已经求解过的 f(k)。

//斐波那契数列变形,求n个台阶的走法,递归方式
public int f(int n){
//新建一个HashMap,用来保存已经求过的f(n),避免重复计算
HashMap<Integer, Integer> hashMap = new HashMap<>();
if(n<=2){
return n;
}
//求f(n)时,先判断map中是否已经存在,如果存在直接取值
if(hashMap.containsKey(n)){
return hashMap.get(n);
}
int ret = f(n-1) + f(n+2);
hashMap.put(n, ret); //将f(n)存进map中
return ret;
}

递归代码要警惕堆栈溢出
我们可以通过在代码中限制递归调用的最大深度的方式来解决这个问题,递归调用超过一定深度(比如 1000)之后,我们就不继续往下再递归了,直接抛出异常。

int depth = 0; //全局变量,表示递归的深度
//斐波那契数列变形,求n个台阶的走法,递归方式
public int f(int n){
depth++;
if(depth > 1000){
System.out.println(“超过设定深度了”);
}
//新建一个HashMap,用来保存已经求过的f(n),避免重复计算
HashMap<Integer, Integer> hashMap = new HashMap<>();
if(n<=2){
return n;
}
//求f(n)时,先判断map中是否已经存在,如果存在直接取值
if(hashMap.containsKey(n)){
return hashMap.get(n);
}
int ret = f(n-1) + f(n+2);
hashMap.put(n, ret); //将f(n)存进map中
return ret;
}

怎么将递归代码改写为非递归代码?
递归本身就是借助栈来实现的,如果我们自己在内存堆上实现栈,手动模拟入栈、出栈过程,便可以将递归改成非递归。

//斐波那契数列变形,求n个台阶的走法,非递归方式
public int f1(int n){
//n=1或n=2,直接返回f(1)=1, f(2)=2
if(n<=2){
return n;
}
int ret = 0;
int prepre = 1;
int pre = 2;
for(int i=3; i<=n; i++){
//手动摸拟栈的过程
ret = prepre + pre;
prepre = pre;
pre = ret;
}
return ret;
}

https://wenku.baidu.com/view/3e46530af12d2af90242e6b8.html
https://www.jianshu.com/p/3b0b92da124c

练习2:
列出一个文件夹下所有子文件夹以及子文件。

思路:
(1)指定文件夹路径;
(2)判断是否是文件,是文件就打印文件路径(到最底层了);相反如果是目录,则继续;
(3)获取文件夹下所有文件对象;
(4)遍历对象数组,执行递归。

递归基本写法:
File fs = new File(“e:\”);
File[] fa = fs.listFiles();
for(File f : fa){
File[] fas = f.listFiles();
for(File f1 : fas){
File[] fass = f1.listFiles();
……
}
}

递归写成方法:
public static void prints(File f){
if(f.isFile()){ //判断是否是文件
System.out.println(f.getPath());
}else if(f.isDirectory()){ //如果是文件夹
File[] fs=f.listFiles(); //得到下面所有的文件对象
for(File fa:fs){
prints(fa);
}
}
}

思考:

1.删除一个目录的过程是如何进行的?

6.字节流
字节流用于处理二进制(0、1)数据的流对象。它是按字节来处理的。

设备上的数据无论是文字、图片、声音或视频,它们都以二进制存储的。
二进制的最终都是以一个8位为数据单元进行体现,所以计算机中的最小数据单元就是字节。
意味着,字节流可以处理设备上的所有数据,当前也包括字符数据。

6.1.字节输入/输出流
字节流主要是对文件内部的数据进行读写操作。
字节流主要分:
字节输入流:InputStream
字节输出流:OutputStream

6.2.InputStream
InputStream是所有字节输入流的父类(抽象类)。它提供了一些方法对文件内容进行操作。
方法 说明
int read() 读取下一个数据字节,如果到达文件尾,返回-1。
int read(byte[] b) 将文件流中的字节存入byte数组中(一次性读出来)。
int read(byte[] b, int off, int len) 将文件流中的最多len个字节的数据读入一个byte数组中。
void close() 关闭文件输入流并释与之有关的系统资源
long skip(long n) 从输入流中跳过并丢弃n个字节的数据。

基本操作:
public class InputStreamDemo{
public static void main(String[] args){
File file = new File(“D:\abc.txt”);
InputStream is = new FileInputStream(file);
int len = is.read();
System.out.println(len);
is.close();
}
}

6.3.OutputStream
OutputStream是所有字节输出流的父类(抽象类)。它提供了一些方法对文件内容进行操作。
方法 说明
void write(int b) 将指定字节写入文件输出流中,一次写一个字节。
void write(byte[] b) 将指定数组写入文件输出流中。
void write(byte[] b, int off, int len) write(byte[],起始位置,长度)表示从字节数组的开始位置写多长
void close() 关闭文件输入流并释与之有关的系统资源
void flush() 刷新此输出流并强制任何缓冲的输出字节被写出。

基本操作:
public class OutputStreamDemo{
public static void main(String[] args){
File file = new File(“D:\abc.txt”);
OutputStream os = new FileOutputStream(file);
os.write(97);
os.write(98);
os.write(99);
os.close();
}
}

7.字符流
因为字符每个国家都不一样,所以涉及到了字符编码问题,那么GBK编码的中文用unicode编码解析是有问题的,所以需要获取中文字节数据的同时 + 指定的编码表才可以解析正确数据。为了方便文字的解析,所以将字节流和编码表封装成对象,这个对象就是字符流。

只要操作字符数据,优先考虑使用字符流体系。

7.1.字符输入/输出流
字符流主要分:
字符输入流:Reader
字符输出流:Writer
7.2.Reader
常用方法:
方法 说明
int read() 读一个字符
int read(char[] cbuf) 将字符读入数组
int read(char[] cbuf, int off, int len) 将字符读入数组的一部分
int read(CharBuffer target) 将字符读入指定的字符缓冲区

示例:
public class BufferedOutputStreamTest {
public static void main(String[] args){

}
}

7.3.Writer
常用方法:
方法 说明

示例:
public class BufferedOutputStreamTest {
public static void main(String[] args){

}
}

8.文件流
即对文件的内容进行读写操作。
FileInputStream 文件输入流(读)
FileOutputStream 文件输出流(写)

8.1.1.FileInputStream
文件输入流是用于将文件或文件夹的内容数据读到内存中。
方法 说明
int read() 读取下一个数据字节,如果到达文件尾,返回-1。
int read(byte[] b) 将文件流中的字节存入byte数组中(一次性读出来)。
int read(byte[] b, int off, int len) 将文件流中的最多len个字节的数据读入一个byte数组中。
void close() 关闭文件输入流并释与之有关的系统资源
long skip(long n) 从输入流中跳过并丢弃n个字节的数据。

示例:
public class InputStreamTest {
public static void main(String[] args){

File f1 = new File(“D:\abc\120.txt”);
FileInputStream fis = new FileInputStream(f1);
//读取一个字节
int r = fis.read();
System.out.println((char)r);

//循环读取
int len;
while((len = fis.read()) != -1){
fis.skip(len); //跳过长度
System.out.println((char)len);
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值