13.1.File类的使用
File类的使用
1.File类的一个对象,代表一个文件或一个文件目录(俗称: 文件夹)
2.File类声明在java.io包下
3.File类中涉及到关于文件或文件目录的创建,删除,重命名,修改时间,文件大小等方法,
并未涉及到写入或读取文件内容的操作.若需要读取或写入文件内容,必须用IO流来完成.
4.后续File类的对象常会作为参数传递到流的构造器中,指明读取或写入的"终点"(:从哪读或写入到哪,或叫节点,这个点就用File来表示),真的要读写内容了,File类的对象主要指的是一个文件就不是文件目录了
- File类的实例化
1.如何创建File类的实例
File(String filePath)
File(String filePath, String childPath)
File(File parentFile, String childPath)
2.相对路径: 相较于某个路径下,指明的路径
绝对路径: 包含盘符在内的文件或文件目录的路径
3.路径分隔符:
windows: \
unix: /
@Test
public void test1(){
// 构造器1
File file1 = new File("hello.txt");// 路径相对于当前module
File file2 = new File("F:\\markdown\\JavaSE\\基础语法\\src\\com\\JavaSenior\\IO\\he.txt");// 第二条斜杠表示避免转义的意思
// 这里还这是单元测试现在还不需要对应硬盘中的文件,此时就仅仅只是内存层面的对象,还没有涉及到对文件中的数据的操作,现在就理解为内存层面对象而已
System.out.println(file1);
System.out.println(file2);
// 构造器2,在上一层目录下指定的可以是文件也可以是文件目录
File file3 = new File("F:\\markdown\\JavaSE\\基础语法\\src\\com","JavaSenior");// 上一层目录和下一层目录
System.out.println(file3);
// 构造器3: 在file3的目下创建的文件
File file4 = new File(file3, "hi.txt");
System.out.println(file4);
}
- File类的常用方法
// File类的常用方法
@Test
public void test2(){
File file1 = new File("hello.txt");
File file2 = new File("F:\\markdown\\IO\\hi.txt");
// 获取绝对路径
System.out.println(file1.getAbsolutePath());
// 获取路径
System.out.println(file1.getPath());
//获取名字
System.out.println(file1.getName());
// 获取上层文件目录路径,若无返回null
System.out.println(file1.getParent());
// 获取文件长度(大小)(即:字节数).不能获取目录长度
System.out.println(file1.length());
// 获取最后一次修改时间,毫秒值
System.out.println(new Date(file1.lastModified()));
System.out.println();
System.out.println(file2.getAbsolutePath());
System.out.println(file2.getPath());
System.out.println(file2.getName());
System.out.println(file2.getParent());
System.out.println(file2.length());
System.out.println(file2.lastModified());
}
// 如下的两个方法适用于文件目录:
//public String[] list(): 获取指定目录下的所有文件或文件目录的名称数组
// public File[] listFiles(): 获取指定模流下的所有文件或文件目录的File数组
@Test
public void test3(){
File file = new File("F:\\markdown\\JavaSE");
String[] list = file.list();// 获取下一层目录或文件
for (String s : list) {
System.out.println(s);
}
File[] files = file.listFiles();// 以绝对路径方式获取下一层文件或目录
for (File f : files) {
System.out.println(f);
}
}
/*
public boolean renamTo(File dest): 把文件移动到指定文件路径并重命名
比如: file1.renameTo(file2)为例:
要想保证返回true,要file1在硬盘中是存在的,且file2不能在硬盘中存在
*/
@Test
public void test4(){
File file1 = new File("hello.txt");
File file2 = new File("F:\\markdown\\IO\\hi.txt");
boolean renameTo = file1.renameTo(file2);
System.out.println(renameTo);
}
@Test
public void test5(){
File file1 = new File("hello.txt");
System.out.println(file1.isDirectory());//是否为文件目录
System.out.println(file1.isFile());// 是否为文件
System.out.println(file1.exists());//是否真正在硬盘中存在
System.out.println(file1.canRead());//是否可读
System.out.println(file1.canWrite());//是否可写
System.out.println(file1.isHidden());//是否隐藏
// 判断文件呢目录
File file2 = new File("F:\\markdown\\IO");
System.out.println(file2.isDirectory());
System.out.println(file2.isFile());
System.out.println(file2.exists());
System.out.println(file2.canRead());
System.out.println(file2.canWrite());
System.out.println(file2.isHidden());
}
@Test
public void test6() throws IOException {
File file1 = new File("hi.txt");
// 如果不存在
if (!file1.exists()) {
// 创建文件.若文件真实存在硬盘中,则不创建,返回false
file1.createNewFile();
System.out.println("创建成功");
}else{// 文件存在
// 删除磁盘中的文件或文件目录,java中的删除不走回收站
file1.delete();
System.out.println("删除成功");
}
}
@Test
public void test7(){
// 文件目录(夹)的创建
File file1 = new File("F:\\markdown\\IO\\IO1\\IO3");// 上一层目录存在
// 创建文件目录.如果此文件目录存在,就不创建了.如果此文件目录的上层目录不存在,也不创建
boolean mkdir = file1.mkdir();
if (mkdir) {
System.out.println("创建成功1");
}
File file2 = new File("F:\\markdown\\IO\\IO1\\IO4");
// 创建文件目录,若上层文件目录不存在,一并创建
boolean mkdirs = file2.mkdirs();
if (mkdirs) {
System.out.println("创建成功2");
}
// 文件删除,要想成功,欲删除的文件目录下不能有子目录或文件
File file3 = new File("F:\\markdown\\IO\\IO1\\IO3");
System.out.println(file3.delete());
}
File类的练习
public class FileDemo {
// 在
@Test
public void test1() throws IOException {
// 创建一个与file同目录下的另一个文件,名为"haha.txt"
File file = new File("F:\\markdown\\IO\\IO1\\hello.txt");
File destFile = new File(file.getParent(), "haha.txt");
boolean newFile = destFile.createNewFile();
if (newFile) {
System.out.println("创建成功");
}
}
// 打印出指定目录所有文件名称,包括子文件目录中的文件
@Test
public void test4(){
// 创建目录对象
File dir = new File("F:\\markdown\\JavaSE");
printSubFile(dir);
}
public static void printSubFile(File dir){
// 打印目录子文件
File[] subFiles = dir.listFiles();
// 循环每个子文件
for (File f : subFiles) {
// 判断子文件是否文件目录
// 是文件夹,递归打印文件目录方法
if (f.isDirectory()) {
printSubFile(f);
}else{
// 不是文件夹,输出每个文件的绝对地址
System.out.println(f.getAbsolutePath());
}
}
}
}
13.2.IO流原理及流的分类
- 字节流: 以一个byte(8bit)为基本单位传输的流;适合处理非文本数据
- 字符流: 以一个char(两个字节:16bit)为基本单位传输的;适合处理文本数据
- 节点流: 直接作用在文件上的流
- 处理流: 作用在已有节点流处理之上的
- IO流的体系结构
一.流的分类:
1.操作的数据单位: 字节流,字符流
2.数据的流向: 输入流,输出流
3.流的角色: 节点流,处理流
二.流的体系结构
抽象基类 | 节点流(或文件流) | 缓冲流(处理流的一种) |
---|---|---|
InputStream | FileInputStream (read(byte[] buffer)) | BufferedInputStream (read(byte[] buffer)) |
OutputStream | FileOutputStream (write(byte[] buffer,0,len)) | BufferedOutputStream (write(byte[] buffer,0,len))/ flush() |
Reader | FileReader (read(char[] cbuf)) | BufferedReader (read(char[] cbuf)) |
Writer | FileWriter (write(char[] cbuf,0,len)) | BufferedWriter (write(char[] cbuf,0,len))/ flush() |
13.3.节点流(或文件流).
- FileReader读入数据的基本操作
public class FileReaderWriterTest {
public static void main(String[] args) {
// 用绝对路径可以无视main和单元测试
// 相较于当前工程: F:\markdown\JavaSE\hello.txt
File file = new File("hello.txt");
System.out.println(file.getAbsolutePath());
File file1 = new File("IO\\hello.txt");// 地址这要写就可以相较于当前module下
System.out.println(file1.getAbsolutePath());
}
/*
将IOmodule下的hello.txt文件内容读入程序中,并输出到控制台,实现读取硬盘中文件的操作
说明点:
1.read()的理解: 返回读入的一个字符.如果达到文件末尾,返回-1
2.异常处理: 为了保证流资源一定可以执行关闭操作.要使用try-catch-finally处理
3.读入的文件一定要存在,否则就会报FileNotFoundException
*/
@Test // 单元测试,归具体的module所有
public void testFileReader() {
// 异常处理用try-catch比throws好,假如流的对象创建好了没有抛异常,但是下面read的时候出现阻塞读不过来抛了一个IOException
// 出现了异常就会创建一个异常对象,默认情况下就抛出程序了,相当于程序后面就不执行了,这个时候流就没关,资源就浪费了,存在内存泄露
// 为了保证即使出现了read异常也要能执行流的资源能执行关闭操作,所以不要用过throws处理,流资源的关闭放在finally是为了防止中途return退出
FileReader fr = null; // 因为外部要使用,所以这里要先定义
try {
// 1.实例化File类的对象,指明要操作的文件
File file = new File("hello.txt");// 相较于当前module:F:\markdown\JavaSE\基础语法\hello.txt
// 2.提供具体的字符流来操作文件,相当于给文件提供一个管道来输出数据
fr = new FileReader(file);// 参数提供文件的路径或名字,上面的file文件一定要存在,否则就会报异常
// 打开管道的开关,让数据流出
// 3.数据的读入
// read(): 返回读入的一个字符.如果达到文件末尾,返回-1,读的是char类型但返回的是字符的int型值
// int data = fr.read();// 读取文件返回单个字符,文件读完(读取字符末尾结束时)返回-1,文件有多个字符就要用到循环
// 如果文件是空的,上来就是-1,所以先要做判断,并且有好多字符要读,用while循环
/*while (data != -1) {
// 以字符形式输出每个字符
System.out.print((char) data);
// 再读一个看看是不是-1
data = fr.read();//像迭代器一样
}*/
// 方式二: 把方法直接写到循环体表达式汇总
int data;
while ((data = fr.read()) != -1) {
System.out.println((char) data);
}
} catch (IOException e) {
e.printStackTrace();
}
// 4.手动关闭流的操作,防止内存泄露
/*try {
// 假如文件流对象没有实例化出现了异常,这里关闭会出现空指针异常,所以造了对象才要关闭
if (fr != null)
fr.close();
} catch (IOException e) {
e.printStackTrace();
}*/
// 或
if (fr != null) {
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
- FileReader使用read(char[ ] cbuf)读入数据
// 对read()操作升级: 使用read的重载方法
@Test
public void testFileReader1() {
FileReader fr = null;
try {
// 1.File类的实例化(创建文件)
File file = new File("hello.txt");
// 2.FileReader流的实例化(创建读取文件的流对象)
fr = new FileReader(file);
// 3.读入的操作
// read(char[] cubf): 返回每次到底写入cbuf数组中的字符的个数.如果达到文件末尾,返回-1
// 先创建一个数组用来缓存要读数据的个数
char[] cbuf = new char[5];// 可以存放5个字符的数组
int len;// 定义每次读数据的个数
// 判断每次读数据的个数,并且每读一次数据都会覆盖一次数组,可以理解为数组元素的替换
while ((len = fr.read(cbuf)) != -1) {
// 如果遍历的长度为cbuf.length,输出的结果就是123ld,因为最后一次只有3个字符,无法完全覆盖上一次 最后2个字符就留下了,所以不能是
// 方式一:
// 错误的写法
/*for (int i = 0; i < cbuf.length; i++) {
}*/
// 正确写法: 每次读进去几个就遍历几个,不要过多的取后面的
/*for (int i = 0; i < len; i++) {
System.out.print(cbuf[i]);
}*/
// 方式二:
// 错误,对应着方式一的错误写法
// 这里cbuf第三次读取还是保留最后的ld的,相当于数组有什么就全取出来了,其实跟cbuf.length一样
/*String str = new String(cbuf);// char型数组转成String型数组
System.out.print(str);*/
// 正确写法: 用String的重载构造器,从头开始取,取到第len个
String str = new String(cbuf, 0, len);
System.out.println(str);
}
} catch (IOException e) {
e.printStackTrace();
}finally { // 写在finally里防止try的时候return
// 4.资源的关闭
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- FileWriter写出数据的操作
从内存中写出数据到硬盘的文件里
说明:
1.输出操作,对应的File可以不存在的.
File对应的硬盘中的文件若不存在,在输出过程中,会自动创建此文件
若存在:
如果流用的构造器是: FileWriter(file,false) / FileWriter(file): 会对原有文件进行覆盖
如果流用的构造器是: FileWriter(file,true): 不会对原有文件覆盖, 而是在原有文件基础上追加内容
@Test
public void testFileWriter() throws IOException {
FileWriter fw = null;
try {
// 1.提供File类的对象,指明写出的文件
File file = new File("hello1.txt");
// 2.提供FileWriter类(数据流)的对象,用于数据的写出
fw = new FileWriter(file, true);// 第二个参数的意思: 是否在原有文件上添加,表示对原有文件的覆盖,默认为false
// 3.写出操作
fw.write("have dream\n");
fw.write("have dream");
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
// 4.流资源关闭
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 用FileReader和FileWriter实现文本文件的复制
@Test
public void testFileReaderFileWriter() {
FileReader fr = null;
FileWriter fw = null;
try {
// 1.创建File类的对象,指明读取和写出的文件
// 不能用字符流处理图片等字节数据
File srcFile = new File("hello.txt");
File destFile = new File("hello2.txt");
// 2.创建输入流和输出流的对象
fr = new FileReader(srcFile);// 对于输入流,文件一定要存在
fw = new FileWriter(destFile);// 文件可以不存在
// 3.读入内存和写出硬盘操作
// 一次读多个先创建一个数组
char[] cbuf = new char[5];
int len;// 记录每次读入到cbuf数组中字符的个数
// 循环读入: fr流对象读入到cbuf数组返回读入了几个 如果不是-1就还有数据
while ((len = fr.read(cbuf)) != -1) {
// 把数据写出到硬盘当中
// 错误:若这样写 每次5个5个就把数据读出来了
// fw.write(cbuf);
// 正确: 读进去几个就写出几个
fw.write(cbuf, 0, len);// 每次从0开始写出len个字符
// 循环结束返回-1
}
} catch (IOException e) {
e.printStackTrace();
}finally {
// 4.关闭流资源,建议从下往上关
// 两个操作并列写就可以了(也可以用finally),因为就算中途出现异常也已经catch掉了,不影响try以外的代码运行,try里面代码出现异常,内部接着往下的才不执行
try {
if (fw != null)
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fr != null)
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
字符流不能处理图片文件的测试
用FileInputStream不能读取文本文件的测试
用FileInputStream和FileOutputStream读写非文本文件
用FileInputStream和FileOutputStream复制文件的测试
13.4.缓冲流
- 缓冲流(字节型)实现非文本文件的复制
13.5.转换流
转换流概述与InputStreamReader的使用
转换流实现文件的读入和写出
13.6.标准输入输出流
13.7.打印流
13.8.数据流
13.9.对象流
- 对象流序列化与反序列化字符串操作
对象流的使用
1.ObjectInputStream 和 ObjectOutputStream
2.作用:用于存储和读取基本数据类型数据或对象的处理流。他的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来
3.要想一个java对象是可序列化的,需要满足相应的要求。见Person.java
public class ObjectInputOutputStreamTest {
/*
序列化过程:将内存中的java对象保存到磁盘中或通过网络传输出去
使用ObjectOutPutStream输出实现
*/
@Test
public void testObjectOutputStream(){
ObjectOutputStream oos = null;
try {
// 1.造一个对象处理流包住一个节点流,造文件和流写在一起
oos = new ObjectOutputStream(new FileOutputStream("object.dat"));
// 2.写出到磁盘的对象
// 一开始内存当中有个对象就是这个String,随时可能会被回收,就把他持久化保存起来,
// 或者可以不生成文件,通过网络把它传出去也行,传出去以后再另外一端又可以把它还原成字符串
oos.writeObject(new String("我爱天安门"));
// 显式的刷新操作
oos.flush();
oos.writeObject(new Person("王敏", 23));// 被持久化到Object.dat文件里
oos.flush();
// 序列化自定义类,该类的属性也全都要可序列化
oos.writeObject(new Person("张帅", 21,1001,new Account(5000)));
oos.flush();
} catch (IOException e) {
e.printStackTrace();
}finally {
if (oos != null) {
// 3.流关闭
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/*
反序列化过程: 将磁盘文件中的对象还原为内存中的一个java对象,或通过网络方式获取到流,把网络中的流的数据还原为java层面的对象
使用ObjectInputStream来实现
*/
@Test
public void testInputStream(){
ObjectInputStream ois = null;
try {
// 1.造文件和流对象
ois = new ObjectInputStream(new FileInputStream("object.dat"));
// 2.写出磁盘操作,一般写入的都是同一个类型的
Object obj = ois.readObject();// 通常往里放的都是同类型的对象
String str = (String) obj;// 这个对象其实是个字符串所以可以强转
Person p = (Person) ois.readObject();
Person p1 = (Person) ois.readObject();
// 还原为内存层面
System.out.println(str);
System.out.println(p);
System.out.println(p1);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}finally {
// 3.流关闭
try {
if (ois != null)
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
Person类要满足如下的需求,方可序列化
1.要实现标识接口: Serializable
2.当前类要提供一个全局常量: serialVersionUID(版本序列号)
如果不添加这个全局常量,给这个类的对象序列化了,存储在磁盘中,紧接着对Person类修改了,修改后,自动生成的serialVersionUID就变了,
变了再去还原的话就找不到当初序列化的全局常量对应的Person,在还原时候就出问题了(报异常)
3.除了当前Person类需要实现Serializable接口之外,还必须保证其内部所有属性也必须是可序列化的。(默认情况下,基本数据类型是可序列化的)
补充: ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量
static属于类的资源不归对象所有所以不能序列化,transient是瞬态的
transient:被修饰的成员属性变量不能被序列化
4.对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,
从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。
当其它程序获取了这种二进制流,就可以恢复成原来的Java对象
public class Person implements Serializable {
public static final long serialVersionUID = 451615182L;
private static String name;
private transient int age;
private int id;
private Account acct;// 自定义类属性也要是可序列化的,
public Person(String name, int age, int id, Account acct) {
this.name = name;
this.age = age;
this.id = id;
this.acct = acct;
}
public Person(String name, int age, Account acct) {
this.name = name;
this.age = age;
this.acct = acct;
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", acct=" + acct +
'}';
}
}
class Account implements Serializable{
// 加上唯一的全局静态常量避免反序列化报异常
public static final long serialVersionUID = 4515182L;
private double balance;
public Account(double balance) {
this.balance = balance;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
@Override
public String toString() {
return "Account{" +
"balance=" + balance +
'}';
}
}
13.10.随机存取文件流
RandomAccessFile的使用
1.RandomAccessFile直接继承于java.Lang.Object类,实现了DataInput和DataOutput接口
2.RandomAccessFile既可以作为一个输入流,又可以作为一个输出流
3.如果RandomAccessFile作为输出流时,写出到的文件若不存在,则在执行过程中自动创建
若写出到的文件存在,则会对原有文件内容进行覆盖.(默认情况下,从头覆盖,能覆盖多少算多少)
4.可以通过把指定指针位置后的内容缓存起来,然后再在指定指针位置插入新值,再把缓存的内容追加回原来的文件,实现RandomAccessFile"插入"数据的效果
public class RandomAccessFileTest {
@Test
public void test11() throws FileNotFoundException {
RandomAccessFile raf1 = null;
RandomAccessFile raf2 = null;
try {
// 1.造流的对象
raf1 = new RandomAccessFile(new File("pic.jpg"), "r");// 只读模式
raf2 = new RandomAccessFile(new File("pic3.jpg"), "rw");// 可读可写模式
// 2.读入写出操作
byte[] buffer = new byte[1024];
int len;
while ((len = raf1.read(buffer)) != -1) {
raf2.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}
try {
// 3.流关闭
if (raf2 != null) {
raf2.close();
}
if (raf1 != null) {
raf1.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void test2() throws IOException {
// 创建写出流的对象和文件对象
RandomAccessFile raf1 = new RandomAccessFile("hello4.txt", "rw");
// 写出操作
raf1.seek(3);// 将指针调到角标为3的位置
raf1.write("xyz".getBytes());// 覆盖掉原来文件里的头三个字符
// 流关闭
raf1.close();
}
/*
用RandomAccessFile实现数据的插入效果
*/
@Test
public void test3() throws IOException {
RandomAccessFile raf1 = new RandomAccessFile("hello4.txt", "rw");
raf1.seek(3);// 将指针调到下标为3的位置
// 将读取的数据存入一个数组,将数组长度先设置为文件长度的大小避免扩容,提高效率,length()返回一个long类型的值
// 保存指针角标3后面的所有数据到StringBuilder中
StringBuilder builder = new StringBuilder((int) new File("hello4.txt").length());//新造数组的大小要么比原文件小,要么和源文件一样大
// 读取操作
byte[] buffer = new byte[20];
int len;
while ((len = raf1.read(buffer)) != -1) {
// 把每次遍历的字符放入builder数组中
builder.append(new String(buffer, 0, len));
}
// 此时指针自动跑到最后了,将指针调回角标为3的位置
raf1.seek(3);
// 插入"xyz"的数据,相当于对原有内容进行覆盖
raf1.write("xyz".getBytes());// 这里write返回byte[]类型,所以要用getBytes()
// 写出后不用再调指针了,指针已经在z的后面了
// 将builder中的数据写入到文件中
raf1.write(builder.toString().getBytes());
// 关闭流
raf1.close();
}
}
13.11.NIO.2中Path,Paths,Files类的使用
- 用第三方jar包实现数据读写
14.1.网络编程概述
14.2.网络通信要素概述
14.3.通信要素1: IP和端口号
- 端口号的理解
public class InetAddressTest {
public static void main(String[] args) {
// 创建一个对象,其对应的ip就是方法的参数
// 类似于File file = new File("hello.txt");
// 内存中的file对象对应着hello.txt路径的文件
try {
InetAddress inet1 = InetAddress.getByName("192.168.10.14");// 参数host代表主机名
System.out.println(inet1);
InetAddress inet2 = InetAddress.getByName("www.taobao.com");
System.out.println(inet2);
InetAddress inet3 = InetAddress.getByName("127.0.0.1");
System.out.println(inet3);
// 直接获取本地IP地址的方法
InetAddress inet4 = InetAddress.getLocalHost();
System.out.println(inet4);
// getHostName(): 获取域名
System.out.println(inet2.getHostName());
// getHostAddress(): 获取主机地址
System.out.println(inet2.getHostAddress());
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
}
14.4.通信要素2: 网络协议
- TCP和UDP网络通信协议的对比
- 三次握手: 应该这样理解:1.A发送请求 2.B告诉A已经收到请求(ACK确认)3.A确认B收到了A的请求 4.A开始数据传输
14.5.TCP网络编程
- TCP网络编程例题:
14.6.udp网络编程
- UDP编程举例