IO流
-
用于读写数据的(可以读写文件,或网络中的数据)
概述:
I指 Input,称为输入流:负责从磁盘或网络上将数据读到内存中去
O指Output,称为输出流,负责写数据出去到网络或磁盘上
因此,IO流总体来看就有四大流
-
字节输入流:以内存为基准,在管道中,以一个一个字节的形式,从外部读取数据到内存中的流。
-
字节输出流:以内存为基准,在管道中,以一个一个字节的形式,将数据从内存中读取到外部磁盘或网络中的流。
-
字符输入流:以内存为基准,在管道中,以一个一个字符的形式,从外部读取数据到内存中,只适合操作纯文本文件的流。
-
字符输出流:以内存为基准,在管道中,以一个一个字符的形式,把内存中的数据写出到磁盘文件或者网络介质中的流。
前置知识:
File
-
File是java.io.包下的类,File类的对象,用于代表当前操作系统的文件(可以是文件、或文件夹)。
-
File类提供的主要功能:
-
获取文件信息(大小,文件名,修改时间)
-
判断文件的类型
-
创建文件/文件夹
-
删除文件/文件夹
-
注:File类只能对文件本身进行操作,不能读写文件里面存储的数据
File类:对象的创建
常用方法
构造器 | 说明 |
---|---|
public File(String pathname) | 根据文件路径创建文件对象 |
public File(String parent,String child) | 根据父路径与子路径名字创建文件对象 |
public File(File parent, String child) | 根据父路径对应文件对象和子路径名字创建文件对象 |
代码展示:
package com.lyc.io;
import java.io.File;
//测试file类的构造器
public class FileTest1 {
public static void main(String[] args) {
//1.创建一个File对象,指代某个具体的文件 绝对路径 带盘符
File file = new File("D:\\IdeaProjects\\collectionTest\\src\\main\\java\\com\\lyc\\io\\test.txt");
//也可以使用/ 来表示
file = new File("D:/IdeaProjects/collectionTest/src/main/java/com/lyc/io/test.txt");
//也可以使用File.separator来表示分隔符,这个分隔符是系统相关的,具有兼容性
System.out.println(file.length());//返回的是文件的字节个数
//2.创建一个File对象,指代某个具体的文件夹
File file1 = new File("D:\\IdeaProjects\\collectionTest");
System.out.println(file1.length());//4096 是指这个文件夹本身的字节数,不包括里面的文件
//注意:File对象只带一个不存在的文件路径
File file2 = new File("D:\\IdeaProjects\\collectionTest\\src\\main\\java\\com\\lyc\\io\\test1.txt");
System.out.println(file2.length()); //为0
//3.判断文件是否存在
System.out.println(file2.exists());
//使用相对路径创建文件对象
File file3 = new File("src/main/java/com/lyc/io/test.txt");
System.out.println(file3.exists());
}
}
注意:
-
File对象既可以代表文件,也可以代表文件夹
-
File封装的对象仅仅是一个路径名,这个路径可以是存在的,也允许是不存在的
绝对路径、相对路径
-
绝对路径:从盘符开始
-
相对路径:不带盘符,默认直接到当前工程的目录下寻找文件
File类:常用方法
方法名称 | 说明 |
---|---|
public boolean exists() | 判断当前文件对象,对应的文件路径是否存在,存在则返回true |
public boolan isFile() | 判断当前文件对象指代的是否为文件,是文件返回true,反之false |
public boolean isDirectory() | 判断当前文件对象指代的是否是文件夹,是文件夹返回true,反之false |
public String getName() | 获取文件的名称(包含后缀) |
public long length() | 获取文件的大小,返回字节个数 |
public long lastModified() | 获取文件的最后修改时间 |
public String getPath() | 获取创建文件对象时,使用的路径 |
public String getAbsolutePath() | 获取绝对路径 |
public boolean createNewFile() | 创建一个新文件(文件内容为空) |
public boolean mkdir() | 用于创建文件夹,注意:只能创建一级文件夹 |
public boolean mkdirs() | 用于创建多级文件夹 |
public boolean delete() | 删除文件 或者空文件夹 注意:不能删除非空文件夹,而且删除后的文件不会进入回收站 |
File类提供的遍历文件夹的功能
方法名称 | 说明 |
---|---|
public String[] list() | 返回一个String数组,获取当前目录下所有的“一级文件名称” |
public File[] listFiles() | 返回一个File数组,获取当前目录下所有的“一级文件对象” |
使用listFiles方法时的注意事项:
-
当主调是文件,或者路径不存在时,返回null
-
当主调是空文件夹时,返回一个长度为零的数组
-
当主调是一个有内容的文件夹时,将里面所有一级文件和文件夹的路径放在File数组中返回
-
当主调是一个文件夹,且里面有隐藏文件时,将里面所有文件和文件夹的路径放在File数组中返回,包含隐藏文件
-
当主调是一个文件夹,但是没有权限访问该文件夹时,返回null
代码展示:
package com.lyc.io;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.logging.SimpleFormatter;
//测试文件类的常用方法
public class FileTest2 {
public static void main(String[] args) {
//1.创建文件对象
File file = new File("src/main/java/com/lyc/io/test.txt");
//2:判断文件是否存在 public boolean exists()
System.out.println(file.exists());
//3.判断文件是否是文件 public boolean isFile()
System.out.println(file.isFile());
//4.判断文件是否是目录 public boolean isDirectory()
System.out.println(file.isDirectory());
//5.获取文件或者目录的名称 public String getName()
System.out.println(file.getName());
//6.获取文件的绝对路径 public String getAbsolutePath()
System.out.println(file.getAbsolutePath());
//7.获取文件的长度 public long length()
System.out.println(file.length());
//8.获取文件的最后修改时间 public long lastModified() 返回的是毫秒值 需要转换为日期格式
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
long l = file.lastModified();
System.out.println(simpleDateFormat.format(l));
//9.获取创建文件对象时,使用的路径 public String getPath()
System.out.println(file.getPath());
//11.创建文件 public boolean createNewFile() 创建文件成功返回true 文件为空
File file2 = new File("src/main/java/com/lyc/io/test2.txt");
try {
System.out.println(file2.createNewFile());
} catch (Exception e) {
e.printStackTrace();
}
//12.创建目录 public boolean mkdir()
File file3 = new File("src/main/java/com/lyc/io/test3");
System.out.println(file3.mkdir());
//13.创建多级目录 public boolean mkdirs()
File file4 = new File("src/main/java/com/lyc/io/test3/test4/test5");
System.out.println(file4.mkdirs());
//14.删除文件或者目录 public boolean delete()
System.out.println(file2.delete());
}
}
案例练习:
package com.lyc.io;
import java.io.File;
//测试:改变某个文件夹下视频的序号
public class fileTest {
public static void main(String[] args) {
//1.拿到所有的文件,以及对象
File file = new File("E:\\桌面\\java学习");
//2.拿到所有的文件对象
File[] files = file.listFiles();
//遍历
for (File file1 : files) {
String name = file1.getName();
//截取开始到需要截的地方之间的文件名
String index = name.substring(0, name.indexOf("的"));
//截取需要截取的地方到最后的部分,最后将其拼接
String lastName = name.substring(name.indexOf("的"));
String newName = index + lastName;
//3.修改文件名
if (file1.isFile()) {
file1.renameTo(new File(file,newName));
}
}
}
}
那么文件搜索需要访问不只一级文件夹,我们可以使用方法递归来遍历文件夹
前置知识:方法递归
-
递归是一种算法,在程序设计语言中广泛应用
-
从形式上讲,方法调用自身的形式称为方法递归
-
直接递归:方法自己调用自己
-
间接递归:方法调用其他方法,其他方法又回调方法自己
使用方法递归时需要注意的问题:
-
递归如果没有控制好终止条件,会出现递归死循环,导致栈内存溢出错误
案例1:计算n的阶乘
代码展示:
public static void main(String[] args) {
System.out.println(f(5));
}
public static int f(int n){
if(n==1){
return 1;
}else {
return n * f(n-1);
}
}
}
案例2:斐波那契数列
斐波那契数列的是这样一个数列:1、1、2、3、5、8、13、21、34…,即第一项 f(1) = 1,第二项 f(2) = 1…,第 n 项目为 f(n) = f(n-1) + f(n-2)。求第 n 项的值是多少。
代码展示:
//斐波那契数列
//1、1、2、3、5、8、13、21、34
public class test1 {
public static void main(String[] args) {
for (int i = 1; i < 10; i++) {
System.out.println(fib(i));
}
}
public static int fib(int n){
if (n==1||n==2){
return 1;
}
else{
return fib(n-1)+fib(n-2);
}
}
}
文件搜索
要求:从D:盘中,搜索”QQ.exe“这个文件,找到后直接输出其位置
分析:
-
先找出D:盘下的所有一级文件对象
-
遍历全部一级文件对象
-
如果是文件,判断是否是自己想要的
-
如果是文件夹,需要继续进入到该文件夹,重复上述过程
代码展示:
package com.lyc.io;
import java.io.File;
/*
要求:从D:盘中,搜索”QQ.exe“这个文件,找到后直接输出其位置
分析:
1. 先找出D:盘下的所有一级文件对象
2. 遍历全部一级文件对象
3. 如果是文件,判断是否是自己想要的
4. 如果是文件夹,需要继续进入到该文件夹,重复上述过程
* */
public class FileSearch {
public static void main(String[] args) {
search(new File("D:/"),"QQ.exe");
}
public static void search(File file,String fileName){
//将非法情况拦截
if (!file.exists() || file.isFile()){
return;//无法搜索
}
//1.public File[] listFiles() 返回一个File数组,获取当前目录下所有的“一级文件对象”
File[] files = file.listFiles();
//判断当前目录下是否有文件,以及是否可以拿到文件
if (files != null){
for (File file1 : files) {
//2.遍历全部一级文件对象
if (file1.isFile()){
//3.如果是文件,判断是否是自己想要的
if (file1.getName().equals(fileName)){
System.out.println("路径是:"+file1.getAbsolutePath());
}
}else {
//3.如果当前是文件夹,需要继续进入到该文件夹,重复上述过程
search(file1,fileName);
}
}
}
}
}
拓展案例:
需求:删除非空文件夹
分析:
File默认不可以删除非空文件夹,需要使用递归删除
1.递归删除文件夹中的内容
2.删除文件夹
代码展示:
public static void main(String[] args) {
File file = new File("E:\\桌面\\deleteDemo");
System.out.println(file.exists());
deleteFile(file);
}
public static void deleteFile(File file) {
if (!file.exists()) {
return;
}
if (file.isFile()) {
file.delete();
return;
}
File[] files = file.listFiles();
if (files != null) {
for (File file1 : files) {
deleteFile(file1);
}
}
// 删除文件夹本身
file.delete();
}
前置知识:字符集
美国人制造了计算机,并在其中存储了128个码点用来表达数字,标点符号,特殊字符,英文字母(大小写),其中'a'是97,'0'为48...
被称为ASII字符集
原理就是将这些码点直接转译成二进制,只有8位,正好是1字节,所以ASII字符集使用一个字节存储
标准ASCII字符集
-
ASII:美国信息交换标准代码,包括了英文,符号等
-
标准ASII码使用1个字节存储一个字符,首位是0,总共可以表示128个字符
GBK(汉字内码扩展规范,国标)
-
汉字编码字符集,包含2万多个汉字等字符,GBK中一个中文字符编码成两个字节的形式存储
-
注意:GBK兼容了ASCII字符集,GBK规定:汉字的第一个字节的第一位必须是1
Unicode字符集(统一码,也叫万国码)
-
Unicode是国际组织制定的,可以容纳世界上所有文字、符号的字符集
-
UTF-32 4个字节表示一个字符,容量大,但缺点:占用太多的存储空间,通信效率降低
-
UTF-8 (重点)
UTF-8
-
是Unicode字符集的一种编码方案,采取可变长编码方案,共分成四个长度区:1个字节,2个字节,3个字节,4个字节
-
英文字符、数字等只占一个字节(兼容标准ASII码),汉字字符占用3个字节
拓展:那英文与中文在一起该如何区分?
UTF-8有自己的编码规则
-
在遇见ASII码时,直接就以一个字节的形式编译成二进制,不做其他处理
-
如果是两个字节,编译时要求第一个字节的前三位为110 ,第二个字节的前两位为10,
-
如果是三个字节,编译时要求第一个字节的前四位为1110,后两个字节的前两位为10.
-
如果是四个字节,编译时要求第一个字节的前五位为11110,后三个字节的前两位为10
-
技术人员在开发时都已应该使用UTF-8编码
注意:
-
字符编码时使用的字符集,和解码时使用的字符集必须一致,否则会出现乱码
-
英文,数字一般不会乱码,因为很多字符集都兼容了ASCII编码
字符集的编码,解码
编码:把字符按照指定字符编码成字节
解码:把字节按照指定字符集解码成字符
Java代码完成对字符的编码
String提供了以下方法 | 说明 |
---|---|
byte[] getBytes() | 使用平台的默认字符集将该String编码为一系列字节,将结果存储到新的字节数组中 |
byte[] getBytes(String charsetName) | 使用指定的字符集将String编码为一系列字节,将结果存储到新的字节数组中 |
java代码完成对字符的解码
String提供了以下方法 | 说明 |
---|---|
String(byte[] bytes) | 通过使用平台的默认字符集解码指定的字节数组来构造新的String |
String(byte[] bytes,String charsetName) | 通过指定的字符集解码指定的字节数组来构造新的String |
代码展示:
package com.lyc.io;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
//掌握如何使用Java代码完成对字符的编码与解码
public class charsetTest {
public static void main(String[] args) throws UnsupportedEncodingException {
//1.编码
String data = "a我b";
byte[] bytes = data.getBytes();//默认是按照平台字符集(UTF-8)进行编码的
System.out.println(Arrays.toString(bytes));
//[97, -26, -120, -111, 98] a我b ASCII码 在UTF-8编码中只占一个字节,而汉字在UTF-8编码中占三个字节 负数是因为首字母为1
// 2.按照指定字符集进行编码
byte[] bytes1 = data.getBytes("GBK");
System.out.println(Arrays.toString(bytes1));
//[97, -50, -46, 98]//a我b GBK编码中,汉字占两个字节,而a占一个字节
//3.解码
String s = new String(bytes);//默认按照平台字符集(UTF-8)进行解码
System.out.println(s);//a我b
String s1 = new String(bytes1,"GBK");//按照GBK进行解码
System.out.println(s1);//a我b
}
}
IO流--字节流
文件字节输入流(FileInputStream)
-
作用:以内存为基准,可以把磁盘文件的数据以字节的形式读入到内存中去
构造器 | 说明 |
---|---|
FileInputStream(File file) | 通过打开与实际文件的连接创建一个 FileInputStream ,该文件由文件系统中的 File 对象 file 命名。 |
FileInputStream(String name) | 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name 命名 |
方法名称 | 说明 |
---|---|
int read() | 从该输入流读取一个字节的数据。没有数据返回-1 |
int read(byte[] b) | 从该输入流读取最多 b.length 个字节的数据为字节数组。返回字节数组读取了多少字节,如果为空返回-1 |
注意事项:
-
使用FileInputStream每次读取一个字节,读取性能较差,并且读取汉字会有乱码
-
使用字节数组读取的话也可能出现读取汉字乱码,因为字节数组如果正好卡在汉字的字节之间,就会乱码
1.使用字节流读取中文,如何保证输出不乱码,怎么解决?
-
定义一个与文件一样大的字节数组,一次性读取完文件的全部字节
-
Java官方为Input Stream提供了如下方法,可以把文件的全部字节读取到一个字节数组中返回
问题:
-
如果文件过大,创建的字节数组也会过大,可能引出内存溢出
读写文本内容更适合用字符流 字节流更适合做数据的转移,:如文件复制
byte[]readAllBytes() throws IOException
: 直接将当前字节输入流对应的文件对象的字节数据装到一个字节数组中返回。
public static void main(String[] args) throws Exception {
//一次性读取完文件的全部字节到一个字节数组中
File file = new File("src/main/java/com/lyc/io/test.cpp");
InputStream is = new FileInputStream(file);
//1.创建一个字节数组,大小与文件的大小一致
// long length = file.length();
// byte[] b = new byte[(int)length];
// int len;
// while((len=is.read(b))!=-1){
// System.out.println(new String(b,0,len));
// }
//第二种方法 一次性读取完文件的全部字节到一个字节数组中
byte[] bytes = is.readAllBytes();
System.out.println(new String(bytes));
is.close();
}
}
代码展示:
package com.lyc.io;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
//测试文件字节输入流
public class FileInputStreamTest {
public static void main(String[] args) throws Exception {
//1. 创建文件字节输入流管道,与源文件绑定
File file = new File("src/main/java/com/lyc/io/test.txt");
InputStream fis = new FileInputStream(file);
//2. 从文件字节输入流管道中读取数据
//创建空字节数组
byte[] buffer = new byte[1024];
// int read = fis.read();
// System.out.println((char)read);//读取文件字节返回一个int类型数据,读取到文件末尾返回-1
//循环读取
int b;
while ((b = fis.read(buffer)) != -1) {
if (b > 0) {
//注意:字节数组buffer中,有可能有0,也有可能有数据,所以需要使用字节数组的参数构造方法,指定读取的长度
//所以,offset:0,length:b,从0开始,读取b个字节
System.out.print(new String(buffer, 0, b));
}
}
fis.close();//关闭流
}
}
文件字节输出流(FileOutputStream)
-
作用:以内存为基准,把内存中的数据以字节的形式写出到文件中去
构造器 | 说明 |
---|---|
FileOutputStream(File file) | 创建文件输出流以写入由指定的 File 对象表示的文件。 |
FileOutputStream(File file , boolean append) | 创建字节输出流管道与源文件接通,可追加数据 |
FileOutputStream(String name) | 创建文件输出流以指定的名称写入文件。 |
FileOutputStream(String name, boolean append) | 创建文件输出流以指定的名称写入文件。 可追加数据 |
方法名称 | 说明 |
---|---|
void``write(int b) | 将指定的字节写入此文件输出流。 |
void``write(byte[] b) | 将 b.length 个字节从指定的字节数组写入此文件输出流。 |
void``write(byte[] b, int off, int len) | 将 len 字节从位于偏移量 off 的指定字节数组写入此文件输出流。 |
void``close() | 关闭此文件输出流并释放与此流相关联的任何系统资源。 |
代码展示:
package com.lyc.io;
import java.io.*;
//测试文件字节输出流的使用
public class FileOutputStreamTest {
public static void main(String[] args) throws Exception {
//创建一个文件字节输出流对象
OutputStream os = new FileOutputStream("D:\\IdeaProjects\\collectionTest\\src\\main\\java\\com\\lyc\\io\\test2.txt",true);
//true 表示追加数据
InputStream is = new FileInputStream("src/main/java/com/lyc/io/test.txt");
int len;
byte[] b = new byte[1024];
while ((len=is.read(b))!=-1){
os.write(b,0,len);
}
//写入数据
os.write(97);
os.write('b');
byte[] bytes = "我爱你但...".getBytes();
os.write(bytes);
//写入换行符
os.write("\r\n".getBytes());
os.close();//关闭流
is.close();
}
}
案例:文件复制
代码展示:
package com.lyc.io;
import java.io.*;
// 文件复制
public class copyTest {
public static void main(String[] args) throws Exception {
// 复制照片
File file = new File("E:\\桌面\\3D旋转魔方相册\\001.jpg");
System.out.println(file.exists());
// 1,创建一个字节输入流管道与源文件接通
InputStream is = new FileInputStream(file);
// 2,创建一个字节输出流管道与目标文件接通
// 修改:将目标路径改为有效的文件路径
String targetFilePath = "src/main/java/com/lyc/io/001_copy.jpg";
OutputStream os = new FileOutputStream(targetFilePath);
// 3,把输入流的数据复制到输出流中,一次读写一个字节数组
byte[] b = new byte[1024];
int len;
while ((len = is.read(b)) != -1) {
os.write(b, 0, len);
}
os.close();
is.close();
}
}
小结:字节流非常适合做一切文件的复制操作,任何文件的底层都是字节,字节流做复制,是一字不漏的转移完所有字节,只要复制后的文件格式一致就没问题。
释放资源的方式
-
try-catch-finally
try {
...
...
} catch (IOException e) {
e.printStackTrace();
}finally {
}
finally代码区的特点:无论try中的程序是正常执行了,还是出现了异常,最后一定会执行finally区,除非JVM终止
代码展示:
package com.lyc.io;
public class TryTest {
public static void main(String[] args) {
try {
int a = 110/2;
return;//跳出整个方法
//System.exit(0);//退出虚拟机
}catch (Exception e){
e.printStackTrace();
}finally {
System.out.println("finally");//始终会执行,除非虚拟机停止
}
}
public static int test(int a, int b) {
try {
return a * b;
} catch (Exception e) {
e.printStackTrace();
return -1;
} finally {
System.out.println("finally");
//不要再finally中返回数据,不然整个方法最终返回的finally语句块中的数据,而不是方法中的数据
}
}
}
使用场景:一般用于程序执行完成后进行资源的释放操作。
代码优化:
package com.lyc.io;
import java.io.*;
// 文件复制
public class copyTest {
public static void main(String[] args) throws Exception {
OutputStream os = null;
InputStream is = null;
// 复制照片
try {
File file = new File("E:\\桌面\\3D旋转魔方相册\\001.jpg");
System.out.println(file.exists());
// 1,创建一个字节输入流管道与源文件接通
is = new FileInputStream(file);
// 2,创建一个字节输出流管道与目标文件接通
// 修改:将目标路径改为有效的文件路径
String targetFilePath = "src/main/java/com/lyc/io/001_copy.jpg";
os = new FileOutputStream(targetFilePath);
// 3,把输入流的数据复制到输出流中,一次读写一个字节数组
byte[] b = new byte[1024];
int len;
while ((len = is.read(b)) != -1) {
os.write(b, 0, len);
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
if (os != null) {
os.close();
}
if (is != null) {
is.close();
}
}
}
}
-
try-with-resource
try-catch-finally资源释放很专业,但是代码过于臃肿,在JDK7中推出了更简单的资源释放方案:try-with-resource
该资源使用完毕后,会自动调用其close()方法,完成对资源的释放
try(定义资源1;定义资源2){
可能出现的异常代码;
}catch(异常类名 变量名){
异常的处理代码;
}
代码展示:
package com.lyc.io;
import java.io.*;
//测试文件字节输出流的使用
public class FileOutputStreamTest {
public static void main(String[] args) throws Exception {
try(
//创建一个文件字节输出流对象
OutputStream os = new FileOutputStream("D:\\IdeaProjects\\collectionTest\\src\\main\\java\\com\\lyc\\io\\test2.txt",true);
//true 表示追加数据
InputStream is = new FileInputStream("src/main/java/com/lyc/io/test.txt");
) {
int len;
byte[] b = new byte[1024];
while ((len=is.read(b))!=-1){
os.write(b,0,len);
}
//写入数据
os.write(97);
os.write('b');
byte[] bytes = "我爱你但...".getBytes();
os.write(bytes);
//写入换行符
os.write("\r\n".getBytes());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
注意事项:try后面只能放置资源对象(流对象)。
资源:
- 都是会实现AutoCloseable接口
- 资源都会有close方法
以上就是字节流的详细内容,希望能够帮助到大家