Java对文件和目录的操作
在java中,对物理存储介质中的文件和目录进行了抽象,使用java.io.File类代表存储介质中的文件和目录。
File类的构造
通常用文件或者目录路径来构造,这个路径可以是绝对路径也可以是相对路径。
File类常用属性和方法
代码示例:
File f = new File("E:\\Hello.java");
System.out.println(f.exists());//文件是否存在
System.out.println(f.isFile());//是文件吗
System.out.println(f.isDirectory());//是目录
System.out.println(f.getName());//获取文件名称
System.out.println(f.getAbsolutePath());//获取文件绝对地址
try {
System.out.println(f.getCanonicalPath());//获取绝对路径的规范表示
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(f.lastModified());//获取文件最后修改时间
System.out.println(f.length());//获取文件大小,按字节算。
}
对文件、目录操作
浏览目录中文件和子目录的方法
代码如下:
//遍历文件是用的是递归
public static void ergodic(File f) {
File[] files = f.listFiles();
for(File file:files) {
System.out.println(file.getName());
if(file.isDirectory()) {
ergodic(file);
}
}
}
//递归删除
public static void delete(File f) {
if(f.isFile()) {
f.delete();//如果是文件直接删除
}else {//如果是目录,需要先删除子文件
File[] files = f.listFiles();
for(File file:files) {
delete(file);
}
}
f.delete();//删除完子文件之后,最后删除文件夹。
Java IO原理
在java程序中要想获取数据源中的数据,需要在程序和数据源之间建立一个数据输入的通道,这样就能从数据源中获取数据了,如果Java程序中把数据写到数据源中,也需要在程序和数据源之间建立一个数据输出的通道。在java程序中创建流对象时,会自动建立这个数据输入通道,而创建输出流对象时会自动建立这个数据输出通道。
Java中流分类
1) 数据流方向
- 输入流
输出流
2) 数据传输单位
字节流
字符流
3)流功能
节点流:用于直接操作数据源的流。
- 过滤流:也叫过滤流,是对一个已经存在的流的封装和连接,提供更加强大的读写功能。
IO流的抽象类
InputStream常用方法
//is.read();//从输入流中读取数据的下一个字节,返回读到的字节值(这个字节值就是一个字节的十进制的值,比如读a,a是一个字符,就占一个字节,所以返回的值就是97),若遇到流结尾返回-1.
//is.read(b);//从输入流中读取b.len个字节数据并存储到缓冲区b数组中,返回是实际读到的字节数。
//is.read(b, 0,b.length);//读取b.len个字节的数据,并从数组b的off位置开始写到数组中。
OutStream常用方法
//out.write(b);//将b.length个字节从指定的byte数组写入到输出流
out.write(97);//指定将规定的一个字节数写入此输出流,(相对应的你写97,就是a)
//out.write(b, 0, b.length);//将指定的byte数组从偏移量off开始的len个字节写入此输出流。
out.flush();//刷新此输出流,并强制写出所有缓冲的输出字节。
Reader常用方法
主要以字符为单位进行处理。
a = reader.read();//读取单个字符,(请注意一个汉字就是一个字符),返回作为整数读取的字符,如果到末尾就是-1
//reader.read(c);//将字符读入数组中,返回读取的字符数
//reader.read(c, 0, c.length);//读取len个字符数据,并从数组的off位置开始写到数组中。
Writer常用方法
主要以字符为单位进行处理。
//w.write("老枪好帅");//写入字符串
w.write(c);//写入字符数组
w.write(c);//写入单个字符
w.write(c, a,c.length);//写入字符数据的一部分
w.write("", 0, len);//写入字符串的一部分
w.flush();//刷新该流的缓冲,全部写出来。
文件流
- FileInputStream
- FileOutputStream
- FileReader
- FileWriter
InputStream in = new FileInputStream("");//传入相应处理的文件路径
OutputStream out = new FileOutputStream("");
Reader reader = new FileReader("");
Writer writer = new FileWriter("");
上面是抽象的Io的实现,你在处理时,根据不同的文件进行相应的选择处理。
缓冲流
为了提高数据的读写速度,java提供了带缓冲流的流类,在使用这些带缓冲功能的流类,它会在内部创建缓冲区数组,在读取字节(创建字节数组)或者字符(创建字符数组)的时候,会把数据先读到缓冲区,然后在返回,在写入字节或者字符,会先把数据填充到内部缓冲区,然后一次性写到目标数据源中。
缓冲区流的分类
- BufferInputStream
- BufferOutputStream
- BufferReader
- BufferWriter
缓冲流属于过滤流,也就是说缓冲流不直接操作数据源,而是对直接操作数据源节点流的一个包装,增加功能。
下面给出操作字节字符缓冲流代码:
BufferedOutputStream bos = null;
OutputStream out = null;
InputStream in = null;
BufferedInputStream bis = null;
int i = 0;
try {
in = new FileInputStream("E:\\Hello.java");
out =new FileOutputStream("E:\\Hello1.java");
bos = new BufferedOutputStream(out);
bis = new BufferedInputStream(in);
while((i = bis.read())!=-1){//这里是从缓冲区上一次读一个
bos.write(i);
}
bos.flush();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
if(bos!=null) {
try {
bos.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(bis!=null) {
try {
bis.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Reader r = null;
Writer w = null;
BufferedReader br = null;
BufferedWriter bw = null;
String str = null;
try {
r = new FileReader("E:\\Hello.java");
w = new FileWriter("E:\\Hello2.java");
br = new BufferedReader(r);
bw = new BufferedWriter(w);
while((str = br.readLine())!=null) {//这个操作字符流和原来不一样,一次读一行。
bw.write(str);
}
bw.flush();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
if(br!=null) {
try {
br.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(bw!=null) {
try {
bw.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
在实际操作中,还是推荐使用第二种操作文件更方便。
注意在使用过滤流的过程中,它是对底层的节点流封装,所以在我们手动关闭了过滤流的时候,就不需要手动关闭节点流。
转换流
有时我们需要在字节流和字符流中进行转化,使用到InputStream Reader 和OutputStreamWriter。
- InputStream Reader
//InputStreamReader isr = new InputStreamReader(in);//传入字节流,转化成默认字符流读。
//InputStreamReader isr1 = new InputStreamReader(in, charsetName);//传入字节流,按照指定编码转化为字符流读。
用于将字节流中读取到的字节按字符集解码成字符。
- OutputStreamWriter
用于将要写入到字节流中的字符按照置顶字符集编码成字节。
OutputStreamWriter osw = new OutputStreamWriter(out);//传入字节流,转化成默认的字符流写入
OutputStreamWriter osw1 = new OutputStreamWriter(out, cs);//传入字节流,按照指定编码转化为字符流写
例子:
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
//从键盘上获取的是字符,通过转换流来变成字符流操作,更便捷。
try {
while((content = br.readLine())!=null) {
if(content.equalsIgnoreCase("e")) {
break;
}else {
System.out.println(content.toUpperCase());
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
数据流
用于操作Java语言的基本数据类型的数据,主要有这两个类:DataInputStream和DataOutputStream。
代码如下:
DataOutputStream dis = null;
DataInputStream is = null;
InputStreamReader isr = null;
BufferedReader br = null;
String content = null;
try {
dis= new DataOutputStream(new FileOutputStream("E:\\data.txt"));
dis.writeInt(87);
dis.writeBoolean(true);
dis.writeUTF("中国");
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
is = new DataInputStream(new FileInputStream("E:\\data.txt"));
int a = is.readInt();//需要注意的是这里在读取的时候必须使用DataInputStream
//否则出现乱码
boolean b = is.readBoolean();
String c = is.readUTF();
System.out.println(a);
System.out.println(b);
System.out.println(c);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
对象流
ObjectOutputStream和ObjectInputStream类是用来存储和读取基本类型数据或对象的过滤流,它的强大之处就是可以把Java中的对象写到数据源中,也能把对象从数据源中还原回来。用ObjectOutputStream类保存基本数据类型或者对象叫序列化,用ObjectInputStream类读取基本数据类型或者对象的机制叫做反序列化。ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量。
序列化:
//定义一个可以序列化的类
public class Student implements Serializable{
private String name;
private transient int age;//不能序列化的属性
private String sex;
public Student(String name, int age, String sex) {
super();
this.name = name;
this.age = age;
this.sex = sex;
}
public Student() {
super();
// TODO Auto-generated constructor stub
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
在这个序列化中,它的成员变量不能被保存和读取。
序列化的好处在于,它可以将任何实现Serializable接口的对象转化为字节数据。这些数据可以保存在数据源中,以后仍可以恢复成原来的对象状态,即使这些数据通过网络也可以还原。
序列化和反序列化代码:
/*ObjectOutputStream out = null;
try {
out = new ObjectOutputStream(new FileOutputStream("E:\\data2.txt"));
out.writeObject(new Student("laoqiang", 23, "男"));
out.flush();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
if(out!=null) {
try {
out.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}*/
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream("E:\\data2.txt"));
Student s = (Student) ois.readObject();
System.out.println(s.toString());
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
if(ois!=null) {
try {
ois.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
序列化版本
凡是实现Serializable接口的类都有一个表示序列化版本标识的静态变量。用来表明类的不同版本之间的兼容性,默认情况下,如果没有直接定义这个变量,它的值是由java运行时根据累的内部细节自动生成,对于不同额java编译器,它的值可能相同也可能不相同。如果对类的源代码修改或者重新编译,该值会变。如果这时还用老版本的类来反序列化的,就会出错。我们建议,用户应该自己定一个,并且给它相应的值。
显式定义serialVersionUID的用途:
- 在某些场合,希望类的不同版本对序列化兼容,因此我们需要保证serialVersionUID相同。
- 在某些场合,不希望类的不同版本对序列化兼容,因此我们需要保证具有不同的serialVersionUID。
随机存取文件流
RandomAcessFile是一种特殊的流类,它可以在文件的任何地方读取或者写入数据,这是因为它提供文件指针,可以指定下次读取字节的位置。通过seek,可以将文件指针移动到文件内部的任意字节的位置。
随机文件存取流,提供两种方式可读(r),或者是同时读写(rw),不可以单独写。
随机存取文件执限于磁盘文件,不能访问来自网络,或者内存映像的流。
下面的是多线程下载文件:
private static final String URL= "www.baidu.com" ;
private static int contentlength = 0;//资源的总字节数
private static int threadnumber = 10;//线程个数
private static int everythreaddownoadlength = 0;//每一个线程的需要下载的长度
private static int extra = 0;
private static int start,end = 0;//记录每一个线程的开始和结束
private static File file = new File("E:\\data3.txt");
public static void main(String[] args) {
try {
URL url = new URL(URL);
URLConnection conn = url.openConnection();
contentlength = conn.getContentLength();
everythreaddownoadlength = contentlength/threadnumber;//计算每个线程需要下载长度
extra = contentlength%threadnumber;//如果在上面计算每一个线程下载长度不能整除。
for(int i = 0;i<threadnumber;i++) {
start = threadnumber*i;
end = start+everythreaddownoadlength-1;//0~9,10~19,20~29
if(i == threadnumber-1) {//这里的意思,将不能平均的下载量全加给最后一个,因为剩余的不够分给一个线程
end = end+extra;
}
MyThread mt = new MyThread(start, end, file,URL);
Thread t = new Thread(mt);
t.start();
}
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class MyThread implements Runnable{
private int start,end,length = 0;
private File endfile = null;
private String url = null;
private BufferedInputStream bis = null;
private byte[] b = new byte[2048];
public MyThread(int start, int end, File endfile,String url) {
super();
this.start = start;
this.end = end;
this.endfile = endfile;
this.url = url;
}
@Override
public void run() {
// TODO Auto-generated method stub
URL url1 =null;
URLConnection conn = null;
try {
url1 = new URL(url);
conn = url1.openConnection();
} catch (MalformedURLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
RandomAccessFile raf = null;
try {
raf = new RandomAccessFile(endfile, "rw");
raf.seek(start);//将文件指针移动到线程开始的位置
bis = new BufferedInputStream(conn.getInputStream());
while((length=bis.read(b))!=-1) {
raf.write(b, 0, length);//将资源写到目的的文件。
}
System.out.println(Thread.currentThread().getName()+"下载好了");
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
if(raf!=null) {
try {
raf.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(bis!=null) {
try {
bis.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
大致思路:获取网路哦资源总长,按照线程个数,让每一个线程从不同开始位置下载。