------- android培训、java培训、期待与您交流! ----------
一、文件File
电脑中的数据最终体现形式为文件,在这个io包中唯一与文件相关的类就是File类,它是文件和目录路径名的抽象表示形式。根据面向对象思维,文件肯定有创建、删除、判断等等一些方法,我们可以查阅api文档来一一了解,在此就不赘述。我们用一个例子来演示File的用法,需求是列出指定路径下的所有后缀名为.java的文件。
package cn.itheima.blog6;
import java.io.File;
import java.io.FilenameFilter;
/**
* 将指定目录下的所有指定后缀名的文件打印到控制台
* 运用到递归的思想,同时结合文件名过滤
*/
//文件名过滤器
class FilterByNames implements FilenameFilter {
private String suffix;
public FilterByNames(String suffix) {
super();
this.suffix = suffix;
}
@Override
public boolean accept(File dir, String name) {
return name.endsWith(suffix);
}
}
public class FileFilterByJava {
public static void main(String[] args) {
//指定目录
File f = new File("E:\\code\\io");
//设定过滤器
FilterByNames filter = new FilterByNames(".java");
list(f, filter);
}
public static void list(File dir, FilterByNames filter){
//列出所有文件
File[] files = dir.listFiles();
//遍历文件
for(File file : files){
//若是目录则继续过滤,即递归思想
if(file.isDirectory()){
list(file, filter);
}
//通过文件名过滤器过滤文件,递归的临界点
if(filter.accept(file, file.getName())){
//打印文件名
System.out.println(file.getName());
}
}
}
}
上述代码用到了递归思想,递归是栈的应用。
递归:在方法定义中,直接或者简介地调用自身。
适用时机:用分解技术,将大问题分解成规模较小的同类问题,通过不断分解,找到最小及其临界点,然后一步步解决。
二:IO流
1、概述
IO流:I (input) ,O(output) 其中用流来连接,用来处理设备之间的数据传输,Java用于操作流的对象都在IO包中。
在程序中,所有的数据都是以流的方式进行传输或者保存的,程序需要数据是要适用输入流读取数据,而需要将一些数据保存起来时,就要适用输出流。
流的分类:
(1)按照流向分:输入流和输出流。
判断流向是以内存作为参照物的,如将硬盘中的数据弄到内存中,就是输入,而将内存中的数据弄到硬盘上,就是输出。
(2)按照数据分:字节流和字符流。
计算机中所存在的均是二进制,用字节流就可以操作所有数据,但是为了方便纯文本数据的操作,因此引入了字符流。其实,字符流读取文本数据后,不直接操作,而是先查指定编码表,获取对应的文本,再对文本其进行操作。字符流 = 字节流 + 编码表。因此,只要是处理纯文本数据,就要优先考虑使用字符流,除此之外都是用字节流。
2、流的四大顶层抽象基类
字节输入输出流:InputStream,OutputStream
字符读写流:Reader(输入),Writer(输出)
IO包中的由这四个类派生出来的子类名称都是以父类名作为后缀,子类的前缀名就是该对象的功能。几乎可以做到见名知意。
3、流的继承体系
(1)InputStream
ByteArrayInputStream、StringBufferInputStream、FileInputStream 是三种基本的介质流,它们分别从Byte 数组、StringBuffer、和文件中读取数据。
SequenceInputStream用于对流进行有序的合并,无对应的OutputStream。
ObjectInputStream用于对以前使用 ObjectOutputStream 写入的基本数据和对象进行反序列化。
PipedInputStream,管道输入流,应连接到管道输出流,一般用于多线程之间的通信。
FilterInputStream的子类均为装饰类,提供特殊功能,如高效输入的BufferedInputStream,基本java数据输入流DataInputStream等。
(2)OutputStream
ByteArrayInputStream、FileInputStream 是两种基本的介质流,它们分别向Byte 数组、和文件中写入数据。
ObjectOutputStream用于对象的持久化,其中的对象需要实现Serializable接口。
PipedOutputStream 管道输出流,连接管道输入流,一般用于多线程之间的通信。
FilterOutputStream的子类均为装饰类,提供特殊功能,如高效写的BufferedOutputStream,基本java数据输出流DataOutputStream,打印流PrintStream,可以向文件、流中打印数据,同时可以保持数据的表现形式,还不抛出IOException等。
(3)Reader
CharReader、StringReader 是两种基本的介质流,它们分别将Char 数组、String中读取数据。
PipedReader 是从与其它线程共用的管道中读取数据。
BufferedReader负责装饰其它Reader 对象,高效读。
InputStreamReader 是一个连接字节流和字符流的桥梁,它将字节流转变为字符流。FileReader 可以说是一个达到此功能、常用的工具类,在其源代码中明显使用了将FileInputStream 转变为Reader 的方法。
(4)Writer
CharWriter、StringWriter 是两种基本的介质流,它们分别将Char 数组、String中读取数据。
PipedWriter 是从与其它线程共用的管道中写出数据。
BufferedWriter负责装饰其它Writer对象,高效写。
InputStreamWriter是一个连接字节流和字符流的桥梁,它将字节流转变为字符流。FileWriter就是实现此功能的具体类
PrintWriter与PrintStream用法相似。
4、流对象选择的四部曲
(1)第一步:先明确源和目的
源:
文本:用Reader
字节:用InputStream
目的:
文本:用Writer
字节:用OutputStream
(2)第二步:明确是不是纯文本
是:用字符流;
不是:用字节流
(3)第三步:明确流体系后,通过设备来明确具体使用哪个流对象
源设备:
键盘:System.in
硬盘:文件流File
内存:数组流ArrayStream
目的设备:
键盘:System.out
硬盘:文件流File
内存:数组流ArrayStream
(4)第四步:明确是否需要其他功能
高效:Buffered+后缀名
合并:SequenceInputStream
序列化:ObjectInputStream
操作基本数据类型:DateInputStream,DateOutputStream
打印:PrintStream,PrintWriter
5、流的应用示例
需求1:将键盘输入的信息写入到文件中
package cn.itheima.blog6;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
public class KeyBordToFile {
/**
* 将控制台的信息写入到文件中
* 1、源与目的:
* 源:InputStream, Reader
* 目的:OutputStream, Writer
* 2、是否为纯文本:
* 源:否,用字节
* 目的:无所谓,字符字节都行,用字符
* 3、具体设备
* 源:键盘,System.in
* 目的:硬盘,File --->FileWriter
* 4、是否需要其他功能
* 需要高效
* BufferedReader、BufferedWriter
* 需要转换
* InputStreamReader
* @throws IOException
*/
public static void main(String[] args) throws IOException {
//高效读封装,因为是System.in为字节流,所以转为字符流
BufferedReader bufr = new BufferedReader(new InputStreamReader(
System.in));
//高效写入文件中
BufferedWriter bufw = new BufferedWriter(new FileWriter("console.txt"));
//流的常用操作,不断读,不断写
String line = null;
while ((line = bufr.readLine()) != null) {
if (line.equals("over"))
break;
bufw.write(line);
bufw.newLine();
}
//关流
if(bufr != null)
bufr.close();
if(bufw != null)
bufw.close();
}
}
需求2:对指定文件进行切割与合并
package cn.itheima.blog6;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.SequenceInputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
public class FileSpiltAndMerge {
public final static int SIZE = 1048576;
/**
* 文件的切割与合并,并且将切割的信息存入到配置文件中,
* 同时要根据配置信息进行合并。
*
* 将流与Properties,集合,File结合起来
* @throws Exception
*/
public static void main(String[] args) throws Exception {
// File file = new File("C:\\111.txt");
// spiltFile(file);
File dir = new File("C:\\partfiles");
merge(dir);
}
//文件切割,并将切割的信息存入到配置文件中
public static void spiltFile(File file) throws Exception{
//若传入文件为路径,抛出异常
if(file.isDirectory())
throw new RuntimeException("不是文件!!!");
//将文件关联上输入流
InputStream in = new FileInputStream(file);
//定义缓冲区
byte[] buf = new byte[SIZE];
int count = 0;
int len = 0;
OutputStream out = null;
//设置切割后文件的位置,若文件夹不存在,则创建
File dir = new File("C:\\partfiles");
if(!dir.exists())
dir.mkdirs();
//将文件进行切割
while((len = in.read(buf)) != -1){
out = new FileOutputStream(new File(dir, (count++) + ".part"));
out.write(buf, 0, len);
out.close();
}
//将切割的信息存入config文件中
Properties prop = new Properties();
prop.setProperty("filepath", file.getAbsolutePath());
prop.setProperty("filecount", count+"");
FileOutputStream fos = new FileOutputStream(new File(dir, "config.property"));
prop.store(fos, "spilt config");
//关流
if(fos != null)
fos.close();
if(in != null)
in.close();
}
public static void merge(File dir) throws Exception{
//不是路径,无法合并
if(dir.isFile())
throw new RuntimeException("不是路径!!!!");
//得到.property文件
File[] files = dir.listFiles(new FilterByName(".property"));
//若配置文件不唯一,则无法合并
if(files.length != 1)
throw new RuntimeException("配置文件不唯一,没法合并");
//新建Properties集合,用来读取设置信息
Properties prop = new Properties();
FileReader fr = new FileReader(files[0]);
prop.load(fr);
//读取文件路径
String filepath = prop.getProperty("filepath");
//读取文件个数
int count = Integer.parseInt(prop.getProperty("filecount"));
File[] parts = dir.listFiles();
//若目录中文件数与配置中读取的文件数不等,则抛出异常
if((parts.length - 1) != count)
throw new RuntimeException("文件个数不一致");
//将输入流添加到集合中
List<InputStream> list = new ArrayList<InputStream>();
FileInputStream fis = null;
for(File part : parts){
fis = new FileInputStream(part);
list.add(fis);
}
//将集合中的流转化为枚举类,用于后面的合并
Enumeration en = Collections.enumeration(list);
//合并流
SequenceInputStream sis = new SequenceInputStream(en);
//流的一般操作,不断读取合并流,不断写入文件
byte[] buf = new byte[1024];
int len = 0;
FileOutputStream fos = new FileOutputStream(filepath);
while((len = sis.read(buf)) != -1){
fos.write(buf, 0, len);
}
//关流
if(fr != null)
fr.close();
if(fos != null)
fos.close();
if(fis != null)
fis.close();
if(sis != null)
sis.close();
}
}
package cn.itheima.blog6;
import java.io.File;
import java.io.FilenameFilter;
public class FilterByName implements FilenameFilter {
private String suffix;
FilterByName(String suffix) {
super();
this.suffix = suffix;
}
@Override
public boolean accept(File dir, String name) {
return name.endsWith(suffix);
}
}
三、装饰设计模式
当一个类的功能不能满足于现在的需求时,我们就需要对这些类的功能进行增强,我们首先会想到的是继承。假设这一功能为缓冲,IO流中许多都需要加入缓冲功能,这样,IO流的体系就越来越臃肿不够灵活。那我们是否可以将缓冲当做一种技术,不将他与具体的IO流对象结合,而是对其进行单独封装,那个对象需要缓冲技术,就将该流对象与缓冲关联。
装饰设计模式的使用步骤:
(1)写一个类,继承被增强对象的父类,实现与被增强对象相同的接口
(2)定义一个变量,接受被增强的对象
(3)定义一个构造函数,接受被增强对象
(4)覆盖需要增强的方法
(5)对于不想增强的方法,直接调用被增强对象的方法。
以BufferedReader为例来了解装饰设计模式,BufferedReader用来装饰Reader用以高效写入,我们可以自定义一个高效类来模仿BufferedReader对象,代码如下
package cn.itheima.blog6;
import java.io.IOException;
import java.io.Reader;
//(1)写一个类,继承被增强对象的父类,实现与被增强对象相同的接口
public class MyBufferedReader extends Reader{
/**
* 高效读取字符
* 缓冲的原理:
* 其实就是从源中获取一批数据装进缓冲区中。
* 在从缓冲区中不断的取出一个一个数据。
*
*/
//(2)定义一个变量,接受被增强的对象
private Reader reader;
//定义一个数组作为缓冲区。
private char[] buf = new char[1024];
//定义一个指针用于操作这个数组中的元素。当操作到最后一个元素后,指针应该归零。
private int pos = 0;
//定义一个计数器用于记录缓冲区中的数据个数。 当该数据减到0,就从源中继续获取数据到缓冲区中。
private int count = 0;
//(3)定义一个构造函数,接受被增强对象
public MyBufferedReader(Reader reader){
this.reader = reader;
}
//(4)覆盖需要增强的方法
public int myRead() throws IOException{
if(count==0){
count = reader.read(buf);
pos = 0;
}
if(count<0)
return -1;
char ch = buf[pos++];
count--;
return ch;
}
//模仿BufferedReader的readLine()
public String myReadLine() throws IOException{
StringBuilder sb = new StringBuilder();
int ch = 0;
while((ch = myRead())!=-1){
if(ch=='\r')
continue;
if(ch=='\n')
return sb.toString();
//将从缓冲区中读到的字符,存储到缓存行数据的缓冲区中。
sb.append((char)ch);
}
if(sb.length()!=0)
return sb.toString();
return null;
}
//(5)对于不想增强的方法,直接调用被增强对象的方法。
@Override
public void close() throws IOException {
reader.close();
}
@Override
public int read(char[] cbuf, int off, int len) throws IOException {
return 0;
}
}
四、编码表
计算机中的文字全是二进制表示,但是为了适合人们阅读,就产生了编码表,将数值与文字一一对应,这样看不懂的二进制就变成了看得懂的明文。
常见的编码表:
ASCII:美国标准信息交换码,用一个字节的七位表示
ISO8859-1:拉丁码表,欧洲码表,用一个字节的八位表示
GB2312:中文编码表,用两个字节表示
GBK:中文编码表升级,融合录入更多的中文字符,用两个字节表示,为避免与美国重复,故两字节的最高位都是1,即汉字都是用负数表示
Unicode:国际标准码,融合了多种文字,所有文字都用两个字节表示
UTF-8:用一个字节到三个字节表示。
编码与解码:
编码:字符串变成字节数组:String-->getBytes()-->byte[]()
解码:字节数组变成字符串:byte[]-->new String(byte[],0,len)-->String