IO流 装饰设计模式 适配器设计模式
io流(input/output):二进制的数据。源源不断
作用:实现两个设备之间数据的传递。
设备:磁盘,内存,键盘,文件,网络(主机之外的网络),控制台
内存:(在描述数据的时候以内存为参考)内存是核心,将数据放入内存叫读入(input),对应的是输入流。将数据从内存放入网络叫写出,对应的是输出流(Output)。
1.分类:根据操作的方式;输入流和输出流
2.分类:根据数据的类型:字节流和字符流
- 字节流:传输的是字节,可以操作任意类型的数据。可以传输任意的数据
- 字符流:传输的是字节,不同点是在传输的过程中加入了编码的方式,让我们的操作更方便–文本。
字节流:字节输入流InputStream,字节输出流:OutputStream
字符流:字符输入流:Reader ,字符输出流:Writer
将数据写入文本
因为操作的是文本,所以使用的是字符流 。写入文件–写出流–FileWriter
步骤:
1.创建FileWriter对象并关联相应的数据。
Writer fileWriter = new FileWriter(“xxx.txt”);
注意1:如果只写文件的名字,不写具体的路径,默认路径是当前的工程
注意2:对于关联的文件,如果之前不存在,程序会自动创建一个,如果存在,回将原来的内容覆盖
注意3:可以自己指定路径,但是必须是保证路径是真实存在的,否则会报错—FileNotFountException(系统找不到指定的路径)
2.写:在执行write方法时,数据被临时保存放到了流对象的内部数组中,这个数组是一个字节数据,会默认去查编码表
fileWriter.writer(“chenchen”);
3.刷新–降临时的数组中的数据放入磁盘;
fileWriter.flush();
4.关闭流–两个功能:a:关闭流 b:刷新
注意4:当流对象使用完成后必须关闭
fileWriter.close();
注意5:当流对象关闭之后,不能再对进行操作,否则会报异常:Stream closed
文件的续写:FileWriter(String file,boolean value).但value为true的时候,不会将原来的内容覆盖。
读内容文本 (输入)
步骤:
1.创建FileReader对象
FileReader fileReader = new FileReader(“test2.txt”);
2.读(read();一个字符一个字符的读,每次读取一个字符;read(数组):一次读取多个字符,数组的作用:每次会将读出的字符放到这个数组中。
数组时临时存放数据的地方,我们会将读到的字符放回到临时数组中数组的大小决定了我们一次可以读到的字符,一般这个数组的大小为<=1kB
fileReader.reade()读的时候会默认向后移动一位,类似于指针。返回值是ASCll,当read()读到完的时候返回-1;
3.fileReader.close();
字符缓冲流:没有写的能力,真正完成写的是字符写出流。
可以将缓存流类好比如催化剂或高速的小车。
字符缓冲读入流:BufferedReader 没有读的能力
字符缓冲写出流:BufferedWriter 没有写的能力
public class Demo6 {
public static void main(String[] args) throws IOException {
//0.创建字符写出流对象
//1.创建字符缓冲写出流对象
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("text3.txt"));
//2.写
bufferedWriter.write("bingbing");
//windows系统 换行符 \r\n linux/unix 换行符 \n
//bufferedWriter.write("\r\n");//换行
bufferedWriter.newLine();//换行---支持跨平台
bufferedWriter.write("chenchen");
//3.关闭资源 a.关闭内部的写出流 b.关闭自己 c.刷新
bufferedWriter.close();
}
}
分析:模拟字符缓冲读入流:BuffereReader
1.要有一个属于流的体系
2.要有一个Reader类型的成员变量
3.要有一个带参数的构造方法接受外部传入的流对象
4.模拟readLine(),实现读一行的功能
5.关闭流
重要代码示范:
public String readLine() throws IOException {
//a.创建一个临时的可变字符串,用于装当前行的字符
StringBuffer stringBuffer = new StringBuffer();
//b.使用read()方法读
int num = 0;
while ((num = this.reader.read()) != -1) {
if (num == '\r') {
continue;
}else if (num == '\n') {
return stringBuffer.toString();
}else {
stringBuffer.append((char)num);
}
}
//当文本为空时
if (stringBuffer.length() == 0) {
return null;
}
//当文本只有一行,没有换行符
return stringBuffer.toString();
//增加效率的代码
}
\r\n是windons下面的换行符,在linux下面是\n,上面的判断其实是个隐式条件,因为是每次只能获取一个值,所以先判断如果是\r,直接进行下一次循环,如果是\n,表示已经读完一行了,返回这一行
装饰设计模式 —字符缓存读入流 BufferedReader的自己实现
基于已经实现的功能,提供增强的功能,装饰设计模式的由来就是对缓冲流的实现。
特点:从缓冲流的角度来讲
1.使流原来的继承体更加简单
2.提高原来的效率
3.由于是在原有的基础上提高增强的功能,所以他还要属于原来的体系
步骤:
1.让BTest继承自Test
2.在BTest内有一个Test类型的成员变量
3.通过BTest内一个带参数的构造方法接收外部传入的一个Test类型的对象,交给内部的Test的属性
4.在实现功能的时候,调用传入的Test类型的对象实现原有的功能,自己实现增强的功能.
适配器设计模式–通过可以变相的理解成装饰设计模式
**理解:**当Dog类想要实现Inter接口的一个方法的时候,如果直接实现Inter接口,就必须将所有的方法都实现,如果在Dog类与Inter接口之间插入一个类,让这个类去实现Inter接口的所有方法,作为这个类的子类只需要实现自己需要的方法。我们将中间的这个类就可以成为适配器类
LineNumberReader:是BufferdReader的子类,不能读,但是可以提高效率,特有功能:设置行号,获取行号
字节流 --传输的是字节,可以传输任意类型的数据
区别:字符流和字节流
字符流:----传输的是字符,只能传输字符
字符写出流 Writer
write() write(字符数组) write(字符串)
字符读入流 Reader
read() read(数组)
字符缓冲写出流 BufferedWriter
newline() 跨平台换行
字符缓冲读入流 BufferedReader
readLine() 读一行
字节流:----传输的是字节,可以传输任何类型的数据
字节输出流 OutputStream
字节输入流 InputStream
字节缓冲输出流 BufferedOutputStream
字节缓冲输入流 BufferedInputStream
字节缓冲流没有newline(),readline(),很多时候所以是把字节流转换成字节流来进行操作
标准输入流(System.in),标准输出流(System.out):
System.in:"标准"输入流。次流已打开并准备提供输入数据。通常,此流对应于键盘输入或者由主机环境或用户指定的另一个输入源。
如果不想让键盘充当输入源,可以通过setIn进行更换
注意点:InputStream inputStream = System.in;System是一个私有构造的类,而in在System类下面是 InputStream类型的final修饰的指向空类型的引用
1. InputStream inputStream = System.in;
//int num = inputStream.read();//把标准输入流的read方法称为阻塞式方法
它可以在控制台输入
控制台的光标会一直停留在输入的状态,而不是像普通的程序一样,执行完,就退出。这是为什么么呢?
InputStream OutputStream 和他们的缓冲器。 都相当于一条管道。 Buffer 相当于一个水池,或者说一个大的水管。当 System.in.read(); 的时候 管道会向来源方 抽水(索取一行字符串) 会一直等到有水来了为止。(阻塞,一直等待) 而管道那头连接的是 用户输入(System.in)。所以当你调用 System.in.read(); 的时候。 就相当于会向管道的那头System.in 索取一行数据。所以程序会一直循环跳到System.in那行,从没关闭的通道 里 获取System.in的用户输入。
2.注意:这里是临时更换,只能在当前程序中使用更换后的输入源输出源,如果在其他的程序中会自动变回原来的输入源输出源。
System.setIn(new FileInputStream(“D:\workspace\BigData1923N20\src\com\qf\test\Demo1.java”));
默认是从控制台输入,这里更改为从文件输入。原理是:它会调用的时候从磁盘里面COPY一份,这里修改只是对内存里面的进行修改,可以在内存里修改成功,还有一点就是,当你在接下里再调用对象的时候,还是这个对象(因为是单例)。但是你不能修改磁盘里面的。
转换流:实现的是从字节流到字符流,本身是字符流
InputStreamReader:输入转换流
OutputStreamWriter:输出转换流
模拟场景:使用字符缓冲输入流的readLine()和newLine方法实现字节流的功能
public class Demo7 {
public static void main(String[] args) throws IOException {
//1将标准字节输入流转成字符缓冲读入流
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
//2.将标准字节输出流转成字符缓冲写出流
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(System.out));
//3.读写
String data = null;
while ((data = bufferedReader.readLine()) != null) {
bufferedWriter.write(data);
bufferedWriter.newLine();
bufferedWriter.flush();
//当输入的内容是over时,结束
if (data.equals("over")) {
break;
}
}
bufferedReader.close();
bufferedWriter.close();
}
}
打印流 除了拥有输出流的特点之外,还有打印功能
字节打印流:PrintStream;
字符打印流:PrintWriter;
1.字节打印流支持的设备
File类型的文件
字符串类型的文件
字节输出流
2.字符打印流支持的设备
File类型的文件
字符串类型的文件
字节输出流
字符写出流
Properties:实际上是Map集合,存储的是属性,属性以键值对的方式存储,
Properties不需要泛型,它规定好都是String
优点:
1.以键值对的形式存储数据
2.内部针对属性的存储封装了大量的专有方法:load(读Properties类型文件),store(重新写道文件),list()
步骤:
- 创建对象:Properties properties = new Properties();
- 存入值:properties.setProperty(“a”,“java”);
- 根据key值取:properties.getProperty(“a”);
- 当前的key在Properties中不存在,会打印python;porperties.getProperty(“c”,“python”)
- 获取属性:properties = System.getProperties();
- 遍历
Set<String> set = properties.stringPropertyNames();
// Iterator<String> iterator = set.iterator();
// while (iterator.hasNext()) {
// String string = (String) iterator.next();
// System.out.println("key:"+string+" value:"+properties.getProperty(string));
// }
//增强for循环
for (String key : set) {
System.out.println("key:"+key+" value:"+properties.getProperty(key));
}
- 对系统属性的某个值进行修改:porperties.setProperty(“sun.jnu.encoding”,“UTF8”)
System.getProperties,是把磁盘里面的文件复制一遍。改变里面的信息,源文件不变。
重新获取一遍值(只存在一个properties对象单例)?/原理:会先到内存中找属性集合的对象,如果有,直接使用.如果没有,会重新初始化一个新的对象,并获取属性集合.
序列化:将短期存储的数据实现了长期存储,这个过程对应的流就是序列化流。
数据的存储分成2类:
1.短期存储:存放在内存中,随着程序的关闭而释放–·对象,集合,变量,数组
2.长期存储:存储在磁盘中,即使程序关闭了,数据仍然存在–文件
进行序列化的步骤:
1.创建一个类实现Serializale
2.使用对应的流将对象存入磁盘中—序列化 – ObjectOutputStream
3.使用对应的流将对象从磁盘中取出放回内存===反序列话–ObjectInputStream
4.关闭流i
注意点:序列化流在工作时也要关联对应的基本的输入流和输出流
**注意点:一个类如果没有实现Serizlizable,进行序列化的时候会报
* * 注意点:
* 1. ClassNotFoundException:当前的类没有找到
* 分析:将Person对象进行序列化之后,将Person类删除,再进行反序列化的时候出现了异常
* 原因:反序列化在执行的时候依赖字节码文件,当类没有了,字节码文件无法创建,反序列化失败
*
* 2.java.io.InvalidClassException 无效的类
* 出现的原因:没有声明自己的serialVersionUID,而使用系统的.在进行反序列化的时候,类被改动了,系统认为现在的类
* 已经不是原来的类了(在使用系统的id进行识别的时候,重写给Person设置了id),认为此类无效
*
* 3.使用系统的serialVersionUID与自定义的ID的区别?
* 使用系统的,序列化和反序列化,id不能手动设置,使用的是编译器默认生成的,一旦类发生了改动,id会重新赋值
* 使用自定义的,序列化和反序列化,id不会发生改变,所以当反序列化的时候,即使对Person类进行了一些改动,也能继续反序列化
*
* 4.总结序列化,反序列化工程的注意点:
* a.合理使用序列化流和反序列化流,要与输入流与输出流配合使用
* b.进行序列化的类一定要实现Serializable接口,只要实现了接口就可以序列化.包括集合,包装类等
* c.进行序列化的类要保证当前类与内部的类都要实现Serializable接口
*
*/
代码示例
class Person implements Serializable{
// /**
// * generated:由编译器自动生成的,后面加L表示long型
// */
private static final long serialVersionUID = -7224641225172644265L;
// /**
// * default:UID是由用户自己指定的,默认值是1L
// */
// private static final long serialVersionUID = 1L;
String name;
//int age;
public Person(String name) {
this.name = name;
}
public String toString() {
return "Person [name=" + name + "]";
}
}
public class Demo13 {
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
//对象的序列化
//objectWrite();
//对象的逆序列化流
objectRead();
}
//对象的序列化
// public static void objectWrite() throws FileNotFoundException, IOException {
//
// ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("test6.txt"));
// //objectOutputStream.writeInt(100);
// //对象序列化
// Person person = new Person("bingbing");
// objectOutputStream.writeObject(person);
// objectOutputStream.close();
// }
//对象的逆序列化流
//序列化的东西要通过逆序列化流读取
public static void objectRead() throws FileNotFoundException, IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test6.txt"));
// int value = objectInputStream.readInt();
// System.out.println(value);
Object object = objectInputStream.readObject();
System.out.println(object);
objectInputStream.close();
}
}
反序列化:将数据从磁盘放回内存
对象序列化:
文件类 Flie类,用来操作文件的路径(目录)
//创建File类的对象
//第一种方式:直接指定文件的绝对路径
File file1 = new File(“D:\workspace\BigData1923N21\src\com\qf\test\Demo1.java”);
//第二种:通过父路径和子路径的字符串形式
File file2 = new File(“D:\workspace\BigData1923N21”,“src\com\qf\test\Demo1.java”);
//第三种:先得到父路径的对象形式,再跟子路径拼接
File file3 = new File(“D:\workspace\BigData1923N21”);
File file4 = new File(file3,“src\com\qf\test\Demo1.java”);
// * 创建文件,在创建时,如果当前的文件已经存在了,不会覆盖
File file5 = new File(“D:\workspace\BigData1923N21\test1.txt”);
file5.createNewFile();
// * 创建单层路径,只能创建单层路径,只能创建目录
File file6 = new File(“D:\workspace\BigData1924N11\a\test2.txt”);
// * 创建多路径,也可创建单层目录,只能创建目录
file6.mkdirs();
// * 判断是否是文件
System.out.println(file6.isFile());//false
// * 判断是否是路径
System.out.println(file6.isDirectory());//true
// * 判断是否隐藏
System.out.println(file6.isHidden());//false
// * 获取最后修改文件的时间
long lastTime = file5.lastModified();
System.out.println(lastTime);
* 获取根目录(路径)-当前主机的所有的根目录
File[] files = File.listRoots();
for (File file : files) {
System.out.println(file);
}
System.out.println("***********************");
// * 获取指定目录下的文件或文件夹 特点:1.只获取的是当层的内容,不管子路径 2.只获取内容的名字,不是全路径
File file = new File(“D:\\workspace\\BigData1924N11”);
String[] strings = file.list();
for (String string : strings) {
System.out.println(string);
}
System.out.println("***********************");
//获取指定目录下的文件或文件夹 的全路径
File[] file2 = file.listFiles();
for (File file3 : file2) {
System.out.println(file3);
编码问题
中国的字符集:GBK/GBK2312
欧洲的:ISO8859-1
通用的:UTF-8
美国的:ASCLL
中文乱码出现的情况研究
* 编码 解码 结果
* GBK utf8 不可以(GBK2个字节,utf83个字节)
* GBK ISO8859-1 可以
* utf8 GBK 有时可以
* utf8 ISO8859-1 可以
* ISO8859-1 GBK 不可以(编码就出错了)
* ISO8859-1 utf8 不可以(编码就出错了)
*/