------- android培训、java培训、期待与您交流! ----------
IO流
IO抽象基类如下图所示:
由这四类派生出来的子类名称都是以器父类名作为子类名的后缀
如:
InputStream的子类FileInputStream;
Reader的子类FileReader。
专门用于操作文件Writer子类对象FileWriter。后缀名是父类名,前缀名是该流对象的功能。
//创建一个FileWriter对象。该对象一被初始化就必须要明确要操作的文件
FileWriter fw=new FileWriter("demo.txt");
//而且该文件会被创建到指定目录下。如果该目录下已有同名文件,将被覆盖,其实该步就是明确数据要存放的目的地
fw.writer("dsfahak");
//此步其实只是写到了内存中,即流里面去了
fw.flush();
//刷新流对象中的缓冲中的数据,即将数据刷到目的地
fw.close();
//关闭流资源,关闭之前会刷新一次
IO的异常处理:
FileWriter fw=null;
try{
fw=new FileWriter("demo.txt");
fw.writer("abc");
}
catch(Exception e){}
finally{
try{
if(fw!=null)
//需要判断流是否存在
fw.close();
//这个fw对象本身就会抛异常
}
catch(Exception e)
}
对IO的异常处理其实比普通异常处理麻烦一点,主要抓住哪里会抛异常,哪里需放在finally块中。
文件的续写: fw=new FileWriter("demo.txt",true);
换行符:\r\n
//创建一个文件读取流对象,和指定名称的文件相关联
FileReader fr=new FileReader("demo.txt");
//要保证该文件是已经存在的,如果不存在,会发生FileNotFoundException
//read()一次只读一个字符,而且会自动往下读,读至末尾则返回-1。
while((ch=fr.read())!=-1){
System.out.println((char)ch);
}
read(char[] cbuf)
//定义一个字符数组,用于存储读到的字符,该read(char[] cbuf)返回的是读到的字符个数
char[] buf=new char[1024];
int num=0;
while((num=fr.read(buf))!=-1){
//假如文件够大,num每次都是1024,直至小于1024后,再读一次才是-1
System.out.print(new String(buf,0,num));
//num为字符流每次读到的个数
//注意,print不能有ln一换行打印出来的东西就晕
}
练习:将文件复制
package com.itheima;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class FileCopy {
/**
* 文件的复制
*/
public static void main(String[] args) {
FileWriter fw=null;
FileReader fr=null;
try {
fr=new FileReader("E:"+File.separator+"1.txt");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
try {
fw=new FileWriter("D:"+File.separator+"1.txt");
char[] buf=new char[1024];
int len=0;
while((len=fr.read(buf))!=-1){
fw.write(buf,0,len);
}
} catch (IOException e) {
e.printStackTrace();
}
finally{
if(fr!=null)
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
finally{
if(fw!=null)
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
字符流的缓冲区(提高效率)
很多程序都有自己的缓冲技术,缓冲时,将数据存入内存,达到一定大小就写入硬盘。
BufferedWriter和BufferedReader
缓冲区的出现是为了提高流的效率而出现的,所以在缓冲区之前,必须要先有流对象:BufferedReader br=new BufferedReader(new FileReader("1.txt"));
注意:
该缓冲区(BufferedWriter)中提供一个跨平台的换行符newLine();
该缓冲区(BufferedReader)中提供了一个一次读一行的方法readLine(),读到文件末尾返回null。
//创建一个字符写入流对象
FileWriter fw=new FileWriter("buf.txt");
//为了提高字符写入流效率,加入了缓冲技术,主要将需要被提高效率的流对象作为参数传递给缓冲区的构造函数即可
BufferedWriter bufw=new BufferedWriter(fw);
bufw.write("abc");
bufw.newLine();
//记得要刷新
bufw.flush();
bufw.close();
//其实关闭缓冲区就是在关闭缓冲区的流对象
FileReader fr=new FileReader("buf.txt");
BufferedReader br=new BufferedReader(fr);
String line=null;
while((line=br.readerLine())!=null){
System.out.println(line);
//此处的print后要加ln,因为readLine()方法不返回换行符
}
br.close();
装饰设计模式:
当想要对已有的对象进行功能增强时,可以定义类,将已有的对象传入,基于已有的功能,并提供强大的功能,那么定义的该类称为装饰类。
装饰类通常会通过构造方法接收被装饰对象,并基于被装饰对象的功能,提供更强大的功能。
装饰和继承的区别如下图:
练习:模拟一个带行号的缓冲区
package com.ken;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
class MyNumberReader {
private Reader reader;
private int linenumber=0;
public int getLinenumber() {
return linenumber;
}
public void setLinenumber(int linenumber) {
this.linenumber = linenumber;
}
public MyNumberReader(Reader r){
this.reader=r;
}
public String readLine() throws IOException{
linenumber++;
StringBuilder sb=new StringBuilder();
int ch;
while((ch=reader.read())!=-1){
if(ch=='\r')
continue;
if(ch=='\n')
return sb.toString();
sb.append((char)ch);
}
if(sb.length()!=0)
return sb.toString();
return null;
}
public void close(){
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class Buffered {
public static void main(String[] args) throws FileNotFoundException {
MyNumberReader myr=new MyNumberReader(new FileReader("D://3.txt"));
String str;
try {
while((str=myr.readLine())!=null)
{
System.out.println(myr.getLinenumber()+":"+str);
}
} catch (IOException e) {
e.printStackTrace();
}
finally{
myr.close();
}
}
}
字节流:
FileOutputStream fos=new FileOutputStream("fos.txt");
fos.write("sdjas".getBytes());
fos.close();
//注意:字节流不用定时刷新
FileInputStream fis=new FileInputStream("fis.txt");
byte[] buf=new byte[1024];
int len=0;
while((len=fis.read(buf))!=-1){
System.out.print(new String(buf,0,len));
}
FileInputStream中有个available()方法,可以获得文件中有多少个字节。如果文件太大,禁用此方法创建这么大的数组对象。
字符编码:
编码:字符串变成字节数组。 String——>byte[] str.getBytes(charsetName);默认是GBK
解码:字节数组变成字符串。 byte[]——>String new String(byte[] byt,charsetName);
情景:A以GBK编码发给B,B以ISO8859-1解码成了????,要怎么处理?将????按ISO8859-1编码,然后再以GBK解码。
实际情况:Tomcat服务器中默认的是ISO8859-1,碰到乱码时,先编一次,再解一次。
以上方法不适用于两种编码都识别中文的。
解决“联通”问题:由于“联通”两个字的GBK码很符合UTF-8编码的规律,所以在以GBK编码存入时,再次解码会以UTF-8解出来变成乱码。
字节流的小知识点:
int read()方法为什么返回的是int类型而不是byte字节类型呢?而且读至末尾返回-1
原因:因为读取字节byte类型也有可能是-1
字节:byte为8位即 0000-0000
如果一开始就是-1或者是中间就是-1则是 1111-1111
为了保证此数据不是-1:
先把字节提升至32位的int 1111-1111 1111-1111 1111-1111 1111-1111
然后,再&256(&oxff)即 &0000-0000 0000-0000 0000-0000 1111-1111
读取键盘录入
System.out:对应的是标准的输出设备(默认控制台)
System.in:对应的是标准的输入设备(默认是键盘)
InputStream in=System.in;//注意in是字段
int buf=in.read();//此read()是阻塞式的
字符转换流:
当字节流需要用到readLine()或是newLine()方法的时候,可以使用转换流,将字节流转换为字符流:
InputStream in=System.in;//键盘录入
InputStreamReader isr=new InputStreamReader(in);//字节流转为字符流
BufferedReader bufr=new BufferedReader(isr);//字符流拿缓冲,提高效率
必须要会,背也要背下来:每次要用到键盘录入的时候
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
br.readLine();
流操作的基本规律:
最痛苦的就是流对象很多,不知道该用哪一个,通过两个明确来完成
1.明确源和目的
源:输入流。InputStream Reader
目的:输出流。OutputStream Writer
2.操作的数据是否是纯文本
是:字符流
否:字符流
3.当体系明确后再明确那个具体的对象,通过设备来区分
源设备:内存ArrayStream,硬盘FileStream,键盘System.in
目的设备:内存,硬盘,控制台System.out
练习:将一个图片文件中的数据存储到另一个文件中,复制文件(按三个明确的思路)
package com.itheima;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class PictureCopy {
/**
*复制图片文件,通过三个明确思路。
*为了代码简练易于阅读复习,所以暂时全抛异常了
* @throws IOException
*/
public static void main(String[] args) throws IOException {
//1.将文件从硬盘读取到内存,因为是图片不是文本,用InputStream,而且需要用到缓存
BufferedInputStream bis=null;
bis=new BufferedInputStream(new FileInputStream("f:"+File.separator+"1.jpg"));
//2.将内存中的数据逐次写到硬盘上,数据是字节的,所以用OutputStream,而且用到缓存
BufferedOutputStream bos=null;
bos=new BufferedOutputStream(new FileOutputStream("e:"+File.separator+"1.jpg"));
//3.目的和源都明确好后就开始来复制了
byte[] buf=new byte[1024];
int len=0;
while((len=bis.read(buf))!=-1){
//bos.write(buf);//这么写的话复制出来的图片文件会比较大
bos.write(buf,0,len);
bos.flush();//必须刷新,要不然写不到数据
}
//4.关闭资源
bis.close();
bos.close();
}
}
扩展:FileWriter使用的是默认的字符编码写文件,此默认是操作系统默认的编码(一般都是GBK)
把字节转换查找指定码表:OutputStreamWriter(OutputStream out,String charsetName)
通常涉及到字符编码转换时,需要用到转换流
InputStreamReaderder的子类FileReader其实就是在父类的基础上封装了固定的的码表GBK。
System类中
setIn(InputStream in):可将默认的键盘录入改变成in
setOut(OutputStream out):可将默认的控制台输出改变成out
如下可以将异常信息存储到文件日志中:
try{
int[] arr=new int[2];
System.out.print(arr[2]);
}catch(Exception e){
e.printStackTrace(new PrintStream("a.txt"));
}
其他流对象
ByteArrayInputStream:在构造对象的时候,需要接收数据源,而且数据源是一个字节数组。
ByteArrayOutpuStream:在构造对象的时候,不用定义数据目的,因为该流的对象中已经内部封装了可变长度的字节数组,这就是数据的目的地。
具体的查阅API
因为这个流对象都操作的是数组,并没有使用系统资源,所以不用进行close()关闭
相似的还有:
CharArrayReader和CharArrayWriter
StringReader和StringWriter
涉及到字符编码的主要以转换流为主。Java语言使用的就是Unicode(两字节)编码,UTF-8是unicode的升级版(三字节)
例:
OutputStreamWriter osw=new OutputStreamWriter(new FileOutputStream("UTF.txt",UTF-8));
osw.write("你好");//将”你好”查UTF-8码表,编码后存入UTF.txt中
osw.close();
自成一派的流:RandomAccessFile
此类自成一派,不继承IO的基类而直接继承Object,它是IO包的成员,支持对随机访问的文件进行读写操作
其内部分装了一个数组,而且通过指针对数组的元素进行操作
可以通过getFilePointer获取指针位置
同时可以通过seek改变指针的位置
其实其完成读写的原理是内部分装了字节输入输出流
通过其构造函数可以看出,该类只能操作文件,而且操作文件有模式:
RandomAccessFile(File file,String mode)
RandomAccessFile(String name,String mode)
例:
RandomAccessFile raf=new RandomAccessFile("ran.txt","rw");
raf.write("李四".getBytes());
raf.write(258);//258是四个字节的,write只写一个字节,即最低8位
raf.writeInt(258);//此方法才是写四个字节的
该对象的构造函数要操作的文件不存在,则会自动创建,如果存在则不会覆盖。如果模式是只读"r",则不会创建文件,会去读取一个已存在的文件,如果文件不存在则抛异常。
此RandomAccessFile可以应用在多线程文件复制,随机访问
数据流
DataInputStream和DataOutputStream
数据流允许应用程序以适当方式将基本 Java 数据类型写入输出流中。然后,应用程序可以使用数据输入流将数据读入。
能够操作数据的流,具体操作看文档。
对象输入输出流(对象的持久化)
将对内存中的对象存到硬盘上,被操作的对象需要实现Serializable(标记接口)。
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("obj.txt"));//一般不存做txt而存为object,因为打开了也是乱码
oos.writeObject(new Preson("李四","39"))//Person必须实现Serializable接口
oos.close();
ObjectInputStream ois=new ObjectInputStream(new FileOutputStream("obj.txt"));
Person p=(Person)ois.readObject();
System,out.println(p);//p需实现toString()方法
ois.close();
在Person类中,加入
public static final long serialVersionUID=42L;//将UID定义成固定的,防止类的被修改而用对象读取流读取失效。
静态是不能被序列化的,因为静态都在方法区
关键字:transient:修饰的成员变量虽然在堆中,单页不能被序列化,保证其值在堆中存在而不再文件中存在。
管道流
PipedInputStream和PipedOutputStream
输入输出可以直接进行连接,通过结合线程使用。
多线程技术+io=>管道流
序列流,合并流
直接一个代码说明:
package com.itheima;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.SequenceInputStream;
import java.util.Enumeration;
import java.util.Vector;
public class JoinStream {
/**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
Vector<FileInputStream> v=new Vector<FileInputStream>();
v.add(new FileInputStream("c:\\1.txt"));
v.add(new FileInputStream("c:\\2.txt"));
v.add(new FileInputStream("c:\\3.txt"));
Enumeration<FileInputStream> en=v.elements();
SequenceInputStream sis=new SequenceInputStream(en);//此构造方法需要用到迭代所以才找Vector
FileOutputStream fos= new FileOutputStream("c:\\4.txt");
byte[] buf=new byte[1024];
int len=0;
while((len=sis.read(buf))!=-1){
fos.write(buf,0,len);
fos.flush();
}
fos.close();
sis.close();
}
}
知识扩充:需要用到Enumeration,又不想用Vector时,在用其他List集合时:
fianl Iterator<FileInputStream> it=list.iterator();//由于是匿名内部类,所以要对返回的对象作fianl修饰
Enumeration<FileInputStream> en=new Enumeration<FileInputStream>(){//匿名内部类,用迭代器创建一个枚举对象
public boolean hasMoreElements(){
return it.hasNext();
}
public FileInputStream nextElement(){
return it.next();
}
}
打印流
该流提供了打印方法,可以将各种数据类型的数据都原样打印(输出流)
包括:
PrintStream和PrintWriter
构造函数有
PrintStream(File file);//文件对象
PrintStream(String str);//字符串路径
PrintStream(OutputStream os);//字节输出流
PrintWriter具有以上的构造函数还多一个字符输出流:PrintWriter(Writer writer);
例:
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
PrintWriter out=new PrintWriter(System.out,true);//加true能让println自动刷新
out.println();//打印流可以不用newLine()方法了
以下与IO相关的知识点
Properties是HashTable的子类(Utils包中)
它具备map集合的特点,而且它里面存储的键值对都是字符串,是集合和IO技术相结合的集合容器。
该对象的特点,可以用于键值对形式的配置文件
存储:
Properties prop=new Properties();
prop.setProperty("zs",80");
System,out,println(prop);
String value=prop.getProperty("zs");
遍历:
Set<String> names=prop.stringPropertyNames();
for(String s:names){
System.out.print(prop.getProperty(s));
}
想要将info.txt中键值对数据存到集合中进行操作
思路:
1.用一个流将info.txt关联读取
2.读取一行,将该数据用“=”进行切割str.split("=");
3.等号左边的作为键,右边作为值,存入到Properties集合中即可
另思路:
通过Properties中的load(Reader r)
store(OutputStream out,String comments)此方法能将内存中的prop设置到文件中,第二个参数为注释信息
凡是带“#”的都是Properties的注释信息
Properties的格式都是“键=值”。
用Properties列出系统的相关配置信息:
Properties prop=System.getProperties();
prop.list(new PrintStream("sys.info"));
File类
将文件或者文件夹封装成对象,方便对文件与文件夹的属性信息进行操作。
构造函数:
File f1=new File("c:\\asd\\a.txt");//构造指定的文件
File f2=new File("c:\\acd","b.txt");//可以先将路径明确了,文件名不确定可以用字符串变量表示
File d=new File(“c:\\sda”);//可以先将路径明确了,文件名不确定可以用字符串变量表示
File f3=new File(d,"c.txt");
目录分隔符(跨平台):File.separator注意,是字段
File常见的方法:
1.创建
boolean mkdirs();//创建多级文件夹
boolean mkdir();//创建目录,只能创建一级目录
boolean creatNewFile();//在指定位置上创建文件,如果该文件存在,则不创建,返回false,和输出流对象不一样,输出流对象一建立就创建文件,而文件已存在会被覆盖
2.删除
boolean delete();//就算放在finally中也有可能删除不了,原因是程序的占用
void deleteOnExit();//先声明(即无所谓放在代码那个位置,只要执行的到就好),退出程序就删除
3.判断
boolean canExecute();//判断是否可执行
boolean canRead();//判断是否可以读取
boolean canWrite();//判断是否可以修改
应用频率最高的:
boolean exists();//文件是否存在
boolean isDirectory();//判断是否是目录
boolean isFile();//判断是否是是文件
boolean isHidden();//判断是否是隐藏文件,尽量别访问隐藏文件
boolean isAbsolute();//判断文件是否是绝对路径
4.获取信息
getName();
getPath();
getParent();
getAbsoluteFile();
getAbsolutePath();
lastModified();
length();
renameTo(File file);//改文件名,相当于剪切
static File[] listRoots();//获得文件根列表
String[] list();//调用list方法的File类对象必须封装一个目录且必须存在
String[] list(FileNameFilter filter);//列出过滤后的文件
new FileNameFilter(){
public boolean accept(File dir,String name){
return name.endWith(".bmp");
}
}
例:删除一个带目录的文件夹
删除原理:在window中删除目录是从里面往外删除的,既然是从里往外,就需要用到递归。
package com.itheima;
import java.io.File;
class Delete{
public static void removeDir(File dir){
if(dir.exists()){
File[] files=dir.listFiles();
for(int x=0;x<files.length;x++){
if(files[x].isDirectory()&&!files[x].isHidden())
removeDir(files[x]);
else
System.out.println(files[x].delete());
}
System.out.println(dir+"___"+dir.delete());
}
}
}
public class DeleteFile {
public static void main(String[] args) {
File file=new File("c:\\test");
Delete.removeDir(file);
}
}
Java删除不走回收站的,Java无法访问隐藏文件,即无法删除隐藏文件。
函数的自身调用称为递归
递归时要注意:
1.限定条件
2.要注意递归的次数,尽量避免内存溢出