`

黑马程序员__IO流

阅读更多
----------- android培训java培训、java学习型技术博客、期待与您交流! ------------


IO流
IO流就是输入输出流(InputStream OutputStream)。

IO分类
按照流向分:
输入流
输出流
按操作数据分:
字节流
字符流

根据IO流的分类IO流有四个抽象基类
字节流抽象基类
InputStream
OutputStream
字符流抽象基类
Reader
Writer


流操作通用流是字节流,字符流是基于字节流的。
字符流每次读取一个字符,也就是两个字节。
字符流操作文本文件跟方便。
字符流融合了编码表。

IO流是用于操作数据的。
最常见的体现形式是操作文件。

FileWriter:字符文件输出流类。

FileWriter的是用
1.创建一个FileWriter对象。
  new FileWriter(String name);new FileWriter(String name,boolean bln);
  FileWriter类没有空构造函数,该对象被初始化时必须要明确被操作的文件。
  而且该文件会被创建到指定目录下。如果该目录下已有同名文件,将被覆盖(调用第一个构造函数)。
  其实该步骤就是要明确数据要存放的位置。

2.调用write方法,将字符写入到流中。
  write(字符数据);可以是单个字符、字符数组或者字符串。
3.刷新流缓冲区中的数据,将数据刷新到指定的文件中。
  flush()
4.关闭流,关闭流的同时会自动调用一次刷新,将数据刷新到指定的文件中。
  close()

close()和flush()方法的区别
flush刷新后,流可以继续使用。
close刷新后,流会被关闭。

FileReader:字符文件输入流类。

FileReader的使用
1.创建一个FileReader对象。
  new FileReader(String name);
  创建一个文件读取流对象,和指定名称的文件相关联。
  要保证该文件是已经存在的,如果不存在会发生FileNotFoundException
2.调用read方法,读取字符。
  read():一次读取一个字符,而且会自动往下读。
3.关闭流。
  close()

文本文件两种读取方式
1.一次读取一个字符
2.创建字符缓冲区,将缓冲区可容纳的字符数读取到缓冲区中,
  在对缓冲区进行操作。缓冲区长度建议是1024的倍数。

第一种方式范例:
FileReader fr=new FileReader("demo.java");
int data=0;
while((data=fr.read()!=-1)
{
	System.out.print((char)data);
}

第二种方式
FilerReader fr=new FileReader("demo.java");
char[] buf=new char[1024];
int len=0;
while((len=fr.read(buf)!=-1)//read(buf)返回读取的字符数,如果读取到文本结尾返回-1
{
	System.out.print(new String(buf,0,len));
}


字符缓冲输出BufferedWriter
缓冲区的出现是为了提高流的操作效率而出现的。
jvm默认的缓冲区大小是64兆。
所以在创建缓冲区之前,必须要有流对象。

字符缓冲输出BufferedWriter流使用
1.创建一个字符写入流对象
2.将需要被提高效率的流对象作为参数传递给缓冲流的构造函数。
3.写数据
4.刷新数据,将缓冲流中的数据刷新到指定的目标。
5.关闭缓冲区,其实就是在关闭缓冲区中的流对象。

该缓冲流提供了一个跨平台的换行符
newLine()方法。

字符流在读取的时候,底层也是在用字节流将读取到的两个字节存放到缓冲字节数组中,
然后去相应的编码表中查找字符,所以字节数组用到了缓冲,
因此在读取字符的时候需要flush。

范例:
FileWriter fw=new FileWriter("buf.txt");
BufferedWriter bufw=new BufferedWriter(fw);
bufw.write("abcd");
bufw.newLine();
bufw.flush();
bufw.write("efghi");
bufw.flush();
bufw.close();


字符读取缓冲流BufferedReader类:

该类提供了一个一次读取一行的方法,方便于对文本数据的获取。
当返回null时,表示读到文件末尾。

字符读取缓冲流的使用:
1. 创建一个读取流对象和文件相关联。
2. 为了提高效率,加入缓冲技术,将字符读取流对象作为参数传递给缓冲流的构造函数。
3. 读取字符数据
4.关闭字符缓冲流。

缓冲字节流
缓冲流中封装了缓冲数组,在写数据时当数组写满时一次写入制定目标中。
如果没有写满需要用flush或者close方法刷新缓冲流。

BufferedInputStream:FileterInputStream的子类

BufferedOutputStream:FileterInputStream的子类

缓冲字节流的使用:
1.创建缓冲字节流对象,构造函数接受一个字节流对象。
2.使用缓冲字节流对象进行读写
3.关闭缓冲字节流

注意:
如果没有对缓冲字节流设定缓冲区大小,
写文件的时候关闭流之前必须要flush,
否则写入的字节数是0.



IO流的异常处理
因为IO在操作时,流的读写和关闭流都可以发生异常,所以流的读写和关闭三部分都要进行异常处理。

IO异常处理范例:
FileWriter fw=null;
FileReader fr=null;
try//捕获异常
{
	fw=new FileWriter("demo_copy.java");
	fr=new FileReader("demo.java");
	char[] buf=new char[1024];
	int len=0;
	while((len=fr.read(buf)!=-1)
	{
		fw.write(buf,0,len);
	}

}
catch(IOException e)//异常处理
{
	//这里往往还会进行一些其他操作后抛出异常,比如读写过程中发生异常把复制没有完成的文件删除。
	 throw new IOException("读写错误");
}
finally//关闭流资源,读写流都要关闭而且都要捕获异常可以分别捕获,也可以将一个捕获放到另一个捕获的finally里面处理。
{
	try
	{
		if(fr!=null)
			fr.close();

	}
	catch(IOExcption e)
	{
		//...
	}
	finally
	{
		try
		{
		   if(fw!=null)
                              fw.close();
		}
		catch(Exception e)
		{
                        //...
		}
	}
}


File类
用来将文件或文件夹进行封装的类。

IO流操作文件的时候只能操作文件数据
而File对象还可以操作文件信息。

File类的默认字段
static String separator:跨平台的分隔符。与系统同有关的默认名称分隔符。

File类构造函数
File(String pathname):pathname文件路径
File(String parent,String child):parent父目录,child文件名称。
File(File parent,String child):parent父目录File对象,child文件名称。

创建File对象
1.将a.txt封装成File对象,可以将已有和未出现的文件或文件夹封装成对象。
File f1=new File("a.txt");
File f2=new File("c:\\abc","b.txt");
File parent=new File("c:\\abc");
File f3=new File(d,"c.txt");
//用跨平台的serperator作为分隔符
File f4=new File("c:"+File.separator+"abc"+File.separtor+"d.txt");

File类常见方法:
1.创建
  boolean createNewFile():在指定位置创建文件,如果该文件已经存在,则不创建,返回false。
   和输出流不一样,输出流对象已建立创建文件。而且文件已经存在,会覆盖(一个参数的构造函数)。
2.删除
  boolean delete():删除失败返回false。
  void deleteOnExit():在程序退出时删除指定文件。常用于临时文件删除。
3.判断
  boolean exists():文件是否存在。判断是否是文件或者是否是目录时,必须先判断文件或目录是否存在。
  isFile():
  isDirectory();
  isHidden():是否是隐藏文件
  isAbsolute():是否是绝对路径。
4.获取信息
  getName():
  getpath():
  getParent():该方法返回的是绝对路径中的父目录。如果获取的是相对路径对象,返回null。
      如果相对路径中有上一层目录那么返回上层目录。

  getAbsolutePath();
  long lastModified():
  long length():

  File[] listRoots():获取机器上所有有效盘符的名称
  File[] listFiles():返回指定目录下所有的文件以及文件夹对象数组。
  其他方法请参照API
5.重命名
  renameTo(File):重命名文件,如果重命名后的文件路径与原路径不同,相当于剪切。

FilenameFilter过滤器接口
因为FilenameFilter过滤器接口只有一个抽象方法,所以可以创建FilenameFileter的匿名内部类实现accept方法实现文件过滤。String类的endsWith方法是否以某个字符串结尾可判断文件是否是某个类型。

java删除目录原理
在windows中,删除目录从里面往外删。
也就是删除文件夹里的所有文件后在删除文件夹。
既然是从里往外删除。就需要用到递归。
java删除不走回收站。

范例:
public static void removeDir(File dir)
{
	File[] files=dir.listFiles();
	
	for(int x=0;x<files.length;x++)
	{
		if(files[x].isDirectory())
			removeDir(files[x]);
		else
			System.out.println(files[x].toString()+"::"+files[x].delete());
	}
	
	System.out.println(dir+"::"+dir.delete());
}


Properties类
Properties是Hashtable的子类。
也就是说它具备map集合的特点。而且它里面存储的键值对都是字符串。
是集合中和IO技术相结合的集合容器。
加载配置文件时,文件数据需要有固定格式。

该对象的特点:
可以用于键值对形式的配置文件。

Properties存取
setProperty(String key,String value);
getProperty(String key);
Set<String> stringPropertyNames();返回此属性列表中的键集List列表。

存取Properties配置文件
load(InputStream is);加载配置文件信息,传入输入流对象,将配置数据加载到Properties对象中。
store(OutputStream os):保存配置文件信息,传入输出流对象,将修改信息从内存中保存到指定目标中。

PrintStream打印流
PrintStream打印流:
是OutputStream的子类,提供了打印方法,可以将各种数据类型都原样打印到指定的目标中去。

分析打印流
OutputStream的wirte方法是按字节类型写数据。
PrintStream打印流提供了打印方法,可以将各种数据类型都原样打印到指定的目标中去。

字节打印流:
PrintStream
构造函数可以接受的参数类型:
1.file对象。
  File file
2.字符串路径。
  String path
3.字节输出流。
  OutputStream os
  PrintStream(OutputStream out,boolean aotoFlush);布尔参数表示是否自动刷新

字符打印流
PrintWriter
构造函数可以接受的参数类型:
1.file对象。
  File file
2.字符串路径。
  String path
3.字节输出流。
  PrintWrite(OutputStream out),
  PrintWrite(OutputStream out,boolean aotoFlush);布尔参数表示是否自动刷新
4,字符输出流。
  PrintWirter(Writer w)
  PrintWirter(Writer out,boolean autoFlush);布尔参数表示是否自动刷新

另外字符打印流和字节打印流还可以接受file对象和字符集作为参数。

范例:
BufferedReader buf=new BufferedReader(new InputStreamReader(System.in));
PrintWriter out=new PrintWriter(new FileOutputStream("a.txt"),true);//为了保证自动刷新,如果只是传路径不会自动刷新
String line=null;
while((line=buf.equals(line))
{
	is("over".equals(line))
		break;
	out.println(line.toUpperCase());
}
out.close();
buf.close();



SequenceInputStream序列流(合并流)
SequenceInputStream序列流(合并流)
是InputStream的子类,将多个输入流对象合并(串联)成一个输入流流对象。

SequenceInputStream构造函数
SequenceInputStream(InputStream s1,InputStream s2);
SequenceInputStream(Enumeration<? extends InputStram> en);

合并文件范例:
用Vector获取Enumeration
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 SequnceInputStream(en);
FileOutputStream fos=new FileOutputStream("c:\\4.txt");
byte[] buf=new byte[1024];
int len=0;
while((len=sis.read(buf))!=-1)
{
	fos.write(buf,o,len);
}
fos.close();
sis.close();
因为Vector是同步的效率低,所以可以用ArrayList存放流对象,
迭代器结合匿名内部类创建Enumeration接口子类对象。
ArrayList<FileInputStream> arr=new ArrayList<FileInputStream>();
final Iterator<FileInputStream> it=arr.iterator();
Enumeration<FileInputStrem> en=new Enumeration(){
	public boolean HasMoreElements()
	{
		return it.hasNext();
	}
	public FileInputStream nextElement()
	{
		return it.next();
	}
};


切割并合并文件
假如一个图片文件是3M,切割三次。
void splitFile()
{
	FileInputStream fis=new FileInputStream("c:\\1.bmp");
	FileOutputStream fos=null;
	byte[] buf=new byte[1024*1024];
	int len=0;
	int count=0;
	while((len=fis.read(buf))!=-1)
	{
		fos=new FileOutputStream("c:\\splitfiles\\"+(count++)+".part");
		fos.write(buf,o,len);
		fos.close();
	}
	fis.close();
}

合并
void merge()
{
	ArrayList<FileInputStream> al=new ArrayList<FileInputStream>();
	for(int x=1;x<=3;x++)
	{
		al.add(new FileInputStream("c:\\splitfiles\\"+x+".part"));
		
	}
	Iterator<FileInputStream> it=al.iterator();
	Enumeration<FileInputStream> en=new Enumerationr<FileInputStream>(){
		public boolean hasMoreElements()
		{
			return it.hasNext();
		}
		public FileInputStream nextElement()
		{
			return it.next();
		}

	};
	SequenceInputStream sis=new SequenceInputStream(en);
	FileOutputStream fos=new FileOutputStream("c:\\splitfiles\\0.bmp");
	byte[] buf=new byte[1024];
	int len=0;
	while((len=sis.read(buf))!=-1)
	{
		fos.write(buf,0,len);
	}
	fos.close();
	sis.close();
}



对象序列化
对象序列化,也叫持久化,就是将内存中对象的状态永久性的存储起来,当需要的时候在去读取。
ObjectInputStrem
示例:
FileInputStream fis = new FileInputStream("t.tmp");
ObjectInputStream ois = new ObjectInputStream(fis);

int i = ois.readInt();
String today = (String) ois.readObject();
Date date = (Date) ois.readObject();

ois.close();


ObjectOutputStrem
示例:
FileOutputStream fos = new FileOutputStream("t.tmp");
        ObjectOutputStream oos = new ObjectOutputStream(fos);

        oos.writeInt(12345);
        oos.writeObject("Today");
        oos.writeObject(new Date());

        oos.close();

被序列化的对象所属的类必须实现Serializable接口,它是一个标识接口,标识类实例可以被序列化。

序列化时使用一个称谓SerialVersionUID的版本号与每个可序列化类相关联,该序列号在反序列化过程中用于验证序列化对象的发送者和接受者是否为该对象加载了与序列化兼容的类。如果接收者加载该对象的类的serialVersionUID与对应发送者的类的版本号不同,则反序列化将会导致InvalidClassException无效类型异常。

可序列化的类可以通过声明为serialVersionUID的字段(该字段必须是静态、最终(final)的long型字段)显示声明自己的serialVersionUID
static final long serialVersionUID=42L;

UID版本号是根据类成员信息获取的,静态和被transient修饰的成员不能被序列化。

RandomAccessFile类
该类不算是IO体系中的子类
而是直接集成自Object

但是它是IO包中成员,因为它具备读和写功能。
内部封装了一个数组,而且通过指针对数组的元素进行操作。
可以通过getFilePointer获取指针位置,
同时可以通过seek改变指针位置。

其实完成读写的原理就是内部封装了字节输入流和输出流。

通过构造函数可以看出,该类只能操作文件。
操作文件的模式:只读r,读写rw等四种。

如果模式为只读r,不会创建文件,会去读取一个已存在的文件,如果该文件不存在,会发生异常。
如果模式为读写rw,操作的文件不存在,会自动创建。如果文件已存在不会覆盖。

常用方法:
seek()指定对象中的指针位置。
skipBytes()跳过指定的字节数。
read(buf);读取数组缓冲区
write(string.getBytes());写入字符串

RandomAccessFile可以实现分段读写文件,多线程上传下载任务的实现。
可以使用多个线程分段读写文件来完成上传下载文件。


用于操作基本数据类型的数据流对象。
DataInputStream
DataOutputStream
构造函数
与相关的输入输出流相关联。
DataInputStream(InputStream in)
DataOutputStream(OutputStream out)

常用方法
readInt()
readChar()
readBoolean()
readUTF()

writeInt()
writeChar()
writeBoolean()
writeUTF()

readUTF()和writeUTF()按照UTF-8修改版编码读写字符串,按照8个字节读写(utf-8是6个字节gbk是4个字节),读写对应,否则会发生EOFException如果输入流在读取所有字节之前到达末尾异常,因为它以8个字节读取字符串。

什么时候用?
涉及到基本数据类型读写的时候用。


用于操作字节数组的流对象
ByteArrayInputStream
ByteArrayOutputStream
用于操作字节数组的流对象。

ByteArrayInputStream:在构造的时候,需要接受数据源,是一个字节数组。
ByteArrayOutputStream:在构造的时候,不用定义数据目的,因为该对象中已经内部封装了可变长度的字节数组。这就是数据目的地。

因为这两个流对象都操作的数组,并没有使用系统资源。
所以,不用进行close关闭。

在流操作规律讲解时:
源设备
键盘System.in,硬盘FileStream,内存ArrayStream
目的设备:
控制台Syste.out,硬盘FileStream,内存ArrayStream

另外还有字符数组流CharArrayReader和CharArrayWriter,字符串流StringReader和StringWriter。
用法与字节数组流类似。

字符的编码解码
字符流都融合了编码表,而大多数字符流都使用默认编码表。
打印流和转换流可以指定编码表。
String类的getBytes和构造函数可以指定编码。

编码表的由来
ASCII:美国标准信息交换码
用一个字节的7位就可以表示
ISO8859-1:拉丁码表。欧洲码表。
用一个字节的8位表示。(每个字节高位补1)
GB2312:中文编码表。(每个字节高位补1)
GBK:中文编码表的升级码表,融合了更多的中文文字字符。
    用两个字节表示一个文字。
Unicode:国际标准码,融合了多种文字。
所有文字都用两个字节来表示,java语言使用的就是unicode
UTF-8:最多用三个字节来表示一个字符字符。按照字节头标识来判断几个字节表示一个字符。
……其他码表

常见异常编码现象
如果用gbk写utf-8读出现?号
如果用utf-8写用gbk读出现其他文字。

编码:字符串变成字节数组。

解码:字节数组变成字符串。

String-->byte[] . str.getBytes(charsetName);
byte[]-->String.  new String(byte[],charsetName);

web编程时,tomcat服务器默认编码是ISO8859-1欧洲编码。
如果编码是gbk服务器解码出现乱码时,
重新编码在用gbk解码,这是通用的解决办法。


UTF-8标识头信息
UTF-8解码时会判断读取到的字节标识头信息,
如果读取到第一个字节最高位是0,表示一个字节来表示一个字符
如果读取到第一个字节最高位是110第二个字节高位是10,表示两个字节表示一个字符。
如果读取到第一个字节高位1110第二个字节高位10第三个字节高位10,表示三个字节表示一个字符。

UTF-8根据字节标识头信息来判断读取几个字节来表示一个字符。

“联通”文字乱码解析及解决
步骤:
1.新建一个记事本
2.打开记事本开头位置写入“联通”两个字
3.关闭记事本在重新打开会发现有个黑框,联通两个字没有显示。

这是因为记事本在解码字节数组时发现它符合unicode编码字格式,
所以记事本在解码的时候按照unicode码表去查表所以会出现乱码。

解决办法
1.开头不要写编码后字节数组符合unicode编码格式的文字。
2.开头加个空格保存然后在打开把空格去掉,再次打开的时候不会出现乱码,因为记事本已经指定了gbk解码,你在修改内容后打开它解码不会改变。

IO开发规范.
实际开发中IO流读写操作要分离,
以便于调试,同时也提高了可读性。
//从文件中读取数据
read2File();
//向文件中写数据
write2File();


----------- android培训java培训、java学习型技术博客、期待与您交流! ------------
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics