0.回顾
- File的常用方法
- 其他略
1.IO流
1.1 IO流的简介
对于文件的读写操作需要使用IO流,Input和Output。
分类:
-
单位划分:
①字节流:InputStream和OutputStream,可以处理任何类型的文件
②字符流:Reader和Writer,文本文件 txt
-
流向划分
①输入流:InputStream和Reader
②输出流:OutputStream和Writer
输入和输出是相对而言的,存储设备—》内存 ,读的过程,输入流;内存—》存储设备,写的过程,输出流
-
角色划分
①节点流
②处理流
一个宗旨:IO流中只有四个抽象基类:InputStream、OutputStream、Reader、Writer
1.2 OutPutStream
通常使用FileOutputStream
常用的构造方法:
FileOutputStream(String name):
创建文件输出流以指定的名称写入文件。
FileOutputStream(String name, boolean append)
:创建文件输出流以指定的名称写入文件。
若使用第一个构造方法,每次写的内容都会覆盖(从头开始写)
若使用第二个构造方法,而且true,则向文件末尾追加内容
写的方法:
void write(byte[] b) :将b的数组写入到文件中,一般通过"zzzz".getBytes()
void write(byte[] b, int off, int len) :将b的数组,可以指定开始下标和长度
void write(int b) :一个一个字节的写 ----》基本不用。
注意:
①IO中流是一种资源,所以使用完之后,要将流关闭。
②若文件不存在,会自动创建
1.3 InputStream
通常使用:FileInputStream
常用的构造方法:
FileInputStream(String name)
常用的读取的方法:
int read() :从该输入流读取一个字节的数据。 效率较低,基本不用。
int read(byte[] b) :直接读取一个数组的数据量,类似shopping时的购物车。
注意:若文件内容正好是byte数组的整数倍,没有问题;若不是整数倍,内容会有错误
原因:byte[]数组,采用是覆盖策略,而不是清空策略;每次取的都是数组长度
解决:我们应该取读取的有效长度,而是不数组长度
//一个一个读取
int read;
while ((read=fis.read()) != -1) {
System.out.println((char)read);
}
byte[] buf = new byte[6];
// is.read(buf);
// System.out.println(new String(buf));
// is.read(buf);
// System.out.println(new String(buf));
int len; //保存真实读取的缓冲数组的长度
while ((len=is.read(buf)) != -1) {
System.out.println(new String(buf,0,len));
}
至于缓冲数组取多大,一般取决于文件大小,通常对于大文件,一般设置为1024的整数倍
int read(byte[] b, int off, int len) :读取数组的指定长度的数据量。
2.Reader和Writer
这两个抽象类,主要处理文本文件。适用范围没有字节流广泛。
2.1 Writer
常用的实现类:FileWriter
FileWriter的常用构造方法:
FileWriter(File file,[boolean append])
FileWriter(String filePath,[boolean append])
常用的方法:
abstract void flush() :刷新流。
void write(char[] cbuf) :写入一个字符数组。直接这么适用不多
abstract void write(char[] cbuf, int off, int len) :写入字符数组的一部分。 void write(int c) :写一个字符,几乎不用
void write(String str) :写一个字符串
void write(String str, int off, int len)
void close():为了避免空指针异常,应该判断是否为空,再去关闭
2.2 Reader
常用的实现类:FileReader
FileReader的常用构造方法:
FileWriter(File file)
FileWriter(String filePath)
常用的方法:
int read() :读一个字符,几乎不用
int read(char[] cbuf) :将字符读入数组。
abstract int read(char[] cbuf, int off, int len) :将字符读入数组的一部分
补充练习:实现文件内容的替换
大致需求如下:
源文件a.txt内容:我叫{name},来自{address}
目标文件b.txt内容:我叫{张三},来自{北京}
2.3 补充
System.out 返回值是PrintStream,本质还是一个OutPutStream,调用的PrintStream的print/println方法进行打印
ctrl+alt+b:查看某个类的实现类或子类
3.BufferedXxx和转换流
明确:在原有的基础流之上,又包裹了一层,所以,构造BufferedXxx系列的时候,需要传入对应的字节流和字符流对象
通过源码发现,里面的很多方法,都是使用
synchronized
修饰,线程安全的,
3.1 BufferedInputStream和BufferedOutputStream
//使用BufferedXxx缓冲流实现文件的复制
@Test
public void testBuffed() throws Exception{
InputStream is = new FileInputStream("f:\\software\\VMware-workstation-full-14.1.5-10950780.exe");
//1.需要创建缓冲流对象
BufferedInputStream bis = new BufferedInputStream(is);
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("3.exe"));
long startTime = System.currentTimeMillis();
//2.实现文件的copy
//读写过程
byte[] buf = new byte[1024*10];
int len; //用于记录读取到的数组元素的真实长度
while ((len = bis.read(buf)) != -1) {
bos.write(buf,0,len);
}
long endTime = System.currentTimeMillis();
System.out.println("耗时:"+(endTime-startTime));
}
3.2 BufferedReader和BufferedWriter
练习:
1,张三,18
2,李四,19
需求:在a.txt存储的是学生信息(学号、姓名、年龄)
将学生信息从a.txt中读取出来,封装成Student
然后存储到ArrayList或hashmap<Integer,Stu>中,
①遍历出来【使用Stream流处理】
②计算sum、avg、max、min
③找出姓名中有三的人有几个
④过滤出年龄大于10的人有哪些
使用BufferReader处理
package exer;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
/**
* @author azzhu
* @create 2019-12-28 10:40:55
*/
public class TestBufferRW {
public static void main(String[] args) {
List<Stu> stus = new ArrayList<>();
Map<Integer,Stu> map = new HashMap<>();
try(BufferedReader br = new BufferedReader(new FileReader("d:\\a.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("d:\\b.txt"))
) {
//读写过程,一行一行读取
String line = null;
while ((line = br.readLine()) != null) {
//需求处理line,将其切割出来,设置到对象的属性值
String[] split = line.split(",");
Stu stu = new Stu();
stu.set(Integer.parseInt(split[0]),split[1],Integer.parseInt(split[2]));
//将stu放入到集合中 ArrayList/Map
stus.add(stu);
map.put(stu.getId(),stu);
//将line原样写出去
bw.write(line);
bw.flush();
}
//遍历list【过滤age>18,最大age】和map
stus.forEach(System.out::println);
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Stu{
private Integer id;
private String name;
private int age;
public void set(Integer id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Stu{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
3.3 备注
- 在使用输出流的时候,使用flush进行刷新,保证内容全部过去
- 流的关闭顺序:先用后关,后用的先关
- 关闭流的时候,应该在finally块中关闭,而且要判断非空
3.4 转换流
将字节流转换为字符串流:InputStreamReader
和OutputStreamWriter
4.序列化和反序列化[了解]
目的:
①内存中的对象能否持久化保存?
②能否将对象在网络中进行传递?能否将对象保存到文件中
注意:序列化多个对象到文件中,读取的时候要避免如下一些异常:
EOFException
、OptionalDataException
序列化:将内存中的对象写到文件中
反序列化:将文件中的对象读取出来
前提:若是自定义的对象,必须实现
Serializable
,若没有实现该接口,抛出如下异常java.io.NotSerializableException: obj.Stu
我们使用ObjectInputStream和ObjectOutputStream
-
构造方法
ObjectInputStream(InputStream in)
ObjectOutputStream(OutputStream out)
-
常用的方法:readXxx和writeXxx
注意:
- 写和读什么类型的数据,使用具体的writeXxx和readXxx方法
- 写入的先后顺序应该跟读取的一致,即先写的先读出来(先进先出–队列)
/**
* 实现序列化和反序列化
* @author azzhu
* @create 2019-12-28 10:57:36
*/
public class TestObjectXxxStream {
@Test
public void testRead() {
try(ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:\\c"))) {
Object obj = ois.readObject();
Stu stu = (Stu) obj;
System.out.println(stu);
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
public void testRead2() {
try(ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:\\c"))) {
Object obj = ois.readObject();
List<Stu> stus = (List<Stu>) obj;
System.out.println(stus);
int value = ois.readInt();
System.out.println(value);
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
public void testSave2() {
List<Stu> list = new ArrayList<>();
try(ObjectOutput oos = new ObjectOutputStream(new FileOutputStream("d:\\c"));) {
Stu stu = new Stu();
Stu stu2 = new Stu();
stu.set(1001,"zs",20);
stu2.set(1002,"zss",28);
list.add(stu);
list.add(stu2);
//写一个集合到文件中
oos.writeObject(list);
oos.writeInt(10);
oos.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
public void testSave() {
try(ObjectOutput oos = new ObjectOutputStream(new FileOutputStream("d:\\c"));) {
Stu stu = new Stu();
stu.set(1001,"zs",20);
oos.writeObject(stu);
oos.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
}@Test
public void testRead() throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("b.txt"));
int num = ois.readInt();
Object obj = ois.readObject();
System.out.println(obj);
System.out.println(num);
ois.close();
}
@Test
public void testWrite() throws Exception{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("b.txt"));
Stu stu = new Stu(1001, "张三", 18);
oos.writeObject(stu);
oos.writeInt(10);
oos.flush();
oos.close();
}
5.注解[扩展了解]
- 明确:不写通用或者封装框架等底层代码,你用不到
- 能看懂注解(注解可以在哪使用)
注解:具有指定意义的标识符,有自己的语法格式
- 掌握自定义直接的格式
@interface
-
jdk常见的几种注解,比如@override/@Deprecated/@SuppressWarnings
-
注解可以在哪些地方使用
观察:
@Target({ElementType.METHOD})
-
在哪个时刻使用,运行/编译等等
观察:
@Retention(RetentionPolicy.RUNTIME)
,一般使用Runtime -
元注解/元信息/元数据
元注解:对我们的注解进行的说明/详细说明/细分的注解
元数据:xx.doc,存储位置、大小、后缀名、作者…
6.反射[掌握]
通过反射:可以洞悉类的一切内容,包括私有,Class
需要掌握的:
-
获取Class的实例 —获取类的内容,都通过这个实例
Class.forName(全类名)
-
对于方法的调用
Method类型 m
m.invoke(对象,参数列表)
-
对于构造器
使用构造器创建对象
//注意,应该根据构造方法的参数个数去调用对应的构造方法 if(constructor.getParameterCount() == 3) { Object obj = constructor.newInstance(1001, "理论", 20); System.out.println(obj); }
-
获取其他信息,比如实现了哪些接口、继承了哪些类、使用了哪些注解、获取类的泛型、为私有属性赋值…
package test08.TestReflect;
import org.junit.Test;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.ArrayList;
/**
* @author azzhu
* @create 2019-12-28 13:58:03
*/
public class TestAnnotation {
//获取某个类实现的接口
@Test
public void test08() throws Exception{
Class clazz = Class.forName("test08.TestReflect.Stu");
Class[] interfaces = clazz.getInterfaces();
for (Class anInterface : interfaces) {
System.out.println(anInterface.getName()+":"+anInterface.getSimpleName());
}
}
//获取方法上的注解
@Test
public void test07() throws Exception{
Class clazz = Class.forName("test08.TestReflect.Stu");
Method m1 = clazz.getMethod("m1", null);
Annotation[] annotations = m1.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation.annotationType());
}
}
//获取到注解
@Test
public void test06() throws Exception{
Class clazz = Class.forName("test08.TestReflect.Stu");
Annotation[] annotations = clazz.getAnnotations();
for (Annotation annotation : annotations) {
// if(annotation.annotationType().getClass()==MyAnnotation.class.getClass()) {
// //做一些处理
// }
System.out.println(annotation.annotationType());
}
}
//获取父类,以带泛型的父类 *****
@Test
public void test5() throws Exception{
Class clazz = Class.forName("test08.TestReflect.Stu");
//获取父类
Class superclass = clazz.getSuperclass();
System.out.println(superclass.getName()); //java.util.ArrayList
//获取带泛型的父类
Type genericSuperclass = clazz.getGenericSuperclass();
//java.util.ArrayList<test08.TestReflect.Stu>
System.out.println(genericSuperclass.getTypeName());
//获取父类中的泛型
ParameterizedType parameterizedType = (ParameterizedType)genericSuperclass;
//获取参数化类型的真实参数:test08.TestReflect.Stu
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.printf(actualTypeArgument.getTypeName());
//可以构建一个泛型对象 stu
}
}
//操作属性
@Test
public void test4() throws Exception{
Class clazz = Class.forName("test08.TestReflect.Stu");
Object obj = clazz.newInstance();
Field field = clazz.getDeclaredField("age");
field.setAccessible(true);
field.setInt(obj,10);
System.out.println(((Stu)(obj)).getAge());
}
//获取构造方法,以及构建对象
@Test
public void test3() throws Exception{
Class clazz = Class.forName("test08.TestReflect.Stu");
Constructor[] constructors = clazz.getConstructors();
// System.out.println(constructors.length);
Object obj = null;
for (Constructor constructor : constructors) {
//调用有参构造创建对象
if(constructor.getParameterCount() > 0) {
obj = constructor.newInstance(10);
}
}
System.out.println(((Stu)obj).getAge());
}
//获取Class实例的方式
@Test
public void test2() throws Exception {
//1.获取Class实例的方式1:常用的
//Class clazz = Class.forName("test08.TestReflect.Stu");
//2.通过类名去获取
Class<Stu> stuClass = Stu.class;
//3.通过对象获取:有些方法或属性无法操作
Stu<Stu> stu = new Stu<>();
Class<? extends Stu> clazz = stu.getClass();
}
//需要获取Class实例:体验版
@Test
public void test1() throws Exception{
//test08.TestReflect.Stu
//1.获取Class实例的方式1:常用的
Class clazz = Class.forName("test08.TestReflect.Stu");
//2.通过clazz创建对象
Object obj = clazz.newInstance();
//======方法操作
//1.获取所有方法
//Method[] methods = clazz.getMethods();
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
//System.out.println(method.getName());
//System.out.println(method.getReturnType());
if("m2".equals(method.getName())) {
method.setAccessible(true);
method.invoke(obj,10);
}
}
}
}
@MyAnnotation({"aa","bb"})
class Stu<Integer> extends ArrayList<Stu> implements Serializable {
private int age;
public Stu(){}
private void m2(int a) {
System.out.println("private method"+a);
}
public int getAge() {
return age;
}
public Stu(int age) {
this.age = age;
}
public void setAge(int age) {
this.age = age;
}
@MyAnnotation("m1")
public void m1() {
}
}
@Target({ElementType.METHOD,ElementType.TYPE}) //我们的注解可以使用的地方
@Retention(RetentionPolicy.RUNTIME)
//@Deprecated
@interface MyAnnotation {
String[] value();
}
7.socket编程[了解]
需求:模拟客户端可以一直给服务端发送消息,服务端显示客户端发送过来的消息。
TCP、IP四层协议 vs OSI七层模型对比
【面试】TCP和UDP的区别
①TCP:面向连接,安全可靠,数据不会丢失,文件下载,TCP的3次握手过程
②UDP:面向无连接,不保证数据是否丢失
网络通信三要素:协议 + IP地址 + 端口号
端口号:用两个字节表示的整数,它的取值范围是0~65535
面试题:http和https的区别
Socket 类:该类实现客户端套接字,套接字指的是两台设备之间通讯的端点 ,理解为端口号
ServerSocket:创建这个对象的时候,只需要传入端口号
Socket:需要ip地址/主机名+端口号
8.xml[了解]
需要掌握的内容:
- xml文件的作用:配置文件
- 知道xml文件里面的标签,通常是有约束的
- dom4j,知道是干啥用的即可,无需走一遍CRUD
- dom解析 vs sax解析的对比 ----了解
Extended Markup Language:可扩展标记/标签语言。文件的后缀名:.xml
标签:可以在里面封装内容
标签有2种
①需要在标签中封装(写)内容:一种有开始,有结束<age>19</age>
②不需要在标签中写内容:<aa/>
对于一些特殊字符的处理,若内容很多,可以将其放在 CDATA块中
<![CDATA[" 开始,由 "]]>
xml文件,里面的标签,通常都不是乱写的,通常是有限制的,我们称之为schema约束,或者是通过dtd文件进行约束
用处:
- 可以当成小型数据库使用,即存储数据
- 数据传输的格式【使用很少】,我们后面会使用json格式进行传输
- 配置文件
如何自定义一个xml文件[了解]
<?xml version="1.0" encoding="UTF-8" ?> <!--第一行是声明,告诉我们这是一个xml文件-->
<stus>
<!--通过自定义标签,存储数据,
1.如何将数据取出来封装成对象,保存到list中
2.如何将List/stu写到文件中
3.一切解析:先将xml文件变为Document对象,一切解析从根stus开始
1和2就是解析文件的问题:
有如下几种方式:
①jdk提供的原生方式,比较麻烦
②使用第三方框架 dom4j https://dom4j.github.io/,后面会学框架,实际一些框架对于xml文件的解析使用的就是dom4j
【小概率面试】xml两种解析方式的区别
dom解析 sax解析【Simple Api xml】
dom解析:将整个文档解析成一颗dom树,加载进内存,
若文件很大,会非常耗费内存
优点:对于查找某个节点非常方便,可以重复获取节点信息
sax解析:边读边解析,对内存消耗不大
缺点:对于多次获取某个标签的需求,需要重新读取
dom4j结合上述两种解析的优点
3.目前我们自定义的这个文件的标签很随意,没有任何的限制
实际上应该有一个约束,schema约束
即在我们后面写配置文件的过程中,不是任意标签都行
-->
<stu id="1001">
<name>张三</name>
<age>19</age>
</stu>
<stu id="1001">
<name>张三</name>
<age>19</age>
</stu>
</stus>
9.异常处理补充
- 之前我们关闭资源都是在finally块中,可以有一个简化的替代品:
try-with-resource
- 还可以使用JDK7优化后的 try-with-resource 语句,该语句确保了每个资源在语句结束时关闭。所谓的资源(resource)是指在程序完成后,必须关闭的对象。
@Test
public void testFileCopy2() {
try(FileInputStream fis = new FileInputStream("F:\\桌面\\VV1.rar");
FileOutputStream fos = new FileOutputStream("d:\\a.rar")
){
//1.创建输入流
//2.读写过程
long startTime = System.currentTimeMillis();
byte[] buf = new byte[1024*2];
int len = 0; //缓冲数组中真实的内容长度
while ((len=fis.read(buf)) != -1) {
//写的过程
fos.write(buf,0,len);
fos.flush();
}
long endTime = System.currentTimeMillis();
System.out.printf("耗时:"+(endTime-startTime));
}catch (Exception e) {
e.printStackTrace();
}
}
@Test
public void testFileCopy() {
FileInputStream fis = null;
FileOutputStream fos = null;
try{
//1.创建输入流
fis = new FileInputStream("F:\\桌面\\VV1.rar");
fos = new FileOutputStream("d:\\a.rar");
//2.读写过程
long startTime = System.currentTimeMillis();
byte[] buf = new byte[1024*2];
int len = 0; //缓冲数组中真实的内容长度
while ((len=fis.read(buf)) != -1) {
//写的过程
fos.write(buf,0,len);
fos.flush();
}
long endTime = System.currentTimeMillis();
System.out.printf("耗时:"+(endTime-startTime));
}catch (Exception e) {
e.printStackTrace();
} finally {
//3.关闭流
try {
if(fos != null) {
fos.close();
}
if(fis != null) {
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
JDK9中 try-with-resource 的改进,对于引入对象的方式,支持的更加简洁。被引入的对象,同样可以自动关闭,无需手动close,我们来了解一下格式。
10.Properties属性集
注意:文本中的数据,必须是键值对形式,可以使用空格、等号、冒号等符号分隔。
需求:写一个配置文件jdbc.properties
username=zs
pwd=111
通过Properties这个类,获取username的value
public class TestProperties {
public static void main(String[] args) {
try(FileInputStream fis = new FileInputStream("D:\\IDEA\\226\\day11_java\\src\\test05\\jdbc.properties");) {
Properties prop = new Properties();
//加载流
prop.load(fis);
//get(key):获取指定key的value值 *****
System.out.println(prop.get("username"));
System.out.println("==================");
//取到key的集合
Set<String> set = prop.stringPropertyNames();
for (String key : set) {
Object value = prop.get(key);
System.out.println(key +":"+value);
}
} catch (Exception e){
e.printStackTrace();
}
}
}
11. 练习
-
流的使用
假如d:/a.txt文件中有以下内容: 编号 名称 价格 数量 Product 01,咖啡,20.5,10 02,伴侣,30.8,2 03,咖啡杯,28,5 04,勺子,5.5,5 需求: 1、写程序读取文件中的数据 2、然后将商品信息按照价格由高到低排序 3、将排序结果输出到一个文件中:d:/price_order.txt 4、然后按成交总金额排序,并将排好的数据输出到文件:d:/amount_order.txt 5、最后输出整个文件中的商品信息的汇总成交金额,打印在控制台 01,咖啡,20.5,10 205 02,伴侣,30.8,2 xx 03,咖啡杯,28,5 04,勺子,5.5,5 bufferedreader bufferedwriter arraylist product collections.sort()
-
完成图片/音频/视频文件的复制,分别使用FileInputStream/FileOutputStream和缓冲流,结合缓冲数组大小的不同,测试耗费的时间
-
使用FileWriter和FileReader,实现文件的复制,同时做一些修改,具体如下
源文件a.txt内容:我叫{name},来自{address} 目标文件b.txt内容:我叫{张三},来自{北京} tips:a.txt的内容,放到StringBuilder/String中,replace() ---> 新的结果,写出去
-
需求:使用对象流,从某个文件中读取出stu的信息,然后封装成ArrayList,将ArrayList写出大到文件中;从序列化文件中,读取出stus信息,在将每个学生的信息写到某个文本文件中,格式跟原始数据一样
属性说明:学号 姓名 年龄
1001,张三,20 1001,张三,20 1001,张三,20
-
反射API的使用
①属性相关
②方法相关
③构造器相关
④带泛型的父类相关