【Java】反射

反射概述

反射机制是 Java 语言提供的一种基础功能,赋予程序在运行时自省(introspect,官方用语)的能力。通过反射我们可以直接操作类或者对象,比如获取某个对象的类定义,获取类声明的属性和方法,调用方法或者构造对象,甚至可以运行时修改类定义。

Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作对象内部属性及方法。

反射最大的作用之一就在于我们可以不在编译时知道某个对象的类型,而在运行时通过提供完整的 “包名+类名.class” 得到。注意:不是在编译时,而是在运行时。

框架 = 反射 + 注解 + 设计模式

加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。

image-20210623202252209

反射技术常用在各类通用框架开发中。因为为了保证框架的通用性,需要根据配置文件加载不同的对象或类,并调用不同的方法,这个时候就会用到反射——运行时动态加载需要加载的对象。

动态语言 vs 静态语言

动态语言是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。主要动态语言:Object-CC#JavaScriptPHPPythonErlang

静态语言与动态语言相对应的,运行时结构不可变的语言就是静态语言。如JavaCC++

Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。 Java的动态性让编程的时候更加灵活。

反射相关类

  • java.lang.Class:反射的源头
  • java.lang.reflect.Method
  • java.lang.reflect.Field
  • java.lang.reflect.Constructor

反射优点和缺点

  • 优点:可以动态地创建和使用对象(也是框架底层核心)
  • 缺点:使用反射基本是解释执行,对执行速度有影响,并且可能破坏类的私有性
阅读全文

【JUC】synchronized 原理

synchronized 原理

Monitor

Monitor 被翻译为监视器管程,是 JVM 实现的对象。每个 Java 对象都可以关联一个 Monitor 对象,如果使用 synchronized(obj) 给对象 obj 上锁(重量级)之后,obj对象头Mark Word 就会保存指向其对应的 Monitor 对象的指针。

Mark Word 结构(运行时元数据)

1
2
3
4
5
6
7
8
9
10
11
12
13
|-------------------------------------------------------|--------------------|
| Mark Word (32 bits) | State |
|-------------------------------------------------------|--------------------|
| hashcode:25 | age:4 | biased_lock:0 | 01 | Normal |
|-------------------------------------------------------|--------------------|
| thread:23 | epoch:2 | age:4 | biased_lock:1 | 01 | Biased |
|-------------------------------------------------------|--------------------|
| ptr_to_lock_record:30 | 00 | Lightweight Locked |
|-------------------------------------------------------|--------------------|
| ptr_to_heavyweight_monitor:30 | 10 | Heavyweight Locked |
|-------------------------------------------------------|--------------------|
| | 11 | Marked for GC |
|-------------------------------------------------------|--------------------|

普通对象的完整对象头信息:

1
2
3
4
5
|--------------------------------------------------------------|
| Object Header (64 bits) |
|------------------------------------|-------------------------|
| Mark Word (32 bits) | Klass Word (32 bits) |
|------------------------------------|-------------------------|

数组对象的完整对象头信息:

1
2
3
4
5
|---------------------------------------------------------------------------------|
| Object Header (96 bits) |
|--------------------------------|-----------------------|------------------------|
| Mark Word(32bits) | Klass Word(32bits) | array length(32bits) |
|--------------------------------|-----------------------|------------------------|

重量级锁

当前线程在获取到锁后,就会将 objMark Word 内原本存储的信息(hashcodeage 等)暂存到 Monitor 对象中,然后在 Mark Word 中保存这个 Monitor 的物理地址 ptr_to_heavyweight_monitor(这样其他想加锁的线程就能通过该指针访问到 Monitor 对象),注意:该操作是通过 Unsafe 类保证 CAS 的原子性。同时 Monitor 对象中的 Owner 将指向该线程 Thread-0。

当解锁时,会再将暂存在 Monitor 中的 Mark Word 信息再 CAS 交换回 obj 的对象头中。

  • 轻量级锁会在锁记录中记录 hashCode 等值
  • 重量级锁会在 Monitor 中记录 hashCode 等值

加锁流程:

image-20220304151528662

  • 刚开始 Monitor 中 Owner 为 null
  • 当 Thread-2 执行 synchronized(obj) 就会将 obj 对象对应的 Monitor 的所有者 Owner 置为 Thread-2。一个 Monitor 中只能有一个 Owner
  • 在 Thread-2 上锁的过程中,如果 Thread-3,Thread-4,Thread-5 也来执行 synchronized(obj),就会进入EntryList 进入 BLOCKED 状态
  • Thread-2 执行完同步代码块的内容,然后唤醒 EntryList 中等待的线程来竞争锁,竞争是非公平的
  • 图中 WaitSet 中的 Thread-0,Thread-1 是之前获得过锁,但是主动调用了 wait() 方法而进入 WAITING 状态的线程。等待持有锁的线程调用 notify() 方法,就会随机从 WaitSet 中选取一个线程唤醒,并放入 EntryList 中阻塞等待当前线程释放锁后一起竞争锁
阅读全文

【Java】IO 流

File类

java.io.File类:文件和文件目录路径的抽象表示形式,与平台无关

  • File 能新建、删除、重命名文件和目录,但 File不能访问文件内容本身。 如果需要访问文件内容本身,则需要使用输入/输出流。
  • 想要在Java程序中表示一个真实存在的文件或目录,那么必须有一个File对象,但是Java程序中的一个File对象,可能没有一个真实存在的文件或目录。
  • File对象可以作为参数传递给流的构造器,指明读取或写入的"终点"。

Java路径:

  • 相对路径:相较于某个路径下,指明的路径。在某个Module下代码内的相对路径默认相对于当前Module
  • 绝对路径:包含盘符在内的文件或文件目录的路径
阅读全文

【Java】集合

集合框架的概述

集合、数组都是对多个数据进行存储操作的结构,简称Java容器。

Java 数组

数组在存储多个数据方面的特点:

  • 一旦初始化以后,其长度就确定了。
  • 数组一旦定义好,其元素的类型也就确定了。我们也就只能操作指定类型的数据了。比如:String[] arr;int[] arr1;Object[] arr2;

数组在存储多个数据方面的缺点:

  • 一旦初始化以后,其长度就不可修改。
  • 数组中提供的方法非常有限,对于添加、删除、插入数据等操作,非常不便,同时效率不高。
  • 获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用

数组存储数据的特点:有序、可重复。对于无序、不可重复的需求,不能满足。

Java 集合

Java集合类可以用于存储数量不等的多个对象 ,还可用于保存具有映射关系的关联数组。其可分为 CollectionMap两种体系。

  • Collection接口:单列集合,用来存储一个一个的对象
    • List接口:存储有序的、可重复的数据。包含ArrayListLinkedListVector
    • Set接口:存储无序的、不可重复的数据。包含HashSetLinkedHashSetTreeSet
  • Map接口:双列集合,用来存储一对(key-value)一对的数据。包含HashMapLinkedHashMapTreeMapHashtableProperties

Collection接口继承树

image-20210617161218475

Map接口继承树

image-20210617161305507

阅读全文

【Java】String

String 的基本特性

String:字符串,使用一对 "" 引起来表示。Java 程序中的所有字符串字面值(如"abc")都作为此类的实例实现。

1
2
String s1 = "hello";                // 字面量的定义方式
String s2 = new String("hello"); // new 对象的方式
  • String是一个final类,代表其不可被继承,从而保护其线程安全性(避免被子类继承后破坏线程安全性)
  • 字符串是常量,用双引号引起来表示。它们的值在创建之后不能更改
  • String对象的字符内容是存储在一个字符数组常量final char value[]中的
  • String实现了Serializable接口:表示字符串是支持序列化的。实现了Comparable接口:表示String可以比较大小

img

String 的不可变性

String:代表不可变的字符序列。简称:不可变性。

  1. 当对字符串重新赋值时,需要重新指定内存区域赋值,不能使用原有的value进行赋值。
  2. 当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
  3. 当调用Stringreplace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
  4. 通过字面量(如"abc")的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池
  5. 字符串常量池中是不会存储相同内容的字符串的。

img

阅读全文

【Java】Java 常用类

日期时间类

JDK 8 之前的日期时间类

  • System类中currentTimeMillis()
  • java.util.Date和子类java.sql.Date
  • SimpleDateFormat(线程不安全)
  • Calendar
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class DateTimeTest {

//1.System类中的currentTimeMillis()
@Test
public void test1(){
long time = System.currentTimeMillis();
//返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差。
//称为时间戳
System.out.println(time);
}

/*
java.util.Date类
|---java.sql.Date类

1.两个构造器的使用
>构造器一:Date():创建一个对应当前时间的Date对象
>构造器二:创建指定毫秒数的Date对象
2.两个方法的使用
>toString():显示当前的年、月、日、时、分、秒
>getTime():获取当前Date对象对应的毫秒数。(时间戳)

3. java.sql.Date对应着数据库中的日期类型的变量
>如何实例化
>如何将java.util.Date对象转换为java.sql.Date对象
*/
@Test
public void test2(){
//构造器一:Date():创建一个对应当前时间的Date对象
Date date1 = new Date();
System.out.println(date1.toString());//Sat Feb 16 16:35:31 GMT+08:00 2019

System.out.println(date1.getTime());//1550306204104

//构造器二:创建指定毫秒数的Date对象
Date date2 = new Date(155030620410L);
System.out.println(date2.toString());

//创建java.sql.Date对象
java.sql.Date date3 = new java.sql.Date(35235325345L);
System.out.println(date3);//1971-02-13

//如何将java.util.Date对象转换为java.sql.Date对象
//情况一:
// Date date4 = new java.sql.Date(2343243242323L);
// java.sql.Date date5 = (java.sql.Date) date4;
//情况二:
Date date6 = new Date();
java.sql.Date date7 = new java.sql.Date(date6.getTime());
}
}
阅读全文

【Java】多线程

基本概念

进程(Process) 是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。 在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。程序是指令、数据及其组织形式的描述,进程是程序的实体。

线程(thread) 是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

  • 进程:指在系统中正在运行的一个应用程序;程序一旦运行就是一个进程;进程——资源分配的最小单位
  • 线程:系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元执行流;线程——程序执行的最小单位

举例解释进程与线程:

  • IDEA 这个应用程序启动后就是一个单独的进程
  • IDEA 里的各种语法提示、错误报警等功能都是一个个线程并发运行

并发和并行的区别:

  • 并行:同一个时间点多个线程一起执行
  • 并发:同一个时间段多个线程交替执行

创建多线程

创建多线程有四种方式:

  • 继承Thread
  • 实现Runnable接口
  • 实现Callable接口
  • 使用线程池

方式一:继承 Thread 类

  1. 创建一个继承于Thread类的子类
  2. 重写Thread类的run() --> 将此线程执行的操作声明在run()
  3. 创建Thread类的子类的对象
  4. 通过此对象调用start()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 1. 创建一个继承于Thread类的子类
class MyThread extends Thread {
// 2. 重写Thread类的run()
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}

public class ThreadTest {
public static void main(String[] args) {
// 3. 创建Thread类的子类的对象
MyThread t1 = new MyThread();

// 4.通过此对象调用start(): ① 启动当前线程 ② 调用当前线程的run()
t1.start();

// 问题一:我们不能通过直接调用run()的方式启动线程,还会在原线程工作,因为必须调用其代理对象。
// t1.run();
// 问题二:再启动一个线程,遍历100以内的偶数。不可以还让已经start()的线程去执行。会报IllegalThreadStateException
// t1.start();

// 我们需要重新创建一个线程的对象
MyThread t2 = new MyThread();
t2.start();

// 如下操作仍然是在 main 线程中执行的。
for (int i = 0; i < 100; i++) {
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i + "***********main()************");
}
}
}
}

start() 方法底层是调用 native 修饰的 start0() 方法,其调用的并非JVM中的Java程序,而是本地的C语言程序执行线程任务,因此调用 t1.start() 方法时该线程并不一定立即执行,其执行时机由CPU决定。

阅读全文

【Java】基本数据类型

Java 数据类型

基本数据类型:

  • 整型:byte \ short \ int \ long
  • 浮点型:float \ double
  • 字符型:char
  • 布尔型:boolean

引用数据类型:

  • 类(class
  • 接口(interfac
  • 数组(array

容量

  • 整型:byte(1字节=8bit) \ short(2字节)\ int(4字节)\ long(8字节)
  • 浮点型:float(4字节) \ double(8字节)
  • 字符型:char(1字符=2字节)

注意:定义long型变量,必须以"l"或"L"结尾;定义float类型变量时,变量要以"f"或"F"结尾

整型常量默认为int类型,浮点类型常量默认为double类型。

自动类型提升

自动类型提升:当容量小的数据类型的变量与容量大的数据类型的变量做运算时,结果自动提升为容量大的数据类型。

byte 、char 、short --> int --> long --> float --> double

特别的:当byte、char、short三种类型的变量做运算时,结果为int型

floatdouble类型的变量相加时,结果为double类型。

强制类型转换

强制类型转换:从容量大的类型转换成容量小的类型。它是自动类型提升运算的逆运算。其中容量大小指的是,表示数的范围的大小。比如:float容量要大于long的容量

注意事项

  1. 情况1:编译不出错,因为右边的123456默认是int类型,转给long类型时为自动类型提升(小转大,不出错)
1
long num  = 123456;
  1. 情况2:编译出错——过大的整数。因为右边的数默认为int类型,但因为该数字过大超过了int类型的范围,所以会报错。此时需要再其后面加上"l""L"
1
2
3
4
long num = 12345678987654321;

// 正确:
long num = 12345678987654321L;
  1. 情况3:编译出错——不兼容的类型:从double转换到float可能会有损失。因为右边的12.3默认为double类型,不能直接转换,需要加上强制类型转换(float)12.3。此时需要再其后面加上"f""F"
1
2
3
4
5
float f1 = 12.3;

// 正确
float f1 = (float)12.3;
float f1 = 12.3F;
  1. 情况4:编译出错——不兼容的类型:从int转换到byte可能会有损失。因为此时的 1 默认是int类型,不能直接转换,需要加上强制类型转换。
1
2
byte b = 12;
byte b1 = b + 1;

进制转换

二进制转十进制细节:https://www.bilibili.com/video/BV1Kb411W75N?t=470&p=64

计算机底层使用补码的方式存储数据

基本数据类型使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
class VariableTest {
public static void main(String[] args) {
//1. 整型:byte(1字节=8bit) \ short(2字节) \ int(4字节) \ long(8字节)
//① byte范围:-128 ~ 127
//
byte b1 = 12;
byte b2 = -128;
//b2 = 128;//编译不通过
System.out.println(b1);
System.out.println(b2);
// ② 声明long型变量,必须以"l"或"L"结尾
// ③ 通常,定义整型变量时,使用int型。
short s1 = 128;
int i1 = 1234;
long l1 = 3414234324L;
System.out.println(l1);

//2. 浮点型:float(4字节) \ double(8字节)
//① 浮点型,表示带小数点的数值
//② float表示数值的范围比long还大

double d1 = 123.3;
System.out.println(d1 + 1);
//③ 定义float类型变量时,变量要以"f"或"F"结尾
float f1 = 12.3F;
System.out.println(f1);
//④ 通常,定义浮点型变量时,使用double型。

//3. 字符型:char (1字符=2字节)
//① 定义char型变量,通常使用一对'',内部只能写一个字符
char c1 = 'a';
//编译不通过
//c1 = 'AB';
System.out.println(c1);

char c2 = '1';
char c3 = '中';
char c4 = 'ス';
System.out.println(c2);
System.out.println(c3);
System.out.println(c4);

//② 表示方式:1.声明一个字符 2.转义字符 3.直接使用 Unicode 值来表示字符型常量
char c5 = '\n';//换行符
c5 = '\t';//制表符
System.out.print("hello" + c5);
System.out.println("world");

char c6 = '\u0043';
System.out.println(c6);

//4.布尔型:boolean
//① 只能取两个值之一:true 、 false
//② 常常在条件判断、循环结构中使用
boolean bb1 = true;
System.out.println(bb1);
}
}

整数类型扩展

整数进制:

  • 二进制:0b10,对应对应十进制2
  • 十进制:10
  • 八进制:010,对应十进制8
  • 十六进制:0x10,对应十进制16

多个不同整数类型的变量相加时,若其中有一个long类型,则相加结果为long;否则都为int类型。

浮点数类型扩展

浮点数float类型占32字节,double类型占64字节,其位数有限、是离散的、有舍入误差的,因此容易出现数值溢出的情况。两个浮点数可能很接近但不严格相等。floatdouble类型的变量相加时,结果为double类型。

使用浮点数时应注意,银行业务中不能使用float和double类型,要使用BigDecimal类进行判断。

1
2
3
4
5
6
7
8
9
10
11
12
// 理论上f和d应该相等
float f = 0.1f;
double d = 1.0 / 10;

// 但是结果输出false
System.out.println(f == d);

float f1 = 2158452165841256123584f;
float f2 = f1 + 1;

// 结果输出true
System.out.println(f1 == f2);

字符类型

编码类型:Unicode 2字节,范围0-65536

转义字符:

  • \t 制表符
  • \n 换行

【OpenCV】OpenCV 读取摄像头数据

通过cv::VideoCapture类读取外接设备视频流(摄像头或外接读卡器获取到的视频流)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
cv::VideoCapture capture(0);

if (!capture.isOpened())
{
printf("could not open camera...\n");
return -1;
}
cv::namedWindow("capture", WINDOW_AUTOSIZE);

cv::Mat frame;
int index = 0;

while (true)
{
if (!capture.read(frame))
break;

cv::imshow("capture", frame);
char c = waitKey(1);

if (c >= 49) {
index = c - 49;
}
if (c == 27) {
break;
}
}

capture.release();

【Qt】基于 Qt 显示 OpenCV 的 Mat 数据

读取图片

使用QFileDialog::getOpenFileName函数获取文件名:

1
2
3
QString filename = QFileDialog::getOpenFileName(this,tr("Open Image"),"",tr("Image File(*.bmp *.jpg *.jpeg *.png)"));
QTextCodec *code = QTextCodec::codecForName("gb18030");
std::string img_name = code->fromUnicode(filename).data();

使用cv::imread函数读取图片:

1
2
3
4
5
6
7
8
9
10
11
12
image_mat = cv::imread(img_name);

if(!image_mat.data)
{
QMessageBox msg_box;
msg_box.setText(tr("Image data is null"));
msg_box.exec();
}
else
{
//TODO: xxxx
}
阅读全文