7.4 File类
File类是io包中唯一代表磁盘文件本身的对象,File类定义了一些与平台无关的方法来操作文件,可以通过调用File类中的方法,实现创建.删除.重命名文件等,File类是对象主要用来获取未文件本身的一些信息,如文件所在的目录,文件的长度.文件的读写权限等.数据流可以将数据系写入到文件中,而文件也是数据流最常用的数据媒体.
1、File类的作用
⑴、用来将文件或文件夹封装成对象
⑵、方便对文件或文件夹的属性信息进行操作
⑶、File对象可以用作参数传递给流的构造方法
2、构造方法:
⑴、File(Fileparent, String child):child:子路径字符串,parent:父路径字符串。据 parent 抽象路径名和 child 路径名字符串创建一个新 File 实例。
//"\\"在上面是目录分隔符,但在不同的系统平台上不一样,所以可以调用方法separator
File d =new File("D:"+File.separator+"workspace"+File.separator+"java就业培训教程");
File f3 =newFile(d,"file1.txt");
⑵、File(Stringpathname)pathname指包括文件路径(包文件名),通过将给定路径名字符串转换为抽象路径名来创建一个新 File 实例。
//将file.txt封装成file对象,可以将已有的和未出现的文件或文件夹封装成对象
File f =newFile("D:\\workspace\\java就业培训教程\\file1.txt");
⑶、File(String parent, String child)根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例。
//将文件和目录分开传递的好处:这样文件的位置
File f2 = newFile("D:\\workspace\\java就业培训教程","file2.txt");
⑷、File(URI uri)通过将给定的file: URI 转换为一个抽象路径名来创建一个新的 File 实例。
3、File类常见的方法
⑴、创建:boolean createNewFile():在指定位置创建文件,如果该文件已经存在,则不创建,返回false。和输出流不一样,输出流对象一创建文件,而且已经存在就会覆盖。
⑵、删除:boolean delete();
void deleteOnExit();
⑶、判断:boolean exists();
boolean isDirctory();
boolean isHidden();//在系统平台中,有些文件是被隐藏的,java读取时失败,但java总是会去读,所以判断一下。
boolean isAbsolute();
boolean isFile();
⑷、获取;
在获取中返回值有两种类型:File和String,两者之间可以相互转换,new File就变成File类型,file.toString()就变成了String类型了。
getName()
getPath()
getAbsolutePath()
getParent()
⑸、fl.renameTo(f2);将f1文件名换成f2的文件名,如果这俩个文件不在同一个路径下,f1会移动到f2所在路径下
⑹、代码示例
publicclass FileTest {
publicstaticvoid main(String[] args) throws IOException {
File f =new File("D:\\workspace\\java就业培训教程\\file1.txt");
f.deleteOnExit();//在虚拟机终止时,请求删除此抽象路径名表示的文件或目录
//原因:当在程序运行的过程中出现异常时,程序无法继续执行,而产生的文件就变成垃圾了,但因为程序正在运行,有可能无法删除
//创建文件夹
File dir = newFile("file2");
dir.mkdir();//创建此抽象路径名指定的一级目录。
dir.mkdirs();//创建多级目录
if(f.exists()){//测试此抽象路径名表示的文件或目录是否存在
f.delete();//当且仅当成功删除文件或目录时,返回 true;否则返回 false
}else{
try{
//当且仅当不存在具有此抽象路径名指定名称的文件时,不可分地创建一个新的空文件。
f2.createNewFile();
}catch(Exception e){
System.out.println(e.getMessage());
}
}
//返回由此抽象路径名表示的文件或目录的名称。
System.out.println("File name:"+f.getName());
//获取你所封装的路径
System.out.println("File path:"+f.getPath());
//返回此抽象路径名的绝对路径名字符串
System.out.println("Abs path:"+f.getAbsolutePath());
//返回此抽象路径名父目录的路径名字符串,即只要封装了路径,就返回这个路径;如果此路径名没有明确指定目录,则返回 null。
System.out.println("Parent:"+f.getParent());
//在判断文件对象是否是文件或目录时,必须先判断文件对象封装的内容是否存在
System.out.println(f.exists()?"exists":"does not exist");
System.out.println(f.canWrite()?"is writeable":"is not writeable");
//测试此抽象路径名表示的文件是否是一个目录。
System.out.println(f.isDirectory()?"is":"is not"+" a directory");
//在判断文件对象是否是文件或目录时,必须先判断文件对象封装的内容是否存在
System.out.println(f.isFile()?"is a normal file":"might be a named pipe");
System.out.println(f.canRead()?"is Readable":"is not Readable");
System.out.println(f.isAbsolute()?"is absolute":"is not absolute");
//获取文件最后一次修改的时间,返回值类型为long
System.out.println("File last modified:"+f.lastModified());
//获取文件大小,返回值类型为long
System.out.println("File size:"+f.length()+" Bytes");
}
}
4、文件列表
⑴、listRoots方法
publicstaticvoid listRoot(){
File[] files = File.listRoots();//获取盘符
for(File f : files){
System.out.println(f);
}
}
⑵、list方法
publicstaticvoidlist(){
File f = new File("d:\\");
String[] names = f.list();//获取指定目录下的文件及文件夹的名称,包括隐藏的和没有隐藏的。调用list方法的file对象必须是封装了一个目录,该目录必须存在。
for(String name : names){
System.out.println(name);
}
}
⑶、文件过滤器
publicstaticvoidfilenameFilter(){
Filedir =new File("d:\\");
//list依据指定的accept方法的返回值来判断是否需要过滤该文件
// 因为FilenameFilter接口只有一个方法,所以我们可以传一个匿名内部类
String[]arr = dir.list(new FilenameFilter(){
//dir:代表指定的 目录 name:文件名称
publicbooleanaccept(File dir, String name) {
return name.endsWith(".java");
}
});
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
总结:以上方法都只能将当前指定路径下的文件读出来,而子目录中的内容无法读取
⑷、列出目录下所有的内容
列出指定路径下的文件或文件夹,包含子目录中的内容,也就是列出目录下所有的内容。
①、使用递归列出目录下所有的内容
publicstaticvoidlistFile(File dir,int level) {
//打印目录
System.out.println(getLevel(level)+dir.getName());
level++;
File[] file = dir.listFiles();
for(File f : file) {
//方法自身调用自身的编程手法成为递归。要注意限定条件,即递归的次数,尽量避免内存溢出
if(f.isDirectory())
listFile(f, level);
else
System.out.println(getLevel(level)+f);
}
}
② 列出带层级的目录
publicstatic String getLevel(intlevel) {
StringBuildersb = new StringBuilder();
sb.append("|--");
for (int i = 0; i < level; i++) {
//sb.append("!--");
sb.insert(0,"| ");
}
return sb.toString();
}
7.5 Properties类
1、概述
Properties是hashtable的子类 也就是说它具有map集合的特点,而且它里面存储的键值对都是字符串它是集合中和IO技术相结合的容器。该对象的特点是可以用于键值对形式的配置文件Properties 类表示了一个持久的属性集。Properties 可保存在流中或从流中加载。属性列表中每个键及其对应值都是一个字符串。
2、常见方法
⑴、设置和获取元素
publicstaticvoidsetAndGet() {
//创建一个无默认值的空属性列表。
Properties prop = new Properties();
//设置
prop.setProperty("zhangsan","30");
prop.setProperty("lisi","29");
//修改键lisi的值
prop.setProperty("lisi",87+"");
//通过键获取值
String value = prop.getProperty("lisi");
//回此属性列表中的键集,其中该键及其对应值是字符串
Set<String> names=prop.stringPropertyNames();
for(String s :names){
System.out.println(s+prop.getProperty(s));
}
}
⑵、load方法
需求:如何将流中的数据存储到集合中?想要将info.txt中的键值数据存储到集合中进行操作。
思路:ⅰ、用一个流和info.txt文件关联
ⅱ、读取一行数据,将该数据用“=”进行切割
ⅲ、等号左边的作为键,右边的作为值,存入到Properties集合中即可
①、load方法原理
publicstaticvoidmethod() throws IOException {
BufferedReader bufr = newBufferedReader(new FileReader("info.txt"));
String line = null;
Properties prop = newProperties();
while((line=bufr.readLine())!=null){
String[] arr = line.split("=");
prop.getProperty(arr[0],arr[1]);
}
bufr.close();
}
②、load方法实现
publicclassPropertiesDemo {
publicstaticvoid main(String[] args) throws IOException {
Properties prop = new Properties();
FileInputStream fis = new FileInputStream("info.txt");
//将流中的数据加载进集合
prop.load(fis);
prop.list(System.out);
// 修改文件中的内容,但是只改变内存的结果,无法加载进Proprpties表中
prop.setProperty("wangwu", "30");
FileOutputStream fos = new FileOutputStream("info.txt");
//public void store(Writer writer, Stringcomments)
//如果 comments 变量非 null,则首先将 ASCII # 字符、注释字符串和一个行分隔符写入输出流。因此,该 comments 可用作一个标识注释
prop.store(fos, null);
System.out.println(prop);
fis.close();
fos.close();
}
3、应用
练习:用于记录应用程序运行的次数。如果使用次数已到,那么给出注册提示。
思路:
很容易想到计数器,可该计数器定义在程序中,随程序的运行而在内存中存在,并自增可随着该应用程序的退出,该计数器内存中消失。但我们希望,程序即使结束,该计数器的值依然存在,下次启动时,会先加载计数器的值并自增一次,再重新存储起来。
所以要建立一个配置文件,用于记录该软件运行次数。该配置文件使用键值对的形式,这样便于阅读书籍,并操作数据。键值对数据是map集合,数据是以文件形式存储,使用IO技术,那么map+io就是propreties。配置文件可以实现应用程序数据的共享。
publicclass PropertiesTest {
publicstaticvoidmain(String[] args) throws IOException {
//将配置文件封装成File对象
File file = new File("count.ini");
if(!file.exists()){
file.createNewFile();
}
FileInputStream fis = new FileInputStream(file);
Properties prop = new Properties();
prop.load(fis);
//从集合中通过键获取次数
String value = prop.getProperty("time");
//定义计数器,记录获取到的次数
int count = 0;
if(value!=null){
count = Integer.parseInt(value);
if(count>=5)
System.out.println("您好,使用次数已到,拿钱");
return;
}
count++;
//将改变后的次数重新存储到集合中
prop.setProperty("time", count+"");
FileOutputStream fos = new FileOutputStream(file);
prop.store(fos, "配置文件");
fos.close();
fis.close();
}
}
7.6 IO包中的其他类
7.6.1打印流
1、打印流:该流提供了打印方法,可以将各种数据类型的数据都原样打印
⑴、字节打印流:PrintStream
其构造方法可以接收的参数类型
①、file对象:File
②、字符串路径:String
③、字节输出流:OutputStream
⑵、字符打印流:PrintWriter
其构造方法可以接收的参数类型
①、file对象:File
②、字符串路径:String
③、字节输出流:OutputStream
④、字符输出流:WriterPrintStream 、PrintWriter
publicclass PrintStreamDemo {
publicstaticvoidmain(String[] args) throws IOException {
BufferedReader bufr = new BufferedReader(newInputStreamReader(System.in));
// PrintStream(OutputStream out, booleanautoFlush)创建新的打印流。autoFlush - boolean 变量;如果为 true,则每当写入 byte 数组、调用其中一个 println方法或写入换行符或字节 ('\n') 时都会刷新输出缓冲区
PrintWriter pw = new PrintWriter("a.txt");
//第二种刷新的方法
// PrintWriter pw = newPrintWriter(System.out,ture);
//PrintWriter pw = newPrintWriter(System.out);
String line = null;
while((line = bufr.readLine())!=null){
if("over".equals(line))
break;
pw.println(line);
//刷新第一种方法
pw.flush();
}
pw.close();
bufr.close();
}
}
相比普通的OutputStream和Writer增加了print()和println()方法,这两个方法可以输出实参的toString()方法的返回值这两个类还提供自动flush()的功能
7.6.2序列流
1、SequenceInputStream
可以将多个字节输入流整合成一个流,在使用这个流读取的时候,读到第一个流的末尾时继续读第二个,第二个读到末尾则继续读第三个,以此类推,直到读到最后一个流的末尾返回-1
将多个个流合并成一个流,它从输入流的有序集合开始 ,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。
⑴、SequenceInputStream(InputStream s1, InputStream s2):将按顺序读取这两个参数,先读取 s1,然后读取 s2
publicclass SequenceInputStreamDemo {
publicstaticvoidmain(String[] args) throws IOException {
Vector<FileInputStream> v = new Vector<FileInputStream>();
v.add(new FileInputStream("d:\\1.part"));
v.add(new FileInputStream("d:\\2.part"));
v.add(new FileInputStream("d:\\3.part"));
v.add(new FileInputStream("d:\\4.part"));
v.add(new FileInputStream("d:\\5.part"));
Enumeration<FileInputStream> en =v.elements();
SequenceInputStream sis = new SequenceInputStream(en);
FileOutputStream fos = new FileOutputStream("d:\\北京北京2.mp3");
byte[] buf = newbyte[1024];
int len = 0;
while((len = sis.read(buf))!= -1){
fos.write(buf,0,len);
}
fos.close();
sis.close();
}
⑵、SequenceInputStream(Enumeration<?extends InputStream> e) :
privatestaticvoid merge() throws IOException {
ArrayList<FileInputStream>al = new ArrayList<FileInputStream>();
for (int i = 1; i <=5; i++) {
al.add(new FileInputStream("d:\\"+i+".part"));
}
//因为it访问的是匿名内部类,所以要进行final修饰
final Iterator<FileInputStream> it =al.iterator();
Enumeration<FileInputStream>en = new Enumeration<FileInputStream>(){
publicbooleanhasMoreElements() {
return it.hasNext();
}
public FileInputStream nextElement() {
return it.next();
}
};
SequenceInputStreamsis = new SequenceInputStream(en);
FileOutputStreamfos = new FileOutputStream("d:\\beijin.mp3");
byte[] buf = newbyte[1024*1024*3];
int len = 0;
while((len = sis.read(buf))!=-1){
fos.write(buf,0, len);
}
fos.close();
sis.close();
}
}
2、切割文件
publicclass FileSplitDemo {
publicstaticvoidmain(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("D:\\北京北京.mp3");
FileOutputStream fos = null;
byte[] buf = newbyte[1024*1024*3];
int len = 0;
int count = 1;
while((len=fis.read(buf))!=-1){
fos = new FileOutputStream("D:\\"+(count++)+".part");
fos.write(buf, 0, len);
fos.close();
}
fis.close();
}
}
7.6.3 操作对象
对象本身存在于堆内存中,当我们将程序执行完毕后,对象及其数据在内存中就会被回收,于是我们可以使用ObjectOutputStream将对象存储到硬盘中,如果以后要使用该对象及其中的数据时,就看使用ObjectInputStream将其读到内存中。这两个对象是成对使用的。
ObjectOutputStream除了有继承自OutputStream以外,还有很多其他的自身特有的方法,下面介绍一些比较常用的:
1、write+基本数据类型,如writeInt()方法,可以直接操作基本数据类型,要注意的是write()和writeInt()方法的区别是前者只写数据的前8位,即1个字节,而后者写32位。还有可以直接操作对象的方法writeObject(),代码示例:
publicclass ObjectStreamDemo {
publicstaticvoid main(String[]args) throws IOException {
writeObj();
}
publicstaticvoid writeObj() throws IOException {
ObjectOutputStreamoos = new ObjectOutputStream(
new FileOutputStream("object.txt"));
oos.writeObject(newPerson("lisi",97));
oos.close();
}
}
执行改程序会报出:Exceptionin thread "main" java.io.NotSerializableException: Person的错误,其原因是该对象需要序列化,却没有没有实现java.io.Serializable 接口,解决方法就是在对象的类中实现该接口,代码如下:
import java.io.Serializable;
class Person implementsSerializable {
String name;
intage;
Person(String name,int age) {
this.name=name;
this.age=age;
}
public String toString() {
returnname+"....."+age;
}
}
该接口没有任何方法,这样的接口称之为标记接口,其实就是给该类盖个戳,即得具备序列化的资格,否则不让你序列化。其原理是序列化运行时会使用一个serialVersionUID的版本号与每个可序列化的类相关联,该序列号在反序列化过程中用于验证对象的发送者与接受者是否为该对象加载了与序列化兼容的类。版本号是根据类中成员计算出来的。
自定义序列化的方式:ANY-ACCESS-MODIFIER static final longserialVersionUID = 42L。
静态数据不能被序列化,因为序列化在堆中进行,而静态数据在方法区中,所以不能被序列化。如果对于非静态的数据也不想序列化的话,可以在该数据前添加transiant。
7.6.4 管道流
7.6.5 RandomAccessFile
该类不算是IO体系中的子类,而是直接继承自object,但是它是IO包中的成员,因为具备读写功能,内部封装了一个数组,而且通过指针对数组的元素进行操作,可以通过getFilePointer获取指针的位置,同时可以通过seek方法改变指针的位置。
其实完成读写原理就是内部封装了字节输入和输出流,同时,通过构造方法可以看出该类只能操作文件,而且操作文件还有模式,模式有4种,其中最常用的是:r、rw。如果模式为只读(r),不会创建文件,回去读取一个已存在文件,如果该文件不存在,则会出现异常。如果模式为rw,操作的文件不存在,会自动创建,如果存在则会覆盖。
RandomAccessFileraf = new RandomAccessFile("random.txt","rw");
RandomAccessFileraf = new RandomAccessFile("random.txt","r");
下面是写数据的代码:
publicstaticvoid writeFile() throws IOException {
RandomAccessFile raf = new RandomAccessFile("random.txt","rw");
raf.write("lisi".getBytes());
raf.writeInt(97);
raf.write("wangwu".getBytes());
raf.writeInt(98);
raf.close();
}
下面是读数据的代码:
publicstaticvoid readFile() throwsIOException {
RandomAccessFile raf = new RandomAccessFile("random.txt","r");
byte[] buf = newbyte[4];
raf.read(buf);
String name = newString(buf);
int age =raf.readInt();
System.out.println("name="+name);
System.out.println("age="+age);
}
如果想要读取wangwu的相关数据,因为wangwu不在数组的0角标位置,所以可以利用seek()方法指定wangwu指针的开头位置,从而获取相关数据。还有一个方法skipBytes()方法也可以实现这样的功能,但是只能向前跳,而seek()方法可以跳到任意给定的位置。因此可以在任意位置添加数据,或者修改数据。
publicstaticvoid writeFile2() throws IOException {
RandomAccessFile raf = new RandomAccessFile("random.txt","rw");
//往第8*3的位置添加zhouqi相关的数据
raf.seek(8*3);
raf.write("zhouqi".getBytes());
raf.writeInt(45);
}
根据随机读写访问的原理,可以实现分段读写,如多线程下载就是用他实现的。
7.6.6 操作基本数据类型的流对象
1、数据读取
privatestaticvoid readData() throws IOException {
DataInputStream dis = new DataInputStream(newFileInputStream("data.txt"));
int num =dis.readInt();
Boolean bool = dis.readBoolean();
Double dou = dis.readDouble();
System.out.println(num+" "+bool+""+dou);
}
2、数据写入
privatestaticvoid writeData() throws IOException {
DataOutputStream dos = new DataOutputStream(newFileOutputStream("data.txt"));
dos.writeInt(243);
dos.writeBoolean(true);
dos.writeDouble(3453.89);
dos.close();
}
3、以与机器无关方式使用 UTF-8 修改版编码将一个字符串写入基础输出流。
privatestaticvoid writeUTFDemo() throws IOException {
DataOutputStreamdos = newDataOutputStream(new FileOutputStream("utfFile.txt"));
dos.writeUTF("你好");
dos.close();
}
7.6.6 内存为源和目的的操作
用流的思想操作数组
1、操作字节的数组
1、ByteArrayInputStream
其中包含一个内部缓冲区,该缓冲区包含从流中读取的字节,即他对应的是源,他会将源数据对应的字节存储到它的内部缓冲区。也就是说在构造的时候,需要接受数据目的,而且数据源是一个字节数组。内部计数器会跟踪read方法提供下一个字节。
注意:该对象没有调用底层资源,所以关闭该流是无效的,此类中的方法在关闭此流还可以继续运行,不会产生IOException
2、ByteArrayOutputStream
这是一个输出流,在构造是不需要定义数据目的,因为其中的数据被写入一个byte数组,缓冲区会随着数据的不断写入而自动增长,这就是数据目的地。可使用toByteArray()和toString()获取数据,而不用flush()。
因为这两个流对象都操作的数组,并没有调用系统资源,所以不用进行close()关闭.
3、方法
1、writeTo()
2、操作字符的数组:CharArrayReader与CharArrayWrite
3、操作字符串:StringReader与StringWriter
7.6.7 字符编码
1、编码表的由来
2、常见的编码表
1、ASCII:美国标准信息交换码,用一个字节的7位可以表示。
2、ISO8859-1:拉丁码表,欧洲码表,用一个字节8位表示。
3、GB2312:中国的中文编码表。
4、GBK:中国的中文编码表的升级,融合了更多的中文字符号。
5、Unicode:国际标准码,融合了多种文字。所有文字都用两个字节来表示,java语言使用的就是unicode。但是由于所有的文字都得用两个字节表示,这样浪费空间。
6、UTF-8:最多用三个字节表示一个字符。他有自己的格式:单字节头位是0;两个字节来表示的话,头一个字节前几位是110,第二个字节前几位是10;三个字节来表示的话,头一个字节前几位是1110,第二个字节前几位是10,第三个字节前几位是10。
3、编码与解码
1、编码:字符串变成字节数组。
默认的编码形式:Stringàbyte[]:string.getBytes()。默认编码表是GBK。
指定的编码形式;string.getBytes(charsetName)
2、解码:字节数组变成字符串
默认的编码形式:byte[]àString:newString(byte[])。默认编码表是GBK。
指定的编码形式;byte[]àString:newString(byte[],charsetName)
3、当乱码产生了,该怎么办?
乱码产生的原因:在编码的时候用的是一张适合该文章的码表,比如中文用GBK,但是在解码的时候却没有使用原来的码表来解码,而是用其他的码表,比如原来的用的是GBK,解码是却用ISO8859-1,这样就会产生乱码。
解决办法:用原来解码时用的码表(以上文的ISO8859-1为例),对乱码进行编码,编成字节码,然后再用原来的编码表(以上文的GBK为例)进行解码。