IO流
流的概念和作用
流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作
IO流分类
IO流主要的分类方式有以下3种:
- 按数据流的方向:输入流、输出流
- 按处理数据单位:字节流、字符流
- 按功能:节点流、处理流
1、输入流与输出流
输入与输出是相对于应用程序而言的,比如文件读写,读取文件是输入流,写文件是输出流,这点很容易搞反。
2、字节流与字符流
字节流和字符流的用法几乎完成全一样,区别在于字节流和字符流所操作的数据单元不同,字节流操作的单元是数据单元是8位的字节,字符流操作的是数据单元为16位的字符。
字节流和字符流的其他区别:
- 字节流一般用来处理图像、视频、音频、PPT、Word等类型的文件。字符流一般用于处理纯文本类型的文件,如TXT文件等,但不能处理图像视频等非文本文件。用一句话说就是:字节流可以处理一切文件,而字符流只能处理纯文本文件。
- 字节流本身没有缓冲区,缓冲字节流相对于字节流,效率提升非常高。而字符流本身就带有缓冲区,缓冲字符流相对于字符流效率提升就不是那么大了。
3、节点流和处理流
节点流:直接操作数据读写的流类,比如FileInputStream。
处理流:对一个已存在的流的链接和封装,通过对数据进行处理为程序提供功能强大、灵活的读写功能,例如BufferedInputStream(缓冲字节流)。
下图就很形象地描绘了节点流和处理流,处理流是对节点流的封装,最终的数据处理还是由节点流完成的。
在诸多处理流中,有一个非常重要,那就是缓冲流。
我们知道,程序与磁盘的交互相对于内存运算是很慢的,容易成为程序的性能瓶颈。减少程序与磁盘的交互,是提升程序效率一种有效手段。缓冲流,就应用这种思路:普通流每次读写一个字节,而缓冲流在内存中设置一个缓存区,缓冲区先存储足够的待操作数据后,再与内存或磁盘进行交互。这样,在总数据量不变的情况下,通过提高每次交互的数据量,减少了交互次数。
四个基础抽象类
字节输入流(Inputstream)
- InputStream 所有输入字节流的基类。抽象类。
- FileInputStream 读取文件输入字节流。
- BufferedInputStream 缓冲输入字节流。该类内部其实就是维护了一个8kb字节数组而已。该类出现的目的是为了提高读读取文件数据的效率。
字节输出流(Outputstream)
- OutputStream 所有输出字节流的基类。抽象类。
- FileOutputStream 向文件输出数据的输出字节流。
- BufferedOutputStream 缓冲输出字节流。该类出现的目的是为了提高向文件写数据的效率。该类内部其实也是维护了一个8kb的字节数组而已。
字符输入流(Reader)
- Reader 所有输入字符流的基类。抽象类。
- FileReader 读取文件字符的输入字符流。
- BufferedReader 缓冲输入字符流。该类出现的目的是为了提高读取文件字符的效率并且扩展了功能(readLine()),它内部其实就是维护了一个8192个长度的字符数组。
字符输出流(Writer)
- Writer 所有输出字符流的基类。抽象类。
- FileWriter 向文件输出字符数据的输出字符流。
- BufferedWriter 缓冲输出字符流。该类出现的目的是为了提高写文件字符的效率并且扩展了功能(newLine())。
转换流
目的是将字节流装转换为字符流(不能将字符流转换为字节流)。
- InputStreamReader(字节流对象):输入字节字符转换流
- OutputStreamWriter(字节流对象):输出字节字符转换流
对象流
ObjectOutputStream(对象输出字节流)
ObjectOutputStream可以将Java对象写入OutputStream。
public static void main(String[] args) throws Exception {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("d:/student.data"));
Student stu = new Student();
stu.name = "test";
stu.age = 12;
objectOutputStream.writeObject(stu);
objectOutputStream.close();
}
public static class Student implements Serializable {
public int age;
public String name;
}
ObjectInputStream(对象输入字节流)
ObjectInputStream性质与ObjectOutputStream一样,只不过它是从InputStream中读取对象。
public static void main(String[] args) throws Exception {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("d:/student.data"));
Student stuRead = (Student) objectInputStream.readObject();
objectInputStream.close();
System.out.println(stuRead.name);
System.out.println(stuRead.age);
}
public static class Student implements Serializable {
public int age;
public String name;
}
序列化/反序列化流
将对象转化为字节之后进行存储 — 序列化 — 持久化
将字节转化为对象的过程 — 反序列化
注意:
- 一个对象想要被序列化,那么它所对应的类必须实现接口 — Serializable — 这个接口中没有任何的方法和属性,仅仅起标志性作用
- 用static/transient修饰的属性不会被序列化
- 如果一个类产生的对象允许被序列化,那么这个时候这个类在编译的时候会根据当前类中的属性自动计算一个版本号。当反序列化的时候,拿着对象中的版本号和类中版本号做比较,如果相等,则说明这个对象是这个产生的,可以被反序列化。如果没有手动指定版本号,自动计算版本号,那么就意味着类每变动一次,版本号就要重新计算一次。为了让序列化出去的对象反序列化回来,需要手动指定版本号 — private static final long serialVersionUID
线程
线程就是一个应用程序的执行路径,从代码的角度来说就是一个接口。
线程和进程
线程
线程是依赖于进程存在的,多个线程之间是共享一块内存空间的,一个线程修改了数据其他线程读取到的就是修改后的数据。(进程中的小任务 ——多线程)
进程
进程是独立的应用程序,每个进程都有自己独立的内存空间,进程之间是不会共享数据的,当关闭一个进程时,不影响其他进程。(计算机中在执行的任务——在CPU上执行和计算。)
时间片
时间片是一个线程执行的时间长短,时间片是由操作系统来决定的。(一般是6个毫秒左右,不是绝对的)
注意:
- 一个核上往往只能执行一个进程中的一个线程。
- 计算机看起来像是在运行多个进程,实际上是因为在计算机中任务切换速度非常快,超过人的反应。
- 进程的执行在宏观上并行的,在微观上是串行的。
线程的状态
- 创建:当使用new创建了一个线程对象之后的状态。
- 就绪:指调用了start方法之后的状态。注意:线程调用了Start方法之后,并不一定立刻执行run方法。
- 运行:值得就是线程在执行run方法的状态。
- 中断: 指暂时停止执行状态,当中断状态解除后,线程又处于就绪状态。
- 死亡:值得就是run方法执行完毕之后的状态。
线程创建
- 写一个类继承Thread类,将要执行的逻辑放到run方法中,创建线程对象,调用start方法来启动线程执行任务。
- 写一个类实现Runnable接口,重写run方法,创建Runnable对象,然后将Runnable对象作为参数传递到Thread对象中,利用Thread对象来启动线程。
多线程的并发安全问题
多个线程同时执行,而多个线程在执行的时候是相互抢占资源导致出现了不合常理的数据的现象——多线程的并发安全问题。
注意:多线程在执行的时候是相互抢占,而且抢占是发生在线程执行的每一步过程中。
同步
同一时刻,我们希望只有一个线程进入方法执行,其他的线程必须在方法外等候,只有此线程执行完毕之后,其他线程才能进入方法。简单来说,在某个时间段内,只有一个线程来执行某段代码。
同步锁机制
利用synchronized ——同步代码块解决多线程并发安全问题 。
同步 —— 一段逻辑在同一时间只能有一个线程执行。
异步 —— 一段逻辑在同一时间能有多个线程执行。
同步一定是安全的吗? —— 是
安全一定是同步的吗? —— 不一定
异步不一定不安全。
不安全一定是异步的。
注意:从微观上而言,同步一定是安全的,安全也一定是同步的。从宏观上,同步一定是安全的,安全不一定是同步的。
需要一个锁对象 ——要求锁对象要被所有的线程都认识:共享对象,类的字节码(方法区是被线程所共享的),this(必须是同一个对象开启了多个线程)。
如果同步方法是一个非静态方法,那么以this作为锁对象;如果同步方法是一个静态方法,那么以当前的类作为锁对象。
死锁——由于锁之间相互嵌套并且锁对象不同导致线程之间相互锁死,致使代码无法继续往下 —— 避免死锁:统一锁对象,减少锁的嵌套。
活锁—— 这个资源没有被任何的线程持有占用,导致程序无法往下执行。
等待唤醒机制
wait,notify来调节线程的执行顺序。
注意:等待唤醒机制必须结合锁来使用,而且锁对象是谁就用谁进行等待唤醒。
eg:生产消费模型
public class WaitNotifyAllDemo {
public static void main(String[] args) {
Product p = new Product();
new Thread(new Producer2(p)).start();
new Thread(new Producer2(p)).start();
new Thread(new Consumer2(p)).start();
new Thread(new Consumer2(p)).start();
}
}
//生产者
class Producer2 implements Runnable {
private Product p;
public Producer2(Product p) {
this.p = p;
}
@Override
public void run() {
while (true) {
synchronized (p) {
while (p.flag == false)
try {
p.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 计算本次所能生产的最大数量
int max = 1001 - p.getCount();
// 计算本次实际的生产数量
int count = (int) (Math.random() * max);
// 计算本次提供的商品数量
p.setCount(count + p.getCount());
System.out.println("本次生产了" + count + "件商品,能提供" + p.getCount() + "件商品");
p.flag = false;
p.notifyAll();
}
}
}
}
//消费者
class Consumer2 implements Runnable {
private Product p;
public Consumer2(Product p) {
this.p = p;
}
@Override
public void run() {
while (true) {
synchronized (p) {
while (p.flag == true)
try {
p.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 计算本次消费数量
int count = (int) ((p.getCount() + 1) * Math.random());
// 计算本次剩余商品数量
p.setCount(p.getCount() - count);
System.out.println("本次消费了" + count + "件商品,剩余了" + p.getCount() + "件商品");
p.flag = true;
p.notifyAll();
}
}
}
}
线程的优先级
线程的优先级:1-10。
理论上,数字越大优先级越高,抢占到资源的概率就越大。
实际上,相邻的两个优先级的差别非常不明显。如果优先级差到5个单位及以上,则结果会相对明显一点点。
套接字
进行网络数据传输的一套API——本质上是可以在网络上使用流。
网络基本概念
- 七层模型:物理层、数据链路层、网络层 、传输层(UDP/TCP) 、会话层、 应用层、 表示层。 ( HTTP、FTP、POP3、SMTP … )
- IP地址:在网络中标记主机。IPv4 — 四组数表示一个IP地址,每一组数的取值范围是0-255 10.8.33.5 IPv6 — 六组数表示一个IP地址。
- 端口:计算机与外界交互的媒介 — 端口号 — 0~65535 — 0-1024。
- 域名:各个网站提供的便于记忆的标记 — news.baidu.com .org .edu。
- DNS解析服务器:将域名和IP地址进行对应的。
UDP
基于流的。不建立连接,不可靠。传输速度相对比较快的。需要对数据进行封包,每个包不超过64K大小。 适用于对速度依赖性比较强但是对可靠性依赖性比较低的场景——视频聊天 (DatagramSocket 、DatagramPacket)。
发送端:
- 创建套接字对象
- 准备数据包,将数据放入数据包中,并且绑定要发往的地址
- 发送数据包
- 关流
接收端:
- 创建套接字对象,并且绑定要接收的端口号
- 准备数据包
- 接收数据
- 关流
- 解析数据
TCP
基于流的。建立连接,经历三次握手,可靠。但是传输速率相对较慢。理论上不限制传输的数据的大小。适用于对可靠性的依赖性更高对速度依赖性较低的场景——文件传输。
注意:receive/connect/accept/write/read都会产生阻塞。
扩展:
BIO - Blocking IO - 同步式阻塞式IO。
NIO - New IO - NonBlocking IO - 同步式非阻塞式IO - JDK1.4。
AIO - Asynchronous IO - 异步式非阻塞式IO - JDK1.8。
客户端 - Socket
- 创建客户端的套接字对象
- 发起连接,绑定连接地址
- 获取自带的输出流,写出数据,禁用输出流
- 如果服务器端有打回的数据,则需要获取输入流读取数据,禁用输入流
- 关流
服务器端 - ServerSocket
- 创建服务器端的套接字对象,并且绑定监听的端口号
- 接受连接,获取到一个Socket对象
- 获取输入流,读取消息,禁用输入流
- 如果需要向客户端打回消息,则需要获取输出流写出数据,禁用输出流
- 关流
可变参数
- 可变参数允许传入的参数个数随意变化
- 可变参数本质是数组
- 一个方法中只能定义一个可变参数
- 这唯一的一个可变参数必须定义到参数列表的末尾
public class VariableDemo {
public static void main(String[] args) {
System.out.println(add(new int[] { 2, 8, 7, 1, 9, 3, 8 }));
// System.out.println(add(new int[] { 6, 8 }));
System.out.println(add(6, 8));
System.out.println(add(4, 7, 9));
// System.out.println(add(1));
// System.out.println(add());
}
// 可变参数允许传入的参数个数随意变化
// 可变参数本质是数组
// 一个方法中只能定义一个可变参数
// 这唯一的一个可变参数必须定义到参数列表的末尾
public static int add(int... is) {
int sum = 0;
for (int i = 0; i < is.length; i++) {
sum += is[i];
}
return sum;
}
}
//输出结果为:
38
14
20
反射
什么是反射
通过一个字符串(一个类的全类名)加载对应的类的字节码的过程就是反射。
(1)Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法。本质是JVM得到class对象之后,再通过class对象进行反编译,从而获取对象的各种信息。
(2)Java属于先编译再运行的语言,程序中对象的类型在编译期就确定下来了,而当程序在运行时可能需要动态加载某些类,这些类因为之前用不到,所以没有被加载到JVM。通过反射,可以在运行时动态地创建对象并调用其属性,不需要提前在编译期知道运行的对象是谁。
- Class ——代表字节码的类 —— 代表类的类
- Field—— 代表属性的类
- Method —— 代表方法类
- Constructor —— 代表构造方法的类
- Annotation ——代表注解的类
- Package —— 代表包的类
反射:在获取这个类的字节码的基础上来解剖这个类。
反射的原理
下图是类的正常加载过程、反射原理与class对象:
Class对象的由来是将.class文件读入内存,并为之创建一个Class对象。
反射的优缺点
优点:在运行时获得类的各种内容,进行反编译,对于Java这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。
缺点:
(1)反射会消耗一定的系统资源,因此,如果不需要动态地创建一个对象,那么就不需要用反射;
(2)反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。
反射的基本使用
1、获得Class:主要有三种方法
- Object–>getClass
- 任何数据类型(包括基本的数据类型)都有一个“静态”的class属性
- 通过class类的静态方法:forName(String className)(最常用)
package fanshe;
public class Fanshe {
public static void main(String[] args) {
//第一种方式获取Class对象
Student stu1 = new Student();//这一new 产生一个Student对象,一个Class对象。
Class stuClass = stu1.getClass();//获取Class对象
System.out.println(stuClass.getName());
//第二种方式获取Class对象
Class stuClass2 = Student.class;
System.out.println(stuClass == stuClass2);//判断第一种方式获取的Class对象和第二种方式获取的是否是同一个
//第三种方式获取Class对象
try {
Class stuClass3 = Class.forName("fanshe.Student");//注意此字符串必须是真实路径,就是带包名的类路径,包名.类名
System.out.println(stuClass3 == stuClass2);//判断三种方式是否获取的是同一个Class对象
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
注意,在运行期间,一个类,只有一个Class对象产生,所以打印结果都是true;
三种方式中,常用第三种,第一种对象都有了还要反射干什么,第二种需要导入类包,依赖太强,不导包就抛编译错误。一般都使用第三种,一个字符串可以传入也可以写在配置文件中等多种方法。
2、判断是否为某个类的示例
一般的,我们使用instanceof 关键字来判断是否为某个类的实例。同时我们也可以借助反射中Class对象的isInstance()方法来判断时候为某个类的实例,他是一个native方法。
public native boolean isInstance(Object obj);
3、创建实例:通过反射来生成对象主要有两种方法
(1) 使用Class对象的newInstance()方法来创建Class对象对应类的实例。
Class<?> c = String.class;
Object str = c.newInstance();
(2) 先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建对象,这种方法可以用指定的构造器构造类的实例。
//获取String的Class对象
Class<?> str = String.class;
//通过Class对象获取指定的Constructor构造器对象
Constructor constructor=c.getConstructor(String.class);
//根据构造器创建实例:
Object obj = constructor.newInstance(“hello reflection”);
注解
给程序看的解释——在Java中,所有的注解的父类是Annotation,注解中的属性只能是基本类型、枚举、String、Class、其他注解类型以及他们所对应的一维数组。
- 元注解 — 修饰注解的注解
- @Target — 限定注解的使用范围
- @Retention — 限定注解的生命周期
- @Documented — 限定这个注解在使用的时候能否产生到文档中
- @Inherited — 限定此注解可以作用在子类上