Java基础——IO流

一、File类
 File类的一个对象,就对应着硬盘中的一个文件(或文件夹)或网络中的一个资源。File类只能对文件或文件目录进行新建、删除、重命名、上层目录等操作,如果涉及到访问文件的内容,File是无能为力的,只能使用相应的输入输出流来实现。File对象作为流的端点通常会被作为参数传递给相应的输入输出流的构造器中。
 File类的方法:

getName();//获取文件名或者当前文件夹名
getPath();//获取路径
getAbsoluteFile();//获取包括文件名在内的绝对路径
getAbsoluterPath();//获取当前文件的路径
getParent();//获取上一级目录路径
//重命名,重命名前后File的类型要相同,也就是说file1是文件的时候file2也必须是文件,file1是文件目录的时候file2也必须是文件目录,重命名的时候不能跨盘符
renameTo(File  newName);

exists();//是否存在
canWrite();//是否可写
canRead();//是否可读
isFile();//是不是文件
isDirectory();//是不是文件目录

lastModified();//最后修改时间的时间戳,返回long型值
length();//文件内容的长度,返回long型值

createNewFile();//先判断该文件是否存在,不存在则file.createNewFile(),返回布尔值
delete();//删除文件或者目录

mkDir();//创建目录,只能创建当前目录,必须在保证该目录的上一级目录存在的
mkDirs();//可以连并上一级目录一起创建
list();//返回值是String[]数组,把该目录下的文件和文件夹以字符串数组的形式读取出来
listFiles();//把该目录下的文件和文件夹以文件数组(File[])的形式读取出来

 在指定路径下创建文件:

File file = new File(file.getAbsolutePath(),"hello.txt"); 

二、IO流的体系构成:
在这里插入图片描述
 1、IO流的划分
  ①按照流向的不同:分为输入流和输出流(站位于内存的角度定位输入还是输出:将文件数据读入到内存的为输入流;将文件数据从内存写入到磁盘或网络的为输出流)
  ②按照数据单位的不同:分为字节流和字符流(只有纯文本文件可使用字符流,所有文件都可使用字节流)
  ③按照角色的不同:分为节点流和处理流 (可直接作用于文件上的流为节点流——4个,除此之外都是处理流)


  1️⃣从硬盘中读入一个文件,要求此文件一定得存在,即输入流中传入的File对象一定得存在,若不存在,则会报FileNotFoundException异常
  2️⃣输出一个文件到硬盘,此文件可以不存在,即输出流中传入的File对象可不存在,若不存在,会创建一个同名文件并输出;若存在,则会将原文件的内容覆盖
  3️⃣开发时尽量使用缓冲流来代替节点流,以避免程序阻塞
  4️⃣流在使用完后一定要关闭:先关闭输出流,再关闭输入流
 2、示例:
  1️⃣文件夹中复制:判断是文件还是文件夹,是文件的话直接复制,若是文件夹则先列出所有文件listFiles(),得到一个File[]数组,然后遍历递归复制:

class CopyUtils {
	public static void copyFiles(String src, String dest) throws Exception {
		File sFile = new File(src);
		BufferedInputStream bis = null;
		BufferedOutputStream bos = null;
		byte[] b = new byte[1024];
		int len;
		try {
			if (sFile.isFile()) {
				bis = new BufferedInputStream(new FileInputStream(sFile));
				bos = new BufferedOutputStream(new FileOutputStream(dest + "\\" + sFile.getName()));
				while ((len = bis.read(b)) != -1) {
					bos.write(b, 0, len);
					bos.flush();
				}
			} else if (sFile.isDirectory()) {
				// 文件夹中的文件夹的复制思想:使用递归,调用复制文件夹的操作
				// 若拷贝的是一个文件夹,则先在目标目录下创建一个同名的文件夹
				dest = dest + "\\" + sFile.getName();
				File dFile = new File(dest);
				dFile.mkdirs();
				File[] files = sFile.listFiles();
				for (int i = 0; i < files.length; i++) {
					// 注意文件夹不能读取和写入,但其内的文件可以
					if (files[i].isFile()) {
						bis = new BufferedInputStream(new FileInputStream(sFile + "\\" + files[i].getName()));
						bos = new BufferedOutputStream(new FileOutputStream(dFile + "\\" + files[i].getName()));
						while ((len = bis.read(b)) != -1) {
							bos.write(b, 0, len);
							bos.flush();
						}
					} else {
						dest = dest + "\\";
						copyFiles(src + "\\" + files[i].getName(), dest);
					}
				}
			} else {
				throw new Exception("不是文件或文件夹,无法复制!");
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (bos != null) {
				try {
					bos.close();
				} catch (IOException e) {
				}
			}
			if (bis != null) {
				try {
					bis.close();
				} catch (IOException e) {
				}
			}
		}
	}
}

三、不常用的流
 1、转换流:实现字节流与字符流之间的转换
  InputStreamReader:输入时,实现字节流到字符流的转换,提高操作的效率(前提是数据是纯文本文件)
  OutputStreamWriter:输出时,实现字符流到字节流的转换
  InputStreamReader继承了Reader,OutputStreamWriter继承了Writer,可以读字符,在用转换流读取和写入文件的时候可以指定编码格式,以免出现乱码,它们都有多个构造器,在有格式转换的时候,在解码的时候要和编码(写入内容)时的格式一致,编码后再打开文件时要以编码的格式浏览。
  示例:

InputStreamReader isr = new InputStreamReader(new FileInputStream(new File("hello.txt")),String charset);//将字节的输入流转换为字符的输入流,也称为解码
   
OutputSteramWriter osw = new OutputStreamWriter(new FileOutputStream(new File("hello1.txt")),String charset);//将字符的输出流转换为字节的输出流,也称为编码,其实是再次编码

InputStreamReader reader = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(reader);
br.readLine();//从键盘获取一个字符串

  
   1️⃣InputStreamReader中charset是文件保存时的编码字符集,是由文件保存时的编码决定的;
   2️⃣OutputSteramWriter中的charset则可以任意指定,不一定要和InputStreamReader中的一致;
  可以这么理解:将文件读入到内存时,必须按照文件原来的编码方式,只有编码方式相同,才能保证不出现乱码,就好比加密电报,使用哪种方式加密就得使用哪种方式解密,因此也称为解码;通过解码将文件解析到内存中之后就成了普通的字节流,字节流是相通的,就像大家都认识的汉字一样,这个时候再将文件从内存中往磁盘或网络中写时就可以选择一个新的编码方式进行编码。
  转换流InputStreamReader和OutputStreamWriter常与标准输入输出流System.in和System.out连用,用来包装标准输入输出流:

InputStreamReader isr = new InputStreamReader(System.in);
OutputStreamWriter osw = new OutputStreamWriter(System.out);

 2、标准输入输出流
  System.in:The “standard” input stream,标准的输入流,默认从键盘输入
  System.out:The “standard” output stream,标准的输出流,默认从控制台输出,可结合打印流自定义设置输出的位置

Scanner scanner = new Scanner(System.in);//获取键盘输入内容

//设置输出位置
PrintStream p = new PrintStream(new FileOutputStream("hello.txt"),true);
System.setOut(PrintStream stream);

 3、打印流 (都是输出流):PrintStream(处理字节)、PrintWriter(处理字符)

FileOutputStream fos = null;//创建字节输出流对象,确定输出位置
try{
	fos = new FileOutputStream(“print.txt”);//参数相当于new File(“print.txt”)
 }catch(FileNotFoundException e){
	e.printStackTrace();
}
PrintStream ps=new PrintStream(fos,true);//将输出流包装,true表示自动刷新
if(ps!=null){
	System.setOut(ps);//设置输出的位置,不设置则默认输出在控制台
}
System.out.print(“hello java”);//打印的内容
ps.close();

 4、数据流(处理基本数据类型、String类、byte[]字节数组):DataInputStream、 DataOutputStream

DataInputStream dis = new DataInputStream(new FileInputStream(new File("data.dat")));

DataOutputStream dos = new DataOutputStream(new FileOutputStream(new File("d:\\io\\data.dat")));
dos.writeXxx(Xxx xxx);
dos.writeUTF(String str);

 通过数据流写入的内容还要借助数据流读取,否则可能出现乱码:注意,读的顺序一定要和写的顺序一致

DataOutputStream dos=null;
Try{
	//写好之后,查看该文档可能是乱码,需借助数据流读取即可呈现出来,这也可用于暗文信息
	FileOutputStream fos = new FileOutputStream(“data.txt”);
	Dos=new DataOutputStream(fos);
	Dos.writeUTF(“hello”);//写入字符串,注意不是writeString
	Dos.writeBoolean(true);
	Dos.writeLong(1234455444);
}catch(IOException e){
	e.printStackTrace();
}finally{
	Try{
		Dos.close();
	}catch(Exception e){
		e.printStackTrace();
	}
}

//读的顺序一定要和写的顺序一致
DataInputStream dis = null;
try{
	dis = new DataInputStream(new FileInputStream(new File("data.txt")));
	String str = dis.readUTF();
	System.out.println(str);
	boolean b = dis.readBoolean();
	long l = dis.readLong();
}catch(Exception e){
	e.printStackTrace();
}finally{
	if(dis != null)
		dis.close();
}

  5.对象流(用来处理对象的)
  对象的序列化机制允许把内存中的Java对象转换成与平台无关的二进制数据流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。当其它程序获取了这种二进制流,可以通过反序列化将其恢复成原来的Java对象。常用的方法:ObjectInputStream的readObject()和ObjectOutputStream的writeObject(Object obj)

Person p1 = new Person("小米", 21);
Person p2 = new Person("红米", 22);
ObjectOutputStream oos = null;
try {
	// 序列化对象保存的位置
	oos = new ObjectOutputStream(new FileOutputStream("person.txt"));
	oos.writeObject(p1);// 序列化过程
	oos.flush();
	oos.writeObject(p2);
	oos.flush();
} catch (Exception e) {
	e.printStackTrace();
} finally {
	try {
		oos.close();
	} catch (Exception e) {
		e.printStackTrace();
	}
}

//反序列化,该文件必须保存有序列化的对象,读取的顺序和存入的顺序要一致
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(“person.txt”));
Person p1=(Person)ois.readObject();
System.out.println(p1);
Person p2=(Person)ois.readObject();
System.out.println(p2);
ois.close();

  对象序列化的要求:
   ①要求类要实现Serializable接口
   ②同样要求类的所有属性也必须实现Serializable接口
   ③ 要求给类提供一个序列化版本号:private static final long serialVersionUID;
   ④声明为static 或transient的属性,不可以被序列化
   ⑤用对象流ObjectOutputStream和ObjectInputStream写入和读出对象或数据的时候,读取的顺序必须和写入的顺序一致,否则会出现异常OptionalDataException

  6、RandomAccessFile(随机存取文件流)
  随机存取文件流RandomAccessFile虽然在java.io包下,但它是直接继承于Object的,可以用来操作字节数据,也可以操作字符数据,但是操作字符数据的时候一定要注意在多次操作时指针的位置变化需要回到原来读或写的位置(即相同操作——读和写的指针位置一定不要跳跃,举例来说:使用RandomAccessFile复制文件时,第一次读文件时指针从第0个位置读10个字节,复制到第二个文件,那第二次再读文件时一定要从第10个位置开始读,否则就很可能会出现乱码,原因很简单字符数据和字节数据不同,一个字符是由多个字节组成的,如果发生字节的跳跃则很可能使原来的某个字符被破坏就会导致乱码),若跳跃则很可能会出现乱码;该流可用于下载文件,可以只用一个RandomAccessFile的对象即读又写,但别忘了调回指针。
  既可以充当一个输入流,又可以充当一个输出流:

public RandomAccessFile(File file, String mode);

  支持从文件的开头读取、写入;若输出的文件不存在,直接创建;若存在,则对原有文件内容进行覆盖;支持任意位置的“插入”。
  RandomAccessFile对象包含一个记录指针,用以标示当前读写所处的位置,RandomAccessFile对象可以自由移动记录指针:

long getFilePointer();//获取文件记录指针的当前位置
void seek(long pos);//将文件记录指针定位到pos位置

  构造器:

public RandomAccessFile(File file,String mode);
public RandomAccessFile(String name,String mode);

  创建RandomAccessFile类实例需要制定一个mode参数,该参数用来指定实例访问文件的权限模式:

r:只读
rw:读和写
rwd:读写并同步文件内容的更新
rws:读写并同步文件内容和元数据的更新

  RandomAccessFile即可实现读也可实现写,示例:复制文件内容至另一文件

RandomAccessFile raf1 = null;
RandomAccessFile raf2 = null;
try {
	raf1 = new RandomAccessFile(new File("hello.txt"), "r");// r也可是rw表示可读写
	raf2 = new RandomAccessFile("hello2.txt", "rw");// rw表示可读写
	byte[] b = new byte[20];
	int len;
	while ((len = raf1.read(b)) != -1) {
		raf2.write(b, 0, len);
	}
} catch (Exception e) {
	e.printStackTrace();
} finally {
	if (raf2 != null) {
		try {
			raf2.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	if (raf1 != null) {
		try {
			raf1.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

  覆盖原文件内容:在任意位置写入新内容

RandomAccessFile raf = null;
try {
	raf = new RandomAccessFile("hello.txt", "rw");
	raf.seek(7);// 从哪个位置开始覆盖原来的内容
	raf.write("world".getBytes());// 写入新内容
} catch (Exception e) {
	e.printStackTrace();
} finally {
	if (raf != null) {
		try {
			raf.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

  在一行的任意位置插入新内容:

RandomAccessFile raf = null;
try {
	raf = new RandomAccessFile(new File("hello.txt"), "rw");
	raf.seek(4);//移动光标
	String str = raf.readLine();//读取该行光标后的所有内容并赋值给变量str,此时光标会移动到行末
	raf.seek(4);//再将光标移动回来
	raf.write("xy".getBytes());//插入新内容
	raf.write(str.getBytes());//粘贴刚才复制的内容
} catch (IOException e) {
	e.printStackTrace();
} finally {
	// 关闭流
}

  更通用的插入(对文件内容没有要求,多少行都可以):

RandomAccessFile raf = null;
try {
	raf = new RandomAccessFile("hello.txt", "rw");
	raf.seek(7);// 调取指针
	byte[] b = new byte[20];
	int len;
	StringBuffer sb = new StringBuffer();
	while ((len = raf.read(b)) != -1) {
		sb.append(new String(b, 0, len));// 将指针后的内容保存至sb
	}
	raf.seek(7);// 调回指针
	raf.write("Hello world!".getBytes());// 插入内容
	raf.write(sb.toString().getBytes());
} catch (Exception e) {
	e.printStackTrace();
} finally {
	if (raf != null) {
		try {
			raf.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

四、向内存中加载文件
 Class类的Class.getResourceAsStream和ClassLoader类的ClassLoader.getResourceAsStream,都可以以流的形式获取本地文件资源,他们的区别在于默认的文件查找路径不同:
  a、Class.getResourceAsStream():以/开头时是绝对路径,没有/则是相对路径,用“…"表示上一级目录。最终调用是ClassLoader.getResourceAsStream( ),只是在这之前对参数进行了调整——如果参数以/开头,则去除/,否则把当前类的包名加在参数的前面;
  b、ClassLoader.getResourceAsStream():路径直接使用相对于classpath的绝对路径,并且不能已 / 开头;

五、流的关闭顺序
 一般情况下:先打开的后关闭,后打开的先关闭
 另一种情况:看依赖关系,如果流a依赖流b,应该先关闭流a,再关闭流b。例如处理流a依赖节点流b,应该先关闭处理流a,再关闭节点流b。当然完全可以只关闭处理流,不关闭节点流,处理流关闭的时候,会调用其处理的节点流的关闭方法,如果将节点流关闭以后再关闭处理流,会抛出IO异常。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值