------- android培训、java培训、期待与您交流! ----------
———————————————————————————————————————————————————
28、IO流
IO用来处理设备之间的数据传输注-->注-->流按操作数据分为两种:字节流和字符流
字符流其实就是字节流读取文字字节数据后,不直接操作而是先查指定的编码表。获取对应的文字。
再对这个文字进行操作,简单说就是字符流 == 字节流 +编码表
|——字节流抽象基类:
InputStream OutputStream
|——字符流的抽象基类
Reader Writer
|——IO异常处理
在try的外边创建流对象的引用变量,在try里边进行对象初始化。
在流对象关闭前要判断流对象是否为空,否则会发生空指针异常
|——注意,流对象使用完需要进行关闭(关闭流会刷新缓冲中的数据)。
|——字符流操作: 需要注意的是:这两个类是分别是InputStreamWriter 和 OutputStreamReader的子类
InputStreamWriter 和 OutputStreamReader的是继承Writer和 Reader类的
|--FileWriter 类
FileWriter(String fileName, //可以确定是否续写文件,续写为true
boolean append)
参数:
fileName - 一个字符串,表示与系统有关的文件名。
append - 一个 boolean 值,如果为 true,则将数据写入文件末尾处,而不是写入文件开始处。
|--FileReader类
|---int read() //读取单个字符。
在字符可用、发生 I/O 错误或者已到达流的末尾前,此方法一直阻塞。
用于支持高效的单字符输入的子类应重写此方法。
|---int read(char[] cbuf)//将字符读入数组。
在某个输入可用、发生 I/O 错误或者已到达流的末尾前,此方法一直阻塞。
FileRead中的read方法读到末尾 是 -1
public static void filereaderDemo_2()throws IOException {
FileReader fr = new FileReader("Demo.txt");
char [] buf = new char[2];
int len = 0; //len是实际读取到的字符个数。
这个地方有疑问--》 while((len = fr.read(buf))!=-1){ //不知道为什么,我写的时候这个地方的len一直都是2
System.out.print(len+"::"+new String(buf,0,len)); //将字符读入到数组。用的时候再将数组中的元素取出
}
}
|-- 字符流缓冲区: 为了提高效率 字符流缓冲区用的是char []ch = new char[1024];
这两个类的父类分别是Reader 和 Writer
BufferedReader BufferedWriter
|---BufferedWriter
将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。
FileWriter fw = new FileWriter("text.txt"); //文件是目的文件
BufferedWriter bufw = new BufferedWriter(fw);
bufw.write("abcdef");
bufw.close();//这里讲缓冲区流对象关闭。那么fw这个流对象也就关闭
|----void newLine() 这里相当于 System.getProperty("line.separator");
写入一个行分隔符。行分隔符字符串由系统属性 line.separator 定义,并且不一定是单个新行 ('\n') 符。
|---BufferedReader 它的read方法覆盖了Reader类的read方法。这个read方法就是高效的方法
从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。
BufferedReader的readLine方法读到最后是 null
|----String readLine() //注意这里的返回值是String //这个方法只有BufferedReader有
读取一个文本行。通过下列字符之一即可认为某行已终止:换行 ('\n')、回车 ('\r') 或回车后直接跟着换行。
|---需要注意的是:read方法的返回值类型
FileReader fr = new FileReader("1.txt");
BufferedReader bufr = new BufferedReader(fr);
FileWriter fw = new FileWriter("buf_copy.txt");
BufferedWriter bufw = new BufferedWriter(fw);
|----使用缓冲区赋值文本的两种方式:
// 方法一:
___________________________________________
int ch =0;
while((ch = bufr.read())!=-1){
bufw.write(ch);
}
// 方法二: //从缓冲区流对象直接从介质中读取一行
___________________________________________
String str = null;
while((str = bufr.readLine())!= null){
bufw.write(str);
bufw.newLine();
bufw.flush();
}
// 方法三: //直接从存储介质中读取和写入
___________________________________________
int ch; //tmp是读取到的数据
while((ch = fr.read())!=-1){
fw.write(ch);
}
// 方法四: 自己设置缓冲区
___________________________________________
char [] buf = new char[1024];
int len = 0; //len是读取到的长度。不可能超过缓冲区的长度
while((len = fr.read(buf))!=-1){
fw.write(buf,0,len);
}
___________________________________________
|--装饰设计模式
对一组对象的功能进行增强时,就可以使用该模式解决问题
|---装饰和继承都能实现一样的特点,进行功能的扩展增强
|---装饰类和被装饰类都必须所属同一个接口或者父类
|--LineNumberReader //跟踪行号的缓冲字符输入流
LineNumberReader 有setLineNumber(int)和getLineNumber()方法
FileReader fr = new FileReader("1.txt");
LineNumberReader lnr = new LineNumberReader(fr);
String line = null;
while((line = lnr.readLine())!=null){
System.out.println(lnr.getLineNumber()+":"+line);
}
|——字节流的操作 //基本操作与字符流操作相同
字符流用的缓冲区是 char []buf = new char[1024];
字节流用的缓冲区是 byte []buf = new byte[1024];
|--字节流对文件操作的是直接写入到了目的文件中。不需要flush操作
FileInputStream 有一个available()方法
返回下一次对此输入流调用的方法可以不受阻塞地从此输入流读取(或跳过)的估计剩余字节数。
byte [] buf = new byte[fis.available()];
fis.read(buf);
System.out.println(new String(buf));
|--字节流可以对媒体文件进行操作。
不要使用字符流操作媒体文件。因为它读取过后,会去查表,如果表中没有查到字符,会使用未知的区域表示
|--常用的几个流对象。
FileInputStream FileOutputStream //对文件操作
BufferedInputSream BufferedOutputStream //包装类,字节流缓冲类
|——键盘录入
System.in; 得到的是一个InputStream 对象 //这是一个阻塞方式
widows系统中\r对应的ASCII值是13
\n对应的ASCII值是10
这是系统的输入和输出设备。这个流对象是不需要关闭的
|——字节流和字符流的转换
class InputStreamReader
extends ReaderInputStreamReader 是字节流通向字符流的桥梁:
class OutputStreamWriter
extends WriterOutputStreamWriter 是字符流通向字节流的桥梁:
键盘录入可以这么写:
InputStream ips = System.in;
InputStreamReader ipsr = new InputStreamReader(ips);
BufferedReader bufr = new BufferedReader(ipsr);
等效于:
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
控制台输出可以这里写
OutputStream ops = System.out;
OutputStreamWriter opsw = new OutputStreamWriter(ops);
BufferedWriter bufw = new BufferedWriter(opsw);
同样的,等效于:
BufferedWriter bufw = new BufferedWriter(new OutputStream(System.out));
|--转换流:(这两个流对象都在字符流中)
InputStreamReader 字节到字符的桥梁 解码
OutputStreamWriter 字符到字节的桥梁 编码
|---什么时候使用转换流?
1、 源或者目的对应的设备是字节流,但是操作的却是文本数据,可以使用转换作为桥梁
提高对文本的便捷
2、 一旦操作文本设计到具体的指定编码表时,必须使用转换流
|---流的操作规律:
1、明确 源 和汇
源: InputStream Reader
汇: OutputStream Writer
2、明确 数据时否是纯文本数据
源:纯文本 Reader
不是 InputStream
汇:纯文本 Writer
不是 OutputStream
3、是否 需要额外功能:
1、(缓冲区)
需要:加上Buffered
2、(转换)
InputStreamReader 字节到字符的桥梁 解码
OutputStreamWriter 字符到字节的桥梁 编码
4、是否 需要指定编码表 (两个转换流对象都可以指定编码表)
Unicode统一使用两个字节表示文字 utf-8 中文是由3个字节来表示的
这个时候就体现出转换流的优势了
//编码过程 r如果么有指定编码表,使用的系统本地的默认编码表。
FileWriter fw = new FileWriter("1.txt");
等同于
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("1.txt"));
如果需要指定编码表
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("1.txt"),"GBK");
//解码过程
FileReader fr = new FileReader("1.txt");
等同于
InputStreamReader isr = new InputStreamReader(new FileOutputStream("1.txt"));
|——File类
用来将文件或者文件夹封装成对象
File类的几个字段:
static final char separatorChar
此字段被初始化为包含系统属性 file.separator 值的第一个字符。在 UNIX 系统上,此字段的值为 '/';
在 Microsoft Windows 系统上,它为 '\\'。
static final char pathSeparatorChar
此字段被初始为包含系统属性 path.separator 值的第一个字符。此字符用于分隔以路径列表 形式给定的文件序列中的文件名。在 UNIX
系统上,此字段为':';在 Microsoft Windows 系统上,它为 ';'。
|--File类的常见方法
|---获取
|---创建与删除
//文件的创建
File file = new File("file.txt");
boolean b = file.createNewFile(); //如果文件存在,返回false。
// 文件的删除
boolean b = file.delete(); //删除成功返回true
//文件夹的常见与删除
File file = new File("abc");
boolean b = file.mkdir();
// 如果文件夹中有文件,删除失败
注->注-> 如果创建的是多级目录,使用delete方法删除成功,但是删除的是最后一个文件夹
|---判断
// 可以使用listRoots方法获得根目录
File []files = File.listRoots();
for(File file :files){
System.out.println(file+"剩余空间:"+file.getFreeSpace()/1024/1024/1024+"G");
}
获取当前目录下的文件以及文件夹的名称,包含隐藏文件、
调用list方法的File对象中封装的必须是目录,否则会发生空指针异常
如果访问的系统级的目录也会发生空指针异常。
如果目录存在但是没有内容,会返回一个数组,但是长度为0
File file = new File("c:\\");
String []lists = file.list(); //调用list方法
for(String list :lists){
System.out.println(list);
}
list方法也可以使用过滤器。String[] list(FilenameFilter filter);
|----FilenameFilter是一个接口。
实现类需要实现accept方法
|——获取当前目录下的文件。两种方法:
String []lists = file.list(new FilterByJava(".txt"));//返回的是字符串数组
File []files = f.listFiles(new FilterByHidden());//返回的是File对象数组
|——递归
函数自身直接或者间接的调用到了自身
注意:
1、 递归一定要明确条件,否则很容易栈溢出
2、 递归的次数不易过多
|—— Properties 集合
Map
|--Hashtable
|---Properties
Map + IO = Properties
void list(PrintStream out)将属性列表输出到指定的输出流。此方法对调试很有用。
|——特点:
1、 该集合中的 键 和 值 都是字符串类型
2、 集合中的数据可以保存到流中,或者从流中获取
通常该集合用于操作以键值对形式存在的配置文件
|--Properties集合的键值对都是String类型的。
它有Map集合的特点。不可迭代。所以也要转化为Set集合或者是Enumeration
方法一:
Set<String> stringPropertyNames()
方法二:
Enumeration<?> propertyNames() 相对来说,这个方法不经常使用
|——PrintStream
提供好了打印方法可以对很多种素具类型进行打印,并保持数据的表示新华社
它不抛IoException
|--print方法 //将数据先变成字符串保持原样将基本数据打印到目的地
// 此方法保持了原有数据的表现形式
|--write方法 //只写最低8位
|——序列流 //可以实现文件的合并
SequenceInputStream表示其他输入流的的逻辑串联
构造方法:SequenceInputStream(Enumeration<? extends InputStream> e)
构造方法:SequenceInputStream(InputStream s1,InputStream s2)
它可以使两个流对象进行串联。
如果是多个,就要使用枚举:
方法一:
使用Vector集合,将流对象添加到集合中。得到一个枚举。
方法二:
使用List或者List的子类。添加到集合中。用迭代的方法转换为集合。方法如下:
// 内置类访问外部类的局部方法需要fianl
final Iterator<FileInputStream> iterator = list.iterator();
// 自定义了一个Enumeration
Enumeration<FileInputStream> en = new Enumeration<FileInputStream>() {
@Override
public boolean hasMoreElements() {
return iterator.hasNext();
}
@Override
public FileInputStream nextElement() {
return iterator.next();
}
};
方法三:
使用集合工具类,将list集合转换为枚举类型的
Enumeration<> en = Collections.enumeration(list);
|——文件的切割
思路:
文件的切割可以有两种方式:
1、 平均分割
2、 按照大小切割
public class SplitFileDemo {
private static final int BUFFER_SIZE = 1024*1024;
/**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
File file = new File("邱永传 - 十一年.mp3");
File destfile = new File("C:\\Users\\administrator\\desktop\\destfile");
splitFile(file,destfile);
}
public static void splitFile(File file,File destfile) throws IOException{
// 定义一个命名文件的基数
int num = 0;
//定义一个输入流对象
FileInputStream fis = new FileInputStream(file);
//定义一个1M的缓冲区
byte []buf = new byte[BUFFER_SIZE];
//定义目的对象
FileOutputStream fos = null;
//定义目的文件夹
// File destfile = new File("dest");
if(!destfile.exists())
destfile.mkdirs();
//文件的读写操作
int len = 0;
while((len = fis.read(buf))!=-1){
/*String []name = file.getName().split(".");
System.out.println(file.getName());*/
fos = new FileOutputStream(new File(destfile, "_"+(++num)+".mp3"));
fos.write(buf,0,len);
}
fos.close();
fis.close();
}
}
|——文件的合并
思路:
文件的合并需要用到序列流。
序列流中封装的是Enumeration
Enumeration 可以用集合工具类Collections 将list集合转换
List集合可以添加FileInputStream
FileInputStream中可以封装File对象
public class MergeFileDemo {
public static void main(String[] args) throws IOException {
// 合并文件
/**
* 思路:
* 多个输入流,一个输出流
* 使用SequenceInputStream
*/
File file = new File("C:\\Users\\administrator\\desktop\\destfile");
// 得到文件集合
List<FileInputStream> list =trimList(file);
mergeFile(list, file);
}
// 将文件加入到集合中
public static List<FileInputStream> trimList(File dirfile) throws IOException{
// 设置一个集合
List<FileInputStream> list = new ArrayList<FileInputStream>();
// 健壮性判断
if(!dirfile.isDirectory()){
list.add(new FileInputStream(dirfile));
return list;
}
// 得到当前文件夹的所有.mp3文件.使用过滤器
File []files = dirfile.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
// TODO Auto-generated method stub
return name.endsWith(".mp3");
}
});
for(File file :files){
System.out.println(file.getName());
list.add(new FileInputStream(file));
}
// System.out.println(list);
return list;
}
//将输入流集合。
public static void mergeFile(List<FileInputStream> list ,File destfile) throws IOException{
// SequenceInputStream操作的是Enumeration。所以讲list集合转化为Enumeration
Enumeration<FileInputStream> en = Collections.enumeration(list);
//得到了序列流对象
SequenceInputStream sis = new SequenceInputStream(en);
//设置目的对象
FileOutputStream fos = new FileOutputStream(new File(destfile,"mp3_mer.mp3"));
// 设置缓冲区
byte []buf = new byte[1024];
int len = 0;
while((len = sis.read(buf))!=-1){
fos.write(buf,0,len);
}
//关闭资源
sis.close();
fos.close();
}
}
|——IO包中的其他类
|--对象的序列化与反序列化//操作对象的数据流对象
1.ObjectOutputStream —— 对象的序列化
延长对象的生命周期
ObjectOutputStream 将 Java 对象的基本数据类型和图形写入 OutputStream。
只能将支持 java.io.Serializable 接口的对象写入流中。
所以要将对象写入文件。要将对象序列化也就是实现Serializable接口。这个叫做标记接口
|——
final void writeObject(Object obj)
将指定的对象写入 ObjectOutputStream。对象的类、类的签名,以及类及其所有超类型的非瞬态和非静态字段的值都将被写入。
可以使用 writeObject 和 readObject 方法重写类的默认序列化
2.ObjectInputStream —— 对象的反序列化
ObjectInputStream 对以前使用 ObjectOutputStream 写入的基本数据和对象进行反序列化。
final Object readObject()
从 ObjectInputStream 读取对象。对象的类、类的签名和类及所有其超类型的非瞬态和非静态字段的值都将被读取。
可以使用 writeObject 和 readObject 方法为类重写默认的反序列化。
|--Serializable 用于给序列化的类加入ID 号
用于判断类和对象是否是同一版本
|--translent //修饰对象的属性,可以变成瞬态的属性。不被写入流对象
非瞬态和非静态字段的值都将被写入
|——RandomAccessFile 类 //它有读写功能 它将字节输入流和输出流进行了封装
不是IO中的类
此类的实例支持对随机访问文件的读取和写入。
|--void write(int b) //写最低8位
向此文件写入指定的字节。从当前文件指针开始写入。
|--final void writeInt(int v)
按四个字节将 int 写入该文件,先写高字节。写入从文件指针的当前位置开始
注意:
这个类如果文件不存在则创建一个,如果存在,不进行覆盖。
如果文件中有数据,再次写入的时候还是从头开始写。是直接覆盖的。但是后边已有的数据不会改变
比如:
raf.write("张三".getBytes());
raf.writeInt(97);
raf.write("李四".getBytes());
raf.writeInt(99);
用记事本打开文件中
张三 a李四 c
然后更改为一下内容 //这次写还是从0指针开始写
raf.write("王五".getBytes());
raf.writeInt(102);
得到以下内容:
王五 f李四 c // 只是将前8个字节覆盖了
|——所以他可以直接操作指针访问读写位置
void seek(long pos)
设置到此文件开头测量到的文件指针偏移量,在该位置发生下一个读取或写入操作。
偏移量的设置可能会超出文件末尾。偏移量的设置超出文件末尾不会改变文件的长度。
只有在偏移量的设置超出文件末尾的情况下对文件进行写入才会更改其长度。
|——PipedInputStream 和 PipedOutputStream
不建议对这两个对象尝试使用单个线程,因为这样可能死锁线程。
管道流对象连接的两种方式:
方法一: //构造方法
PipedOutputStream(PipedInputStream snk)
方法二://流对象的方法
void connect(PipedInputStream snk)
|——DataInputStream 和DataOutputStream //操作基本数据类型的类
DataInputStream 对于多线程访问不一定是安全的。 线程安全是可选的,它由此类方法的使用者负责。
String readUTF() 和 void writeUTF(String str) //首先,通过 writeShort 方法将两个字节写入输出流,表示后跟的字节数
|——ByteArrayInputStream 和ByteArrayOutputStream //操作字节数组的流对象。 实际操作的是内存
|——CharArrayReader 和CharArrayWriter
注意:这四个流对象它不会抛IO异常。关闭此流无效。
|——编码和解码
简单的编码:
byte [] buf = "你好".getbytes("指定码表");// 如果没有参数,就是默认码表
for(byte b : buf){
System.out.println(b); 默认的是GBK
}
简单的解码:
String str = new String(buf,"指定解码表"); //如果码表参数为空。默认码表是GBK
|——字符截取的方法
用GBK编码截取
public static String cutStringByByte(String str,int len) throws IOException{
byte [] buf = str.getBytes("gbk");
int count = 0;
for (int i = len-1; i>=0; i--) {
if(buf[i] <0){
count++;
}else {
break;
}
}
if(count%2 == 0){
return new String(buf, 0, len,"gbk");
}
else {
return new String(buf, 0, len-1,"gbk");
}
}