IO流(三) File类
一、概述
File类用于将文件或文件夹封装成对象,方便对文件和文件夹的属性信息进行操作。该类可以作为参数传递给IO流的构造函数,弥补流对象在操作文件和文件夹上的缺陷。
二、File类的使用
1.构造方法
1)File(String FileName)
示例:File f1 = new File("C:\\abc\\a.txt");
2)File(Strng, parent, String FileName)
示例:File f2 = new File("C:\\abc", "b.txt");
该构造方法的好处在于对文件的操作更加灵活,出现文件目录固定,文件名需要改变的时候,这个构造方法更好
3)File(File parent, String FileName)
示例:File d = new File("C:\\abc");
File f3 = new File(d, "c.txt");
示例代码:
package com.heisejiuhuche.io;
import java.io.File;
public class FileDemo {
public static void main(String[] args) {
/* 使用File类的不同构造方法封装文件对象 */
File f1 = new File("Demo1.txt");
File f2 = new File("C:/Users/jeremy/Documents/javaTmp/", "Demo2.txt");
File d = new File("C:/Users/jeremy/Documents/javaTmp/");
File f3 = new File(d, "Demo3.txt");
/* 打印各个文件对象 */
System.out.println("f1:" + f1);
System.out.println("f2:" + f2);
System.out.println("f3:" + f3);
}
}
程序输出结果:
f1:Demo1.txt
f2:C:\Users\jeremy\Documents\javaTmp\Demo2.txt
f3:C:\Users\jeremy\Documents\javaTmp\Demo3.txt
打印封装文件对象时的绝对路径或相对路径。
2.成员方法
1)创建操作
-boolean createNewFile():文件名不存在的情况下创建新文件
-boolean mkdir():创建一级目录
-boolean mkdirs():创建多级目录
2)删除操作
-boolean delete():删除指定文件
-void deleteOnExit():虚拟机退出的时候删除指定文件
3)判断操作
-boolean canExecute():判断文件对象是否可执行
-boolean canRead():判断文件对象是否可读
-boolean canWrite():判断文件对象是否可写
-boolean exists():判断文件对象是否存在
-boolean isDirectory():判断文件对象是否是文件夹,判断之前,必须先判断该文件对象是否存在
-boolean isFile():判断文件对象是否是文件,判断之前,必须先判断该文件对象是否存在
-boolean isHidden():判断文件对象是否是隐藏文件
-boolean isAbsolute():判断文件对象路径是否是绝对路径
-int compareTo(File pathname):比较两个文件对象,以自然顺序排序
4)获取操作
-String getName():获取文件对象名
-String getParent():获取文件对象的父母路,必须明确指定文件路径
-String getPath():获取文件对象相对路径
-String getAbsolutePath():获取文件对象绝对路径,文件可以存在也可以不存在
-long lastModified():获取文件对象最近一次被修改的时间
-long length():获取文件对象大小
5)修改操作
-boolean renameTo(File dest):将文件修改为指定文件名并存入指定目录
6)其他重要方法
-static File[] listRoots():获取有效盘符
-String[] list():获取指定目录中的文件和文件夹,包括隐藏文件;必须是存在目录调用,文件调用会空指针
-String[] list(FilenameFileter filter):获取指定格式的文件
-File[] listFiles()
-File[] listFiles(FileFilter filter)
-File[] listFiles(FilenameFilter filter)
3.递归
用递归遍历文件夹里的所有内容并以层级结构打印。
示例代码:
package com.heisejiuhuche.io;
import java.io.File;
public class RecursionDemo {
public static void main(String[] args) {
File dir = new File("C:/Users/jeremy/Documents/javaTmp/testfile");
recur(dir, 0);
}
/* 目录层级结构 */
private static String getLevel(int level) {
StringBuilder sb = new StringBuilder();
/* 树形结构图形 */
sb.append("|--");
for(int x= 0; x < level; x++) {
/* 根据层级的不同在树形结构图形前补上制表符 */
sb.insert(0, "\t");
}
return sb.toString();
}
/* 递归遍历文件夹,并打印所有文件和文件夹 */
private static void recur(File dir, int level) {
/* 列出所有文件到文件数组 */
File[] files = dir.listFiles();
/* 打印文件夹的名称 */
System.out.println(getLevel(level) + dir.getName());
/* 打印完文件夹的名称,层级自增1 */
level++;
/* 遍历所有文件,如果是文件夹,则递归遍历里面的文件和文件夹 */
for(int x = 0; x < files.length; x++) {
if(files[x].isDirectory()) {
recur(files[x], level);
}
else
System.out.println(getLevel(level) + files[x].getName()); //如果不是文件夹,打印文件
}
}
}
程序输出结果:
|--testfile
|--aaa
|--ccc
|--ddd
|--h.txt
|--ddd - Copy (2).txt
|--ddd - Copy - Copy.txt
|--ddd - Copy.txt
|--ddd.txt
|--ccc - Copy (2).txt
|--ccc - Copy - Copy.txt
|--ccc - Copy.txt
|--ccc.txt
|--aaa - Copy (2).txt
|--aaa - Copy - Copy.txt
|--aaa - Copy.txt
|--aaa.txt
|--bbb
|--eee
|--f.txt
|--eee - Copy (2).txt
|--eee - Copy - Copy.txt
|--eee - Copy.txt
|--eee.txt
注意:
递归的使用必须要明确控制条件,便面无限循环;递归要慎重使用,调用次数过多,会造成内存溢出。
4.删除带内容目录
示例代码:
package com.heisejiuhuche.io;
import java.io.File;
public class DelDirWithContentDemo {
public static void main(String[] args) {
File dir = new File("C:/Users/jeremy/Documents/javaTmp/testfile");
recur(dir);
}
/* 遍历所有文件夹,删除文件,并删除文件夹 */
private static void recur(File dir) {
File[] files = dir.listFiles();
for(int x = 0; x < files.length; x++) {
if(files[x].isDirectory())
recur(files[x]);
else
/* 打印文件名及文件删除结果 */
System.out.println(files[x].getName() + "--files--" + files[x].delete());
}
/* 打印文件夹名及文件夹删除结果 */
System.out.println(dir.getName() + "**dir**" + dir.delete());
}
}
程序输出结果:
demo - Copy (2) - Copy.txt--files--true
ddd**dir**true
demo - Copy (2) - Copy - Copy - Copy.txt--files--true
demo - Copy (2) - Copy - Copy.txt--files--true
ccc**dir**true
demo - Copy (2) - Copy - Copy.txt--files--true
demo - Copy (2) - Copy.txt--files--true
demo - Copy (2).txt--files--true
demo - Copy (3) - Copy.txt--files--true
aaa**dir**true
demo - Copy - Copy - Copy.txt--files--true
demo - Copy - Copy.txt--files--true
demo - Copy - Copy - Copy.txt--files--true
eee**dir**true
bbb**dir**true
demo - Copy (2) - Copy.txt--files--true
demo - Copy (2).txt--files--true
demo - Copy - Copy (2).txt--files--true
demo - Copy - Copy (3).txt--files--true
demo - Copy - Copy - Copy.txt--files--true
demo - Copy - Copy.txt--files--true
demo - Copy.txt--files--true
demo.txt--files--true
testfile**dir**true
注意:
Java无法访问windows中的隐藏目录,所以,最好在for循环中判断的时候加上:
if(!files[x].isHidden() && files[x].isDirectory())
忽略隐藏的文件夹
5.练习FileWriter
接收File
的构造方法。
示例代码:
package com.heisejiuhuche.io;
/**
* 接收键盘录入,然后写入指定目录下的文件中
* 为了测试FileWriter接收File类型数据作为构造方法参数
*/
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
public class FileWriterTest {
public static void main(String[] args) {
try {
writeToFile(createFile("C:\\Users\\jeremy\\Documents\\FileWriter.txt"));
} catch(IOException e) {
e.printStackTrace();
}
}
private static File createFile(String filepath) throws IOException {
File file = new File(filepath);
if(!file.exists())
file.createNewFile();
return file;
}
private static void writeToFile(File file) {
BufferedReader bufr = null;
BufferedWriter bufw = null;
try {
bufr = new BufferedReader(new InputStreamReader(System.in));
bufw = new BufferedWriter(new FileWriter(file));
String line = null;
while((line = bufr.readLine()) != null) {
if(line.equals("over"))
break;
bufw.write(line);
bufw.newLine();
bufw.flush();
}
} catch(IOException e) {
e.printStackTrace();
} finally {
try {
if(bufr != null)
bufr.close();
} catch(IOException e) {
e.printStackTrace();
}
try {
if(bufw != null)
bufw.close();
} catch(IOException e) {
e.printStackTrace();
}
}
}
}
其他类及功能性流对象
一、Properties类
1.概述Properties
类是HashTable
的子类。该类具备Map
集合的特点,储存字符串作为键值对。Properties
类是集合中和IO技术相结合的容器。该类可以用于键值对形式的配置文件。配置文件用于保存软件运行时的各项参数。配置完成之后将会被持久化存储,每次运行软件都会加载该配置文件。
2.应用
1)设置并打印键值对
示例代码:
package com.heisejiuhuche.io;
import java.util.Properties;
import java.util.Set;
public class PropertiesDemo2 {
public static void main(String[] args) {
/* 创建Property对象 */
Properties prop = new Properties();
/* 设置键值对 */
prop.setProperty("zhangsan", "18");
prop.setProperty("wangwu", "20");
/* 通过键获取值,并打印 */
System.out.println(prop.getProperty("zhangsan"));
/* 将键都返回并装进Set */
Set<String> value = prop.stringPropertyNames();
/* 遍历集合并打印键值 */
for(String str : value) {
System.out.println(str + "::" + prop.getProperty(str));
}
}
}
程序输出结果:
18
zhangsan::18
wangwu::20
2)从文件加载配置并修改
示例代码:
package com.heisejiuhuche.io;
import java.io.BufferedReader;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Properties;
public class PropertiesLoadDemo {
public static void main(String[] args) throws IOException {
Properties prop = new Properties();
// loadProp();
/* 创建缓冲输入流对象并关联配置文件 */
BufferedReader bufr = new BufferedReader(new FileReader(
"C:/Users/jeremy/Documents/javaTmp/info.txt"));
/* 调用prop独享的load方法加载配置文件 */
prop.load(bufr);
/* 在控制台列出所有键值对 */
prop.list(System.out);
/* 修改键值对 */
prop.setProperty("李四", "50");
/* 调用store方法修改配置文件并保存 */
prop.store(new OutputStreamWriter(new FileOutputStream(
"C:/Users/jeremy/Documents/javaTmp/info.txt"), "GBK"), "Test");
prop.list(System.out);
}
/* 方法一:读取文件,将每一行按=号分割,将键值对存入Properties对象 */
private static void loadProp() {
BufferedReader bufr = null;
Properties prop = null;
try {
bufr = new BufferedReader(new FileReader("C:/Users/jeremy/Documents/javaTmp/info.txt"));
prop = new Properties();
String line = null;
while((line = bufr.readLine()) != null) {
String[] kv = line.split("=");
prop.setProperty(kv[0], kv[1]);
}
} catch(IOException e) {
e.printStackTrace();
} finally {
try {
if(bufr != null)
bufr.close();
} catch(IOException e) {
e.printStackTrace();
}
}
System.out.println(prop);
}
}
程序输出结果:
-- listing properties --
王五=19
张三=20
李四=90
-- listing properties --
王五=19
张三=20
李四=50
注意:
-#都是注释,不会被Properties加载
-Properties加载的配置文件必须有固定格式;通常为:键=值
3)记录程序运行次数
创建配置文件记录程序运行次数,到达5
此,提示用户注册。
示例代码:
package com.heisejiuhuche.io;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Properties;
public class PropertiesTest {
public static void main(String[] args) throws IOException {
try {
checkAuth();
} catch(IOException e) {
throw new RuntimeException("配置文件加载异常...");
}
}
private static void checkAuth() throws IOException {
/* 创建配置文件对象 */
File file = new File("C:/Users/jeremy/Documents/javaTmp/auth.txt");
/* 如果文件不存在,创建文件,避免异常 */
if(!file.exists())
file.createNewFile();
/* 创建输出流对象 */
FileInputStream fis = new FileInputStream(file);
/* 创建Properties对象 */
Properties prop = new Properties();
/* 加载配置文件 */
prop.load(fis);
String val = null;
int count = 0;
/* 如果读取键值为空,说名是第一次运行,那么将计数器count+1,然后和键time一起存入prop对象
* 并写入配置文件
*/
if((val = prop.getProperty("time")) != null) {
/* 如果取到了值,说明不是第一次运行,那么让计数器count等于time键所对应的值
* 再+1,然后和time键一起再次存入配置文件
*/
count = Integer.parseInt(val);
/* 判断,如果count = 5,说明使用次数已到,结束程序,提示注册 */
if(count >= 5) {
System.out.println("请注册...");
return;
}
}
count++;
prop.setProperty("time", count + "");
/* 输出流不能在load语句前声明,否则将会覆盖配置文件,导致配置信息清空 */
FileOutputStream fos = new FileOutputStream(file);
/* 将配置文件更新 */
prop.store(fos, "CheckAuth");
fis.close();
fos.close();
}
}
二、打印流
1.PrintStream类
1)概述PrintStream
类是字节打印流,其为其他流添加了功能,使它们能够方便打印各种数据值形式。该类不会抛出IOException
,同时内部有刷新机制,无须手动刷新缓冲区。PrintStream
可以直接操作文件对象。
2)常用方法
-PrintStream(File file):接收File对象
-PrintStream(OutputStream out):接收字节输出流
-PrintStream(String filename):接收字符串路径
-println():打印所有基本数据类型,保持数据原样
3)PrintStream示例
示例代码:
package com.heisejiuhuche.io;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
public class PrintStreamDemo {
public static void main(String[] args) throws IOException {
/* 创建输入流和PrintStream对象并关联文件 */
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
PrintStream ps = new PrintStream("C:/Users/jeremy/Documents/javaTmp/PrintStream.txt");
String len = null;
/* 接收键盘输入并写入文件 */
while((len = bufr.readLine()) != null) {
if(len.equals("over"))
break;
ps.println(len);
ps.flush();
}
bufr.close();
ps.close();
}
}
该类的使用方法和下面介绍的PrintWriter
类基本相同。
2.PrintWriter类
1)概述
字符打印流可以打印基本舒蕾型。其最强大的功能是println
方法,可以实现自动换行并直接操作基本数据类型。
2)常用方法
-PrintWriter(File file):接收File对象
-PrintWriter(File file, String csn):接收File对象,并制定字符集
-PrintWriter(OutputStream out):接收字节输出流
-PrintWriter(OutputStream out, boolean autoflush):接收字节输出流,设置自动刷新
-PrintStream(Writer out):接收字符输出流
-PrintWriter(String filename):接收字符串路径
3)PrintWriter示例
示例代码:
package com.heisejiuhuche.io;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
public class PrintWriterDemo {
public static void main(String[] args) throws IOException {
/* 创建输入流对象和打印流对象 */
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
PrintWriter pw = new PrintWriter(System.out, true);
String len = null;
while((len = bufr.readLine()) != null) {
/* 如果len等于over,就停止程序 */
if(len.equals("over"))
break;
/* 调用println方法,在打印的时候自动换行 */
pw.println(len.toUpperCase());
}
bufr.close();
pw.close();
}
}
注意:
在PrintWriter构造方法中设置true,就无须调用flush()刷新缓冲。
三、序列流
1.SequenceInputStream类
1)概述SequenceInputStream
类标识其他输入流的逻辑串联。它从输入流的有序集合开始,并从第一个输入流开始读取,直到达到文件末尾;接着从第二个输入流读取,以此类推,直到到达包含的最后一个输入流的文件末尾为止。
2)应用
将三个文件的内容使用SequenceInputStream
写入到一个文件中。
示例代码:
package com.heisejiuhuche.io;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.SequenceInputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
public class SequenceInputStreamDemo {
public static void main(String[] args) {
write();
}
private static void write() {
SequenceInputStream sis = null;
ArrayList<FileInputStream> list = null;
FileOutputStream fos = null;
try {
/* 创建集合对象 */
list = new ArrayList<FileInputStream>();
/* 将输入流对象存入集合 */
for(int x = 1; x <= 3; x++) {
list.add(new FileInputStream(
"C:\\Users\\jeremy\\Documents\\javaTmp\\mp\\" + x + ".txt"));
}
Iterator<FileInputStream> it = list.iterator();
/* 得到装有输入流对象的Enumeration */
Enumeration<FileInputStream> en = new Enumeration<FileInputStream>() {
public boolean hasMoreElements() {
return it.hasNext();
}
public FileInputStream nextElement() {
return it.next();
}
};
/* 创建序列流对象和输出流对象 */
sis = new SequenceInputStream(en);
fos = new FileOutputStream("C:\\Users\\jeremy\\Documents\\javaTmp\\mp\\4.txt");
byte[] buf = new byte[1024];
int len = 0;
/* 合并文件 */
while((len = sis.read(buf)) != -1) {
fos.write(buf, 0, len);
fos.flush();
}
} catch(IOException e) {
e.printStackTrace();
} finally {
try {
if(sis != null)
sis.close();
} catch(IOException e) {
e.printStackTrace();
}
try {
if(fos != null)
fos.close();
} catch(IOException e) {
e.printStackTrace();
}
}
}
}
3)切割文件后再合并
将一个MP3
文件按1M
切割,最后合并,要求保证合并后的文件可以播放。
示例代码:
package com.heisejiuhuche.io;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.SequenceInputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
public class SplitMp3Test {
public static void main(String[] args) {
split();
merge();
}
private static void merge() {
/* 创建集合,用于存放输入流对象 */
ArrayList<FileInputStream> list = new ArrayList<FileInputStream>();
SequenceInputStream sis = null;
FileOutputStream fos = null;
try {
/* 将四个文件输入流对象存入集合 */
for(int x = 1; x <= 4; x++) {
list.add(new FileInputStream("C:\\Users\\jeremy\\Documents\\javaTmp\\mp\\" + x + ".part"));
}
Iterator<FileInputStream> it = list.iterator();
/* 拿到有输入流对象的Enumeration */
Enumeration<FileInputStream> en = new Enumeration<FileInputStream>() {
public boolean hasMoreElements() {
return it.hasNext();
}
public FileInputStream nextElement() {
return it.next();
}
};
/* 创建序列流对象 */
sis = new SequenceInputStream(en);
/* 创建输出流对象,并关联文件 */
fos = new FileOutputStream("C:\\Users\\jeremy\\Documents\\javaTmp\\mp\\back.mp3");
byte[] buf = new byte[1024];
int len = 0;
/* 合并文件 */
while((len = sis.read(buf)) != -1) {
fos.write(buf, 0, len);
fos.flush();
}
} catch(FileNotFoundException e) {
throw new RuntimeException("文件不存在...");
} catch(IOException e) {
throw new RuntimeException("合并失败...");
} finally {
try {
if(sis != null)
sis.close();
} catch(IOException e) {
throw new RuntimeException("资源关闭失败...");
}
try {
if(fos != null)
fos.close();
} catch(IOException e) {
throw new RuntimeException("资源关闭失败...");
}
}
}
private static void split() {
File file = null;
FileInputStream fis = null;
FileOutputStream fos = null;
try {
/* 创建输入流对象 */
file = new File("C:\\Users\\jeremy\\Documents\\javaTmp\\Backseat.mp3");
fis = new FileInputStream(file);
byte[] buf = new byte[1024 * 1024];
int count = 1;
int len = 0;
/* 将文件按1M大小分割,分为count个文件 */
while((len = fis.read(buf)) != -1) {
fos = new FileOutputStream("C:\\Users\\jeremy\\Documents\\javaTmp\\mp\\" + count++ + ".part");
fos.write(buf, 0, len);
fos.flush();
}
} catch(IOException e) {
throw new RuntimeException("分割失败...");
} finally {
try {
if(fis != null)
fis.close();
} catch(IOException e) {
throw new RuntimeException("输入资源关闭失败");
}
try {
if(fos != null)
fos.close();
} catch(IOException e) {
throw new RuntimeException("输出资源关闭失败");
}
}
}
}
四、对象流
1.ObjectOutputStream和ObjectInputStream类
1)概述ObjectOutputStream
类可以将Java对象的基本数据类型和图形写入OutputStream
,再利用ObjectInputStream
读取(重构)该对象。该类用于将对象持久化存储在硬盘上,以便程序下次启动的时候能再次加载该对象。
2)常用方法ObjectOutputStream
具备直接操作基本数据类型的一些列write()
方法及操作对象的writeObject()
方法。以一个示例来演示该类的使用方法及注意事项。
示例代码:
package com.heisejiuhuche.io;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class ObjectOutputInputStreamDemo {
static final File file = new File("C:/Users/jeremy/Documents/javaTmp/person.txt");
public static void main(String[] args) throws Exception {
writeObj();
}
private static void readObj() throws Exception {
/* 创建对象输入流对象,传入一个字节输入流,并关联文件 */
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
/* 读取Person对象,向下转型为Person类型 */
Person p = (Person)ois.readObject();
/* 打印Person对象 */
System.out.println(p);
/* 关闭资源 */
ois.close();
}
private static void writeObj() throws Exception {
/* 创建对象输出流对象,传入一个字节输出流,并关联文件 */
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
/* 调用writeObject方法将Person对象写入文件 */
oos.writeObject(new Person("zhangsan", 28));
/* 写入文件后读取并打印在控制台 */
readObj();
/* 关闭资源 */
oos.close();
}
}
class Person {
private String name;
private int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return this.name + "::" + this.age;
}
}
注意:
上面的代码报出异常:
Exception in thread "main" java.io.NotSerializableException: com.heisejiuhuche.io.Person
在使用ObjectOutputStream写入对象的时候,被写入的对象必须实现Serializable接口
3)Serializable接口Serializable
接口是一个标记接口,它没有任何方法。该接口给对象定义一个固定的数字标识,将该对象类序列化。使用该标识作为对象存储和读取是否一致的判断标准。这个数字标识叫做SerialVersionUID
,是根据每个对象的成员,由Java计算出来的一个固定值。如果读取的时候,该值发生了变化,会抛出异常。下面会用代码演示这一现象及序列化的其他特点。
修改上述代码,按要求使Person
类实现Serializable
接口。
示例代码:
class Person implements Serializable {
private String name;
private int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return this.name + "::" + this.age;
}
}
程序运行结果:
zhangsan::28
此时,如果将Person
类中name
成员的private
关键字去掉,在写入和读取时,就会发生UID
不匹配的情况,抛出异常。
示例代码:
class Person implements Serializable {
/* 去掉了private关键字 */
String name;
private int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return this.name + "::" + this.age;
}
}
程序运行结果:
Exception in thread "main" java.io.InvalidClassException
这证明了UID是根据成员的数字标识算出来的一个固定值。
如果要实现更改成员之后还能读取该类,只需手动指定UID
。下面的代码仍然去掉了private
关键字,但是读取无误。
示例代码:
package com.heisejiuhuche.io;
import java.io.Serializable;
class Person implements Serializable {
/* 手动指定UID */
private static final long serialVersionUID = 37L;
String name;
private int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return this.name + "::" + this.age;
}
}
程序运行结果:zhangsan::28
注意:
-已被赋值的静态成员,在序列化后,值不能修改;如,static String country = "cn";之后再修改country的值,读取的时候,还是"cn";
-不想序列化某非静态成员,可以加上transient关键字:transient private int age;
五、管道流
1.概述
管道流能通过结合线程技术,实现输入流和输出流的直接连接。管道输入和输出流必须连接在一起使用。某个线程从PipedInputStream
读取数据,并由另一个线程写入到PipedOutputStream
。这是涉及多线程技术的IO流
。
2.PipedInputStream和PipedOutputStream类
管道流可以通过构造方法连接,也可以通过调用connect()
方法连接。
示例代码:
package com.heisejiuhuche.io;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
public class PipedStreamDemo {
public static void main(String[] args) throws IOException {
/* 创建管道流对象并连接 */
PipedInputStream pis = new PipedInputStream();
PipedOutputStream pos = new PipedOutputStream();
pis.connect(pos);
/* 启动线程 */
new Thread(new Read(pis)).start();
new Thread(new Write(pos)).start();
}
}
class Read implements Runnable {
private PipedInputStream pis;
Read(PipedInputStream pis) {
this.pis = pis;
}
public void run() {
/* 等待管道输出流写入数据,读到输出流数据前,处于阻塞状态 */
try {
byte[] buf = new byte[1024];
System.out.println("等待读取...");
int len = pis.read(buf);
System.out.println("读取结束...");
System.out.println(new String(buf, 0, len));
} catch(IOException e) {
throw new RuntimeException("读取失败");
}
}
}
class Write implements Runnable {
private PipedOutputStream pos;
Write(PipedOutputStream pos) {
this.pos = pos;
}
public void run() {
/* 往管道输入流中写入数据,写入先等待6秒,模拟写入过程 */
try {
System.out.println("数据写入中...");
Thread.sleep(6000);
pos.write("数据来啦!!!!!".getBytes());
pos.close();
} catch(Exception e) {
throw new RuntimeException("写入失败");
}
}
}
程序运行结果:
等待读取...
数据写入中...
读取结束...
数据来啦!!!!!
六、RandomAccessFile类
1.概述RandomAccessFile
类不是IO体系中的子类,而是直接继承自Object
。但是它仍然是IO包
中的成员,因为它具备读写功能。该类支持对随机访问文件的读取和写入。该类的内部封装了数组,通过文件指针对数据进行操作,可以通过getFilePointer()
方法获取指针位置;通过seek()
方法改变指针的位置。RandomAccessFile
类只能操作文件,并必须设定操作模式。
2.应用
演示RandomAccessFile
的基本方法及特点。
示例代码:
package com.heisejiuhuche.io;
import java.io.IOException;
import java.io.RandomAccessFile;
public class RandomAccessFileDemo {
public static void main(String[] args) throws IOException {
write();
read();
}
private static void write() throws IOException {
/* 创建RnadomAccessFile对象 */
RandomAccessFile raf = new RandomAccessFile("C:/Users/jeremy/Documents/javaTmp/random.txt", "rw");
raf.write("李四".getBytes());
/* write()方法的特点是,只写出数据的最低8位,会造成数据丢失,出现乱码 */
// raf.write(258);
/* 保证数据不丢失,要调用writeInt()方法,写入4个8位 */
raf.writeInt(97);
raf.write("王五".getBytes());
raf.writeInt(99);
/* 让指针回到文件起始位置 */
raf.seek(0);
/* 写入数据,那么李四的数据将被修改 */
raf.write("赵六".getBytes());
raf.writeInt(103);
raf.close();
}
private static void read() throws IOException {
RandomAccessFile raf = new RandomAccessFile("C:/Users/jeremy/Documents/javaTmp/random.txt", "rw");
/* 创建4个字节的数组 */
byte[] buf = new byte[4];
/* 如果想要直接取王五的数据,调用seek方法让指针指向第二个8位即可 */
raf.seek(8);
/* 如果想要直接取王五的数据,调用skepBytes方法跳过前8位 */
raf.skipBytes(8);
/* 读取4个字节到数组中 */
raf.read(buf);
/* 用readInt读取下4个字节 */
int age = raf.readInt();
System.out.println(new String(buf) + age);
}
}
注意:
-write()方法只写数据的最低8位,会造成数据丢失;在写入int类型数据时,调用writeInt()方法;
-skipBytes()方法只能往前跳过指定字节数,不能往回跳转;
-RandomAccessFile对象不仅能写如数据,还能修改该数据;
-RandomAccessFile对象在创建时,如果模式为"r",就不会创建文件;如果该被读取文件不存在,会抛出异常;模式为"rw",会自动创建该文件;如果文件已存在,则不会覆盖,继续添加内容;
-RandomAccessFile类可以结合多线程,实现数据的分段写入,提高写入效率
七、其他流对象
1.DataInputStream和DataOutputStream类
用于操作基本数据类型的流对象。
示例代码:
package com.heisejiuhuche.io;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class DataStreamDemo {
public static void main(String[] args) throws IOException {
write();
read();
writeUTF();
readUTF();
}
private static void readUTF() throws IOException {
DataInputStream dos = new DataInputStream(new FileInputStream(
"C:/Users/jeremy/Documents/javaTmp/utf-8.txt"));
System.out.println(dos.readUTF());
}
private static void writeUTF() throws IOException {
DataOutputStream dos = new DataOutputStream(new FileOutputStream(
"C:/Users/jeremy/Documents/javaTmp/utf-8.txt"));
/* 使用UTF-8修改版字符集写入数据 */
dos.writeUTF("你好");
dos.close();
}
private static void read() throws IOException {
/* 创建DataInputStream对象并关联文件 */
DataInputStream dos = new DataInputStream(new FileInputStream(
"C:/Users/jeremy/Documents/javaTmp/data.txt"));
/* 读取基本数据类型 */
int num = dos.readInt();
boolean b = dos.readBoolean();
double d = dos.readDouble();
System.out.println("num = " + num);
System.out.println("b = " + b);
System.out.println("d = " + d);
}
private static void write() throws IOException {
/* 创建DataOutputStream对象并关联文件 */
DataOutputStream dos = new DataOutputStream(new FileOutputStream(
"C:/Users/jeremy/Documents/javaTmp/data.txt"));
/* 写入基本数据类型 */
dos.writeInt(234);
dos.writeBoolean(false);
dos.writeDouble(124.135252);
dos.close();
}
}
程序运行结果:
num = 234
b = false
d = 124.135252
你好
2.ByteArrayInputStream和ByteArrayOutputStream类
用于操作字节数组的流对象。该类的对象无须关闭,因为没有调用任何系统底层资源;如果关闭,仍可以调用其方法,同时没有任何IO异常
。ByteArrayInputStream
在初始化的时候需要接收一个字节数组;ByteArrayOutputStream
内部的缓冲区,会随着数据的不断写入而自动增长。这两个类用流的读写思想来操作数组,用于往内存中暂时写入数据。
示例代码:
package com.heisejiuhuche.io;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
public class ByteArrayStreamDemo {
public static void main(String[] args) {
readWrite();
}
private static void readWrite() {
/* 创建字节数组流对象并关联文件 */
ByteArrayInputStream bis = new ByteArrayInputStream("abcdefg".getBytes());
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int ch = 0;
/* 循环读取所有字节并写去字节数组输出流对象 */
while((ch = bis.read()) != -1) {
bos.write(ch);
}
/* 打印数组大小,并打印内容 */
System.out.println(bos.size());
System.out.println(new String(bos.toByteArray()));
System.out.println(bos.toString());
}
}
程序运行结果:
7
abcdefg
abcdefg
注意:
和字节数组输入输出流类似的流对象还有:
分类 | 输入 | 输出 |
---|---|---|
字符数组流 | CharArrayReader | CharArrayWriter |
字符串流 | StringReader | StringWriter |
这些类的使用方式和字节数组流相似。 |
字符编码
一、概述
字符编码表是由计算机二进制数1010
的排列组合和文字的对应关系形成的。它的出现可以让计算机识别各个国家不同的文字和符号。
二、常见字符编码表
1.ASCII码表
美国标准信息交换码,用一个字节的7位
标识一个字符,最高位为0
。
2.ISO8859-1
拉丁码表(欧洲码表),用一个字节的8位
标识一个字符。
3.GB2312和GBKGB2312
和GBK
都是中文字符编码表。后者是前者的升级版,囊括更多的中文文字和符号。
4.Unicode
国际标准码,融合了全世界多种文字和符号。
5.UTF-8Unicode
编码表的优化版,最多用3个
字节标识一个字符。
三、乱码问题
乱码问题的产生,原因在于解码的时候没有使用编码时的编码表。比如,在写入中文数据的时候,指定了GBK编码表,但是在读取数据的时候却用了UTF-8编码表。GBK是两个字节表示一个字符,UTF-8是三个字节表示一个字符。如果输入的字符是“你好”,对应的GBK编码表上的数字是【-60,-29,-70,-61】;读取的时候误用了UTF-8编码表,那么会读取【-60,-29,-70】这三个字节,到UTF-8码表里面寻找有没有对应的字符,如果没有,返回?;接着拿【-61】去找,如果没有匹配的字符,返回?。反过来的过程相同。“你好”对应的UTF-8的数字是【-28,-67,-96,-27,-91,-67】,如果在读取时误用了GBK码表,那么就拿每两个数字去GBK表中查找对应字符,因此出现乱码。
四、编码解码
1.定义
编码就是将字符串转换为字节数组的过程;解码就是将字节数组转换为字符串的过程。
2.解决乱码问题
实际开发过程中,web服务器
端通常默认使用ISO8859-1
的编码表。如果出现使用GBK
编码,而数据传到服务器端之后出现乱码的问题,只需要将乱码的字符串再次用ISO8859-1
进行编码,在用GBK
解码即可。
示例代码:
package com.heisejiuhuche.io;
public class EncodeDemo {
public static void main(String[] args) throws Exception {
String s1 = "你好";
/* 使用gbk编码 */
byte[] b1 = s1.getBytes("GBK");
/* 使用iso8859-1解码 */
String s2 = new String(b1, "ISO8859-1");
/* 出现乱码???? */
System.out.println(s2);
/* 是同iso8859-1再次编码 */
byte[] b2 = s2.getBytes("ISO8859-1");
/* 使用gbk解码 */
String s3 = new String(b2, "GBK");
/* 还原字符串成功 */
System.out.println(s3);
}
}
程序运行结果:
????
你好
五、联通的问题
在文本文件中输入“联通”二字,保存退出;第二次打开时会出现乱码。
图中可见“联通”二字的二进制储存形式,符合UTF-8
两个字节存储的格式要求;最高位分别为110
和10
。所以当“联通”二字以GBK
编码表形式编码存储后,记事本在读取的时候误认为是UTF-8
编码表编码,会到UTF-8
码表中查找字符,形成乱码。
六、练习
有5
个学生,每个学生有3
们课程的成绩。从键盘录入以上数据,格式为:姓名,数学成绩,语文成绩,英语成绩;并计算出总成绩。将最后的学生信息和计算出的总分按从高到低的顺序存入文件“stud.txt”
中。
示例代码:
package com.heisejiuhuche.io;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Collections;
import java.util.Comparator;
import java.util.TreeSet;
public class StudentInfoToFileTest {
public static void main(String[] args) {
/* 反转比较器 */
Comparator<Student> student = Collections.reverseOrder();
/* 获得学生对象并写入文件 */
StudentTools.infoToFile(StudentTools.getStudentInfo(student));
}
}
class StudentTools {
public static TreeSet<Student> getStudentInfo(Comparator<Student> comp) {
BufferedReader bufr = null;
try {
/* 创建TreeSet对象,用于储存学生对象并排序 */
TreeSet<Student> stuSet = null;
/* 接收键盘录入 */
bufr = new BufferedReader(new InputStreamReader(System.in));
/* 判断有无比较器参数,分别创建有比较器和没有比较器的两个TreeSet对象 */
if(comp == null)
stuSet = new TreeSet<Student>();
else
stuSet = new TreeSet<Student>(comp);
String tmp = null;
/* 不断接收键盘按固定格式的录入,如果收到over,结束程序 */
while((tmp = bufr.readLine()) != null) {
if(tmp.equals("over"))
break;
/* 将字符串按指定格式切割 */
String[] info = tmp.split(",");
/* 将每段信息传入学生对象封装 */
Student stu = new Student(info[0], Integer.parseInt(info[1]),
Integer.parseInt(info[2]),
Integer.parseInt(info[3]));
/* 将封装号的学生对象存入集合以便排序 */
stuSet.add(stu);
}
return stuSet;
} catch(IOException e) {
throw new RuntimeException("文件读入失败...");
} finally {
try {
if(bufr != null)
bufr.close();
} catch(IOException e) {
throw new RuntimeException("输入流关闭失败...");
}
}
}
public static void infoToFile(TreeSet<Student> stuSet) {
File file = new File("C:/Users/jeremy/Documents/javaTmp/StudentInfo.txt");
BufferedWriter bufw = null;
try {
bufw = new BufferedWriter(new FileWriter(file));
/* 将集合中的数据写入文件 */
for(Student stus : stuSet) {
bufw.write(stus.toString() + "\t");
bufw.write(stus.getSum() + "");
bufw.newLine();
bufw.flush();
}
} catch(IOException e) {
throw new RuntimeException("文件写入失败...");
} finally {
try {
if(bufw != null)
bufw.close();
} catch(IOException e) {
throw new RuntimeException("输入流关闭失败...");
}
}
}
}
/* 创建学生类 */
class Student implements Comparable<Student> {
private String name;
private int math, cn, en, sum;
Student(String name, int math, int cn, int en) {
this.name = name;
this.math = math;
this.cn = cn;
this.en = en;
sum = math + cn + en;
}
public void setName(String name) { this.name = name; }
public void setMath(int math) { this.math = math; }
public void setCn(int cn) { this.cn = cn; }
public void setEn(int en) { this.en = en; }
public String getName() { return name; }
public int getMath() { return math; }
public int getCn() { return cn; }
public int getEn() { return en; }
public int getSum() { return sum; }
/* 复写hashCode方法 */
public int hashCode() {
return this.name.hashCode() + this.sum * 37;
}
/* 复写equals方法 */
public boolean equals(Object obj) {
if(!(obj instanceof Student))
throw new ClassCastException("类型不匹配");
Student stu = (Student)obj;
return this.name.equals(stu.name) && this.sum == stu.sum;
}
/* 复写compareTo方法 */
public int compareTo(Student stu) {
int flag = new Integer(this.sum).compareTo(new Integer(stu.sum));
if(flag == 0)
return this.name.compareTo(stu.name);
return flag;
}
/* 复写toString方法 */
public String toString() {
return "Student[" + this.name + "," + math + "," + cn + "," + en + "]";
}
}
程序运行结果:
Student[zhouqi,70,70,70] 210
Student[zhaoliu,60,60,60] 180
Student[wangwu,50,50,50] 150
Student[lisi,40,40,40] 120
Student[zhangsan,30,30,30] 90