尚硅谷_Java零基础教程-java入门必备-初学者基从入门到精通全套完整版(宋红康主讲) P577-619
(*)表示仅需了解
文章目录
1. File类的使用
- java.io.File类:文件和文件目录路径的抽象表示形式,与平台无关。
- File能新建、删除、重命名文件和目录,但File不能访问文件内容本身。如果要访问文件内容本身,则需要使用输入/输出流。
- 想要在Java程序中表示一个真实存在的文件或目录,那么必须有一个File对象,但是Java程序中的一个File对象,可能没有一个真实存在的文件或目录。
- File对象可以作为参数传递给流的构造器。
File类的实例化
// 构造器1
String dir = "D:\\web\\IdeaProjects\\chunzhao\\";
File file = new File("hello.txt");
File file1 = new File(dir+"src\\learn\\others\\io\\hello.txt");
System.out.println(file);//hello.txt
System.out.println(file1);//D:\web\IdeaProjects\chunzhao\src\learn\others\io\hello.txt
//构造器2
File file2 = new File(dir , "src\\learn\\others\\io");
System.out.println(file2);//D:\web\IdeaProjects\chunzhao\src\learn\others\io
//构造器3
File file3 = new File(file2, "hello.txt");
System.out.println(file3);//D:\web\IdeaProjects\chunzhao\src\learn\others\io\hello.txt
*路径分隔符
- 路径中的每级目录之间用一个路径分隔符隔开。
- 路径分隔符和系统有关:
- windows和DOS系统默认使用""
- UNIX和URL默认使用"/"
- Java程序支持跨平台,因此路径分隔符要慎用。
- 为了解决这个隐患,File类提供了一个常量:
public static final String separator
。根据操作系统动态的提供分隔符。例如
new File("d:"+File.separator+"java"+File.separator+"demo1"+File.separator+"hello.txt")
File类的常用方法
- File类的获取功能
- getAbsolutePath
- getPath
- getParent
- getName
- length:文件长度(字节数)
- lastModified:毫秒值。可以用时间相关类转化为显示时间
- list:获取名称
- listFiles:获取文件
System.out.println(file1.getAbsolutePath());
//D:\web\IdeaProjects\chunzhao\world.txt --创建文件之后--> 不变
System.out.println(file1.getPath()); //world.txt --创建文件之后--> 不变
System.out.println(file1.getParent()); //null ---> null
System.out.println(file1.getName()); //world.txt ---> world.txt
System.out.println(file1.length()); //0 ---> 12
System.out.println(file1.lastModified()); //0 ---> 1592209152962
File file = new File(this.dir);
String[] list = file.list();
for (String s:list) {
System.out.println(s);
}
// .idea
// chunzhao.iml
// out
// src
// world.txt
File[] files = file.listFiles();
for (File f:files ) {
System.out.println(f);
}
// D:\web\IdeaProjects\chunzhao\.idea
// D:\web\IdeaProjects\chunzhao\chunzhao.iml
// D:\web\IdeaProjects\chunzhao\out
// D:\web\IdeaProjects\chunzhao\src
// D:\web\IdeaProjects\chunzhao\world.txt
- File类的重命名功能
- renameTo:重命名(可以移动路径)
File file1 = new File("world.txt");
File file2 = new File(dir+"src\\learn\\others\\io\\world.txt");
//移动文件路径:需要file1存在,file2不存在
boolean renameTo = file1.renameTo(file2);
System.out.println(renameTo);
- File类的判断功能
- isDirectory
- isFile
- isHidden
- exists
- canRead
- canWrite
File file2 = new File("world.txt");
File file1 = new File(dir+"src\\learn\\others\\io\\world.txt");
//默认值(如果文件不存在)都为false
System.out.println(file1.isAbsolute());
System.out.println(file1.isDirectory());
System.out.println(file1.isFile());
System.out.println(file1.isHidden());
System.out.println(file1.canRead());
System.out.println(file1.canWrite());
System.out.println(file1.canExecute());
-
File类的创建功能
- createNewFile
- mkdir:如果此文件目录存在或上层目录不存在,不创建。
- mkdirs:多级目录
- 如果没有写盘符,默认在项目路径下
-
File类的删除功能
- delete:不走回收站,要注意目录下是否有文件
@Test
public void test6(){
File file = new File("newFile.txt");
if (file.exists()){
file.delete();
System.out.println("删除文件成功");
return;
}
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("创建文件成功");
}
@Test
public void test7(){
//目录
File directory = new File("D:\\web\\IdeaProjects\\chunzhao\\src\\learn\\others\\io","newDir");
if (directory.exists()){
boolean delete = directory.delete();
if (delete) {
System.out.println("删除目录成功");
}else {
System.out.println("删除目录失败");
}
return;
}
boolean mkdir = directory.mkdir();
if (mkdir){
System.out.println("创建目录成功");
}else {
System.out.println("创建目录失败");
}
例题1:计算指定目录所占的空间
@Test
public void test1(){
File file = new File("D:\\web\\IdeaProjects\\chunzhao\\src\\learn\\others\\io");
System.out.println(calDirSize(file));
}
public long calDirSize(File dir){
if (dir.isFile()) return dir.length();
long len = 0;
for (File f:dir.listFiles()) {
len+=calDirSize(f);
}
return len;
}
例题2:删除指定目录下的所有文件和目录。
2. IO流原理及流的分类
- IO是Input/Output的缩写,I/O技术是非常实用的技术,用于处理设备之间的数据传输。如读写文件,网络通讯等。
- java程序中,对数据的输入/输出操作以“流”的方式进行
- java.io包下提供了各种“流”类和接口,用以获取不同种类 的数据,并通过标准的方法输入或输出数据。
输入输出是站在程序的角度来看:
- 输入:读取外部数据(磁盘等存储设备的数据)到程序(内存)中。
- 输出:将程序(内存)数据发送到外部设备中。
流的分类
- 按操作数据单位分:字节流(8bit),字符流(16bit)
- 按照流的流向分:输入流,输出流
- 按流的角色分:节点流(作用在文件上的),处理流(包装流,在已有的流上包裹的流)
(抽象基类) | 字节流 | 字符流 |
---|---|---|
输入流 | InputStream | Reader |
输出流 | OutputStream | Writer |
- Java的IO流共涉及40多个类,主要是从4个抽象基类派生的。
- 由这四个类派生出的子类名称都是以其父类名作为后缀的。(图中蓝色为常用类)
3. 节点流(或文件流)
FileReader类
- read()的理解:返回读入的一个字符。如果到达文件末尾,返回-1
- 异常的处理:为了保证流资源一定可以执行关闭操作。需要使用try-catch-finally
- 读入的文件一定要存在,否则就会报
FileNotFoundException
read()每次读一个字符
对异常的不同处理,有3种写法:
@Test
public void test1() throws IOException {
//1. 实例化 File
File file1 = new File("src\\learn\\others\\io\\world.txt");
boolean exists = file1.exists();
System.out.println(exists);
if (!exists) return;
//2. 提供具体的流
FileReader fileReader = new FileReader(file1);
//3. 数据读入(方式一)
// int data = fileReader.read();
// while (data!=-1){
// System.out.println((char)data);
// data = fileReader.read();
// }
//3. 数据读入(方式一 修改)
int data;
while ((data = fileReader.read()) != -1) {
System.out.println((char) data);
}
//4.流的关闭
fileReader.close();
}
@Test
public void test2() {
//1. 实例化 File
File file1 = new File("src\\learn\\others\\io\\world.txt");
//2. 提供具体的流
FileReader fileReader = null;
try {
fileReader = new FileReader(file1);
//3. 数据读入(方式一 修改)
int data;
while ((data = fileReader.read()) != -1) {
System.out.println((char) data);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.流的关闭
if (fileReader != null) {
try {
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Test
public void test3() {
//1. 实例化 File
File file1 = new File("src\\learn\\others\\io\\world.txt");
//2. File流的实例化
try (FileReader fileReader = new FileReader(file1)) {
//3. 数据读入(方式一 修改)
int data;
while ((data = fileReader.read()) != -1) {
System.out.println((char) data);
}
} catch (IOException e) {
e.printStackTrace();
}
}
read() 每次读多个字符
public void testRead1() {
//1. File类实例化
File file1 = new File("src\\learn\\others\\io\\world.txt");
//2. File流的实例化
try (FileReader fileReader = new FileReader(file1)) {
//3. 读入操作
char[] cbuf = new char[5];//每次读数据存到data空间。长度为5表示一次最多可读5个字符
int len;
while ((len = fileReader.read(cbuf)) != -1) {
//正确方式一
for (int i = 0; i < len; i++) {
System.out.print(cbuf[i]); //hello world!
}
//错误方式一
// for (int i = 0; i < cbuf.length; i++) {
// System.out.print(cbuf[i]); //hello world!orl
// }
//错误方式二
// String str = new String(cbuf);
// System.out.print(str); //hello world!orl
//正确方式二
String str = new String(cbuf,0,len);
System.out.print(str); //hello world!
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
FileWriter
- 输出操作,对应的File可以不存在,会自动创建。
- 如果流使用的构造器是
FileWriter fileWriter = new FileWriter(file1,true);
不覆盖原有文件 - 如果流使用的构造器是
FileWriter fileWriter = new FileWriter(file1,false);
或FileWriter fileWriter = new FileWriter(file1);
覆盖原有文件
//写出数据到文件
@Test
public void testFileWriter(){
//1.File类
File file1 = new File("src\\learn\\others\\io\\world.txt");
//2. File流的实例化
//FileWriter fileWriter = new FileWriter(file1,true) 不覆盖原有文件
//FileWriter fileWriter = new FileWriter(file1,false) 覆盖原有文件
try (FileWriter fileWriter = new FileWriter(file1,false)) {
//3. 写出操作
fileWriter.append("到此一游!");
} catch (IOException e) {
e.printStackTrace();
}
}
复制文件:字符流
//复制一个文件的内容到另一个文件
@Test
public void copyContentBetweenFiles(){
//1.File类
File outFile = new File("src\\learn\\others\\io\\hello.txt");
File inFile = new File("src\\learn\\others\\io\\world.txt");
//2. File流的实例化
try (FileWriter fileWriter = new FileWriter(outFile,true);
FileReader fileReader = new FileReader(inFile)) {
//3. 读写操作
fileWriter.write("\ncopy from "+inFile.getName());
char[] buf = new char[5];
int len ;
while ((len=fileReader.read(buf))!=-1){
String string = new String(buf,0,len);
fileWriter.write(string);
}
} catch (IOException e) {
e.printStackTrace();
}
}
FileInputStream、FileOutputStream
- 对于文本文件,使用字符流
- 对于非文本文件,使用字节流
复制文件:字节流
从一个文件读取内容,复制到另一个文件
@Test
public void testCopyFileStream(){
// 1.造文件
File file1 = new File(curPath, "杨慎.png");
File file2 = new File(curPath, "hello.txt");
File inFile = file2;
File outFile = new File(curPath,"copy of-"+inFile.getName());
// 2. 流类
try(FileInputStream inputStream = new FileInputStream(inFile);
FileOutputStream outputStream = new FileOutputStream(outFile)){
//3. 读写数据
byte[] buf = new byte[10];
int len;
while ((len = inputStream.read(buf)) != -1) {
outputStream.write(buf,0,len);
}
} catch (IOException e) {
e.printStackTrace();
}
}
4. 缓冲流
- 缓冲流是为了提高文件读写效率:内部提供了一个缓冲区。
- 逻辑上,先关缓冲流,再关文件流。操作中,关闭外层流的同时,内层流也会自动进行关闭,因此不用再手动关闭。
- 内部有缓冲器,不用在外部写了:
bos.write(bis.readAllBytes())
- flush:刷新
BufferedInputStream 、BufferedOutputStream
复制文件:有缓冲字节流包裹
@Test
public void testCopyFileStream(){
// 1.造文件
File file1 = new File(curPath, "杨慎.png");
File file2 = new File(curPath, "hello.txt");
File inFile = file1;
File outFile = new File(curPath,"copy of-"+inFile.getName());
// 2. 流类
try(
// 2.1造节点流
FileInputStream inputStream = new FileInputStream(inFile);
FileOutputStream outputStream = new FileOutputStream(outFile);
// 2.2造缓冲流
final BufferedInputStream bis = new BufferedInputStream(inputStream);
final BufferedOutputStream bos = new BufferedOutputStream(outputStream)){
//3. 读写数据
// byte[] buf = new byte[1024];
// int len;
// while ((len = bis.read(buf)) != -1) {
// bos.write(buf,0,len);
// }
//内部有缓冲器,不用在外部写了
bos.write(bis.readAllBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
BufferedReader、BufferedWriter
复制文件:缓冲字符流
BufferedReader和BufferedWriter的按行读数据
String data;
while ((data= bf.readLine())!=null){
bw.write(data);
bw.newLine();//提供换行操作
}
缓冲流练习
- 实现图片加密:write(read()^5)
- 获取文本上每个字符出现的次数:用map
5. 转换流
- 转换流提供了在字节流和字符流之间的转换
- InputStreamReader:将一个字节的输入流转换为字符的输入流
- OutputStreamWriter:将一个字节的输出流转换为字符的输出流
复制文件:改变编码方式
- 把字节输入流转换成字符输入流,按照编码方式1;
- 把字节输出流转换成字符输出流,按照编码方式2;
- 输入流读取数据,写入输出流。
@Test
public void test1(){
String str1 = curPath+"hello.txt";
String str2 = curPath+"杨慎.txt";
try (
final InputStreamReader isr = new InputStreamReader(new FileInputStream(str1),"UTF-8");
final OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(str2),"GBK");
)
{
char[] cbug = new char[1024];
int len;
while ((len=isr.read(cbug))!=-1){
osw.write(cbug,0,len);
}
}catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
*字符集
常见的编码表
-
ASCII
:美国标准信息交换码(一个字节的7位) -
ISO8859-1
:拉丁码表,欧洲码表(一个字节的8位) -
GB2312
:中国的中文编码表。最多两个字节编码所有字符 -
GBK
:中国的中文编码表升级。最多两个字节编码所有字符 -
Unicode
:国际标准码,融合了目前人类使用的所有字符。位每个字符分配唯一的字符码。两个字节来表示。 -
UTF-8
:变长的编码方式,可用1-4个字节来表示一个字符。(修正的UTF-8编码最多可能需要6个字节) -
Unicode不完美,有三个问题:1. 英文字母一个字节表示就够了, 2.如何才能区分Unicode和ASCII? 计算机怎么知道两个字节表示一个符号,而不是分别表示两个符号?3. 如果和GBK等双字节编码方式一样,用最高位是1或0来表示两个字节和一个字节,就少了很多可用的值,两个字节不够表示所有字符。Unicode在很长一段时间内无法推广,直到互联网的出现。
-
面向传输的众多UTF(UCS Transfer Format)标准出现了。UTF-8就是每次8个位传输数据,
UTF-16
就是每次16位。这是为传输而设计的编码,并使编码无国界,这样就可以显示全世界所有文化的字符了。 -
Unicode只是定义了一个庞大的、全球通用的字符集,并为每个字符规定了唯一确定的编号,具体存储成什么样的字节流,取决于字符编码方案。推荐的Unicode编码是UTF-8和UTF-16。
ANSI
编码,通常指的是平台的默认编码。例如英文操作系统中是ISO-8859-1,中文系统是GBK- Unicode编码,是对UTF-8、UCS-2/UTF-16等具体编码方案的统称,并不是具体的编码方案。
6. 标准输入、输出流(*)
System.in、System.out
练习键盘输入字符串,进行操作
从键盘输入字符串,要求将读到的整行字符转化为大写输出,然后继续输入,知道输入"e" 或"exit"退出
try(final InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);)
{
System.out.println("请输入字符串:");
while (true) {
String data = br.readLine();
if ("e".equalsIgnoreCase(data) || "exit".equalsIgnoreCase(data)) {
System.out.println("程序结束");
break;
}
System.out.println(data.toUpperCase());
}
}
catch (IOException e) {
e.printStackTrace();
}
7. 打印流(*)
- 实现将基本数据类型的数据格式转化为字符串输出
- 打印流:PrintStream和 PrintWriter
- 提供了一系列重载的print()和println()方法,用于多种数据类型的输出
- PrintStream和 PrintWriter的输出不会抛出IOException异常
- PrintStream和 PrintWriter有自动flush功能
- PrintStream打印的所有字符都默认使用平台的默认字符编码转换为字节。在需要写入字符而不是写入字节的情况下,应该使用 PrintWriter。
- System.out返回的是PrintStream的实例。
PrintStream、 PrintWriter
把标准输出流改成文件
@Test
public void test2(){
// PrintStream ps = null;
String str = "src\\learn\\others\\io\\PrintStream.txt";
try (final FileOutputStream fos = new FileOutputStream(new File(str));
PrintStream ps = new PrintStream(fos,true)){
if (ps!=null){//把标准输出流改成文件
System.setOut(ps);
}
for (int i = 0; i < 256; i++) {
System.out.print((char) i );
if (i%10==0){
System.out.println();//换行
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
8. 数据流(*)
- 为了方便地操作java语言的基本数据类型和String的数据,可以使用数据流
- 数据流有两个类(用于读取和写出基本数据类型、String类的数据):
- DataInputStream和DataOutputStream
- 分别“套接”在InputStream和OutputStream子类的流上
- DataInputStream的方法:
- readBoolean()
- readChar()
- readByte()
- …
- DataOutputStream的方法:
- writeBoolean()
- writeChar()
- writeByte()
- …
输入输出测试
@Test
public void test3() throws IOException {
DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));
dos.writeUTF("数据输出流");
dos.flush();
dos.writeInt(23);
dos.flush();
dos.writeBoolean(true);
dos.flush();
dos.close();
}
@Test
public void test4() throws IOException {
DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));
System.out.println(dis.readUTF());
System.out.println(dis.readInt());
System.out.println(dis.readBoolean());
dis.close();
}
9. 对象流
- ObjectInputStream和ObjectOutputStream
- 用于存储和读取基本数据类型和对象的处理流。它的强大之处就是可以把java中的对象写入到数据源中,还能把对象还原回来。
- 序列化:用ObjectOutputStream类保存基本数据类型或对象的机制。
- 凡序列化:用ObjectInputStream类读取基本数据类型或对象的机制
- ObjectInputStream和ObjectOutputStream不能序列化
static
和transient
修饰的成员变量。
对象的序列化
- 对象序列化机制允许把内存中的java对象转换成平台无关的二进制流,从而允许把这种二进制流持久的保存在硬盘上(序列化),或通过网络把这种二进制流传输到另一个网络节点。当其他程序获取了这种二进制流,就可以恢复成原来的java对象(反序列化)
- 序列化的好处是可将任何实现了Serializable接口的对象转化为字节数据,使其在保存和传输时可被还原
- 序列化是RMI(Remote Method Invoke——远程方法调用)过程的参数和返回值都必须实现的机制,而RMI是JavaEE的基础。因此序列化机制 是JavaEE平台的基础。
- 如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可序列化的,为了让某个类是可序列化的,该类必须实现以下两个接口之一。
Serializable
Externalizable
- 凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量
private static final long serialVersionUID
- serialVersionUID用来表明类的不同版本间的兼容性。其目的是以序列化对象进行版本控制,有关各版本反序列化时是否兼容
- 如果类没有显式定义这个静态变量,它的值是java运行时环境根据类的内部细节自动生成的。若类的实例变量做了改变,serialVersionUID可能会发生变化,因此,建议显式声明。
- 简单来说,java的序列化机制是通过在运行时判断类serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID和本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会报
InvalidCastException
异常。
序列化的过程
- 需要实现接口Serializable
- 当前类需要一个全局常量
private static final long serialVersionUID
- 当前类内部所有属性也必须是可序列化的。(默认情况下,基本数据类型和String是可序列化的)
- static和transient修饰的不会被序列化
10. 随机存取文件流(*)
RandomAccessFile类
- RandomAccessFile声明在java.io包下,但直接继承于Object类。并且它实现了DataInput、DataOutput这两个接口,也就意味着这个类既可以读也可以写。
- RandomAccessFile类支持“随机访问”的方式,程序可以直接跳到文件的任意地方来读、写文件
- 支持只访问文件的部分内容
- 可以向已存在的文件后追加内容
- RandomAccessFile对象包含一个记录指针,用以标示当前读写处的位置。
- RandomAccessFile类对象可以自由移动记录指针:
- accessFile.getFilePointer(); 获取指针当前位置
- accessFile.seek(long pos); 指定指针当前位置
- 构造器:
- new RandomAccessFile(new File(str),“rw”);
- 创建类实例需要指定一个mode参数,该参数决定访问模式:
- r / rw /
- rwd :同步文件内容的更新
- rws :同步文件内容和元数据的更新。
- 如果为r,则不会创建文件,而是去读取一个已经存在的文件,如果读取的文件不存在则会出现异常。如果模式为rw,如果文件不存在则会创建文件,存在就不创建。
输入输出测试:
String str = "src\\learn\\others\\io\\world.txt";
try(final RandomAccessFile accessFile = new RandomAccessFile(new File(str),"rw");){
accessFile.write("xyz".getBytes());
System.out.println(accessFile.getFilePointer());
long pos = 10l;
accessFile.seek(pos);
accessFile.write("TEN".getBytes());
} catch (IOException e) {
e.printStackTrace();
}
插入操作实现(无)
用RandomAccessFile实现“插入”操作:
因为write()会覆盖文件原内容,可以先把内容读取出来保存起来(用StringBuilder或ByteArrayOutputStream保存),覆盖之后再append这部分内容。
多线程断点下载(无)
可以用RandomAccessFile这个类,来实现一个多线程断点下载的功能:
下载时建立两个临时文件,一个是与被下载文件大小相同的空文件,一个是记录文件指针的位置文件,每次暂停的时候,都会保存上一次的指针,然后下次继续下载,从而实现断点下载或上传的功能。
11. NIO.2中Path、Paths、Files类的使用 (*)
Java NIO
- Java NIO(New IO/ Non-Blocking IO)是从java1.4开始引入的一套新的IO API。可以替代标准的java IO API。 NIO与原本的IO有相同的作用和目的,但使用的方式完全不同,NIO支持面向缓冲区的(IO是面向流的)、基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作。
- Java API中提供了两套NIO,一套针对标准输入输出流NIO,另一套就是网络编程NIO
|---- java.nio.channels.Channel
|----FileChannel:处理本地文件
|----SocketChannel:TCP网络编程的客户端的Channel
|----ServerSocketChannel:TCP网络编程的服务端的Channel
|----DatagramChannel:UDP网络编程的发送端和接收端的Channel - NIO.2
- JDK7,对NIO进行了极大的扩展,增强了对文件处理和文件系统特性的支持
Path、Paths、Files核心API
- 早期的java只提供一个File类来访问文件系统,但File的功能比较有限,所提供的方法性能也不高。并且,大多数方法在出错时,仅返回失败,并不能提供异常信息。
- NIO.2 为了弥补这种不足,引入了Path接口,代表一个平台无关的平台路径,描述了目录结构中文件的位置。Path可以看成是File类的升级版本,实际引用的资源也可以不存在。
- Paths类提供的静态get方法用来获取Path对象:
Path path = Paths.get("index.html")
- java.nio.file.Files用于操作文件或目录的工具类。
- 使用第三方jar包(例如commons.io-2.5.jar)实现数据读写