文章目录
- 硬基础
- 数据库相关
- 线程相关
- 简单数据结构与算法
- 网络编程
- 设计模式
- JMM
- 学习资料
- 问题
硬基础
位运算
假设有一个 int 类型的数,值为5,那么,我们知道它在计算机中表示为:
00000000 00000000 00000000 00000101
5转换成二制是101,不过int类型的数占用4字节(32位),所以前面填了一堆0。
在计算机中,负数以原码的补码形式表达。
原码->反码->补码
原码:一个正数,按照绝对值大小转换成的二进制数;一个负数按照绝对值大小转换成的二进制数,然后最高位补1,称为原码。
反码:正数的反码与原码相同,负数的反码为对该数的原码除符号位外各位取反。
补码:正数的补码与原码相同,负数的补码为对该数的原码除符号位外各位取反,然后在最后一位加1.
比如 00000000 00000000 00000000 00000101 是 5的 原码。
10000000 00000000 00000000 00000101 是 -5的 原码。
00000000 00000000 00000000 00000101 是 5的 反码。
11111111 11111111 11111111 11111010是-5的反码。
00000000 00000000 00000000 00000101 是 5的 补码。
11111111 11111111 11111111 11111011是-5的补码。
再举个栗子:计算机中 -1表示:
1、先取-1的原码:10000000 00000000 00000000 00000001
2、得反码: 11111111 11111111 11111111 11111110(除符号位按位取反)
3、得补码: 11111111 11111111 11111111 11111111
可见,-1在计算机里用二进制表达就是全1。16进制为:0xFFFFFF
位运算:
计算机在底层使用的是二进制补码进行运算。
适当使用位运算可以大量减少运行开销,优化算法。
位运算符:(7个)
&:与运算符
|:或运算符
~:非运算符
^:异或运算符
>>:右移运算符
<<:左移运算符
>>>:无符号右移运算符
例:-5 & 4练习
1、与(&)运算符,两个都为1时才为1,其他情况均为0
2、或(|)运算符,两个都为0时才为0,其他情况均为1
3、非(~)运算符,取反,即1变为0,0变为1
4、异或(^)运算符,相同值为0,不同值为1
5、右移(>>)运算符,m>>n,把m的二进制数右移n位,m为正数,高位全部补0,m为负数,高位全部补1
注意:在数字没有溢出的情况下:m>>n相当于m除以2的n次方,得到的数为整数时,即为结果,得到的数为负数时,根据m的有值两种情况
1.m为正数时,得到的商会舍弃小数位
2.m为负数时,得到的商会舍弃小数位,然后把整数部分+1得到结果
6、左移(<<)运算符,m<<n,把m的二进制数左移n位,高位超出n位都舍弃,低位补0(此时可能出现正数变负数)
注意:在数字没有溢出的情况下,对于整数和负数,m<<n相当于m乘以2的n次方。
7、无符号右移(>>>)运算符,m>>>n,整数m表示的二进制右移n位,不论正负数,高位都补0
进制转换计算
例如:30!转换为三进制有几个零?
30、 24、 21、 15、 12、 6、 3共有7个零;
27 有3个零;
18、9各有2个,共4个;
总计14.
正则表达式
\w
匹配字母、数字、下划线。等价于’[A-Za-z0-9_]‘。
\W
匹配非字母、数字、下划线。等价于 ‘[^A-Za-z0-9_]’。
\d
匹配一个数字字符。等价于 [0-9]。
\D
匹配一个非数字字符。等价于 [^0-9]。
[a-z]
字符范围。匹配指定范围内的任意字符。例如,’[a-z]’ 可以匹配 ‘a’ 到 ‘z’ 范围内的任意小写字母字符。
[^a-z]
负值字符范围。匹配任何不在指定范围内的任意字符。例如,‘[^a-z]’ 可以匹配任何不在 ‘a’ 到 ‘z’ 范围内的任意字符。
\s
匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。
\S
匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。
\n
匹配一个换行符。等价于 \x0a 和 \cJ。
\r
匹配一个回车符。等价于 \x0d 和 \cM。
Java中的基本数据类型以及占用的字节
数据类型 | 占用字节 |
---|---|
boolean | 不到1字节 |
byte | 1字节 |
short | 2字节 |
char | 2字节 |
int | 4字节 |
float | 4字节 |
double | 8字节 |
long | 8字节 |
补充: | |
1byte(字节)=8bit(位) | |
1、bit --位:位是计算机中存储数据的最小单位,指二进制数中的一个位数,其值为“0”或“1”。 | |
2、byte --字节:字节是计算机存储容量的基本单位,一个字节由8位二进制数组成。在计算机内部,一个字节可以表示一个数据,也可以表示一个英文字母,两个字节可以表示一个汉字。 |
四种访问修饰符以及作用范围
访问修饰符 | 类内部 | 本包 | 子孙类 | 外部包 |
---|---|---|---|---|
public | Y | Y | Y | Y |
protected | Y | Y | Y | N |
default | Y | Y | N | N |
private | Y | N | N | N |
java文件编译
java程序经过编译后会产生byte code即二进制文件,.class文件
java的编译过程先是java源程序扩展名为java的文件,由java编译程序将java字节码文件,就是class文件然后在java虚拟机中执行。机器码是由CPU来执行的。Java编译后是字节码, 电脑只能运行机器码。Java在运行的时候把字节码变成机器码。
编译后产生的.class文件个数:有多少个类,产生多少个.class文件
类加载器
1、什么是类加载器?
类加载器负责加载所有的类,其为所有被载入内存的类生成一个java.lang.Class实例对象。
2、类加载器有哪些?
JVM有三种类加载器:
(1)启动类加载器
该类没有父加载器,用来加载Java的核心类,启动类加载器的实现依赖于底层操作系统,属于虚拟机实现的一部分,它并不继承自java.lang.classLoader。
(2)扩展类加载器
它的父类为启动类加载器,扩展类加载器是纯java类,是ClassLoader类的子类,负责加载JRE的扩展目录。
(3)应用程序类加载器
它的父类为扩展类加载器,它从环境变量classpath或者系统属性java.lang.path所指定的目录中加载类,它是自定义的类加载器的父加载器。
3、说一下类加载的执行过程?
当程序主动使用某个类时,如果该类还未被加载到内存中,JVM会通过加载、连接、初始化3个步骤对该类进行类加载
4、JVM的类加载机制是什么?
JVM类加载机制主要有三种:
1、全盘负责
类加载器加载某个class时,该class所依赖的和引用其它的class也由该类加载器载入。
2、双亲委派
先让父加载器加载该class,父加载器无法加载时才考虑自己加载。
3、缓存机制
缓存机制保证所有加载过的class都会被缓存,当程序中需要某个class时,先从缓存区中搜索,如果不存在,才会读取该类对应的二进制数据,并将其转换成class对象,存入缓存区中。
这就是为什么修改了class后,必须重启JVM,程序所做的修改才会生效的原因。
==和equals的区别
对于引用数据类型 ,有equals和== 两种方法比较
== 比较的是引用,比较的是引用的地址值 ,equals方法,是object中的方法,如果不进行重写的话,比较的也是引用的地址值,实际和==一样。
String和Integer对equals方法进行了重写,比较的也是值。
参考链接
Integer和int
基本概念的区分:
1、Integer 是 int 的包装类,int 则是 java 的一种基本数据类型
2、Integer 变量必须实例化后才能使用,而int变量不需要
3、Integer 实际是对象的引用,当new一个 Integer时,实际上是生成一个指针指向此对象;而 int 则是直接存储数据值
4、Integer的默认值是null,int的默认值是0
Integer、new Integer() 和 int 的比较
1、两个 new Integer() 变量比较 ,永远是 false
2、Integer变量 和 new Integer() 变量比较 ,永远为 false。
3、两个Integer 变量比较,如果两个变量的值在区间-128到127 之间,则比较结果为true,如果两个变量的值不在此区间,则比较结果为 false 。
4、 int 变量 与 Integer、 new Integer() 比较时,只要两个的值是相等,则为true
参考文档
if(a == 1 && a == 2 && a==3),为true(涉及反射)
Class cache = Integer.class.getDeclaredClasses()[0];
Field c = cache.getDeclaredField("cache");
c.setAccessible(true);
Integer[] array = (Integer[]) c.get(cache);
// array[129] is 1
array[130] = array[129];
// Set 2 to be 1
array[131] = array[129];
// Set 3 to be 1
Integer a = 1;
if(a == (Integer)1 && a == (Integer)2 && a == (Integer)3){
System.out.println("Success");
}
String
String长度有限制吗?是多少?
答:首先字符串的内容是由一个字符数组 char[] 来存储的,由于数组的长度及索引是整数,且String类中返回字符串长度的方法length() 的返回值也是int ,所以通过查看java源码中的类Integer我们可以看到Integer的最大范围是2^31 -1,由于数组是从0开始的,所以数组的最大长度可以使【0~2^31-1】通过计算是大概4GB。
但是通过翻阅java虚拟机手册对class文件格式的定义以及常量池中对String类型的结构体定义我们可以知道对于索引定义了u2,就是无符号占2个字节,2个字节可以表示的最大范围是2^16 -1 = 65535。
其实是65535,但是由于JVM需要1个字节表示结束指令,所以这个范围就为65534了。超出这个范围在编译时期是会报错的,但是运行时拼接或者赋值的话范围是在整形的最大范围。
参考链接
**String.format()常规类型的格式化 **
String类的format()方法用于创建格式化的字符串以及连接多个字符串对象。
常用类型如下:
转换符 | 详细说明 | 示例 |
---|---|---|
%s | 字符串类型 | “kello” |
%d | 整数类型(十进制) | 66 |
%f | 浮点类型 | 3.222222 |
%% | 百分比类型 | %(%特殊字符%%才能显示%) |
String result = String.format(“%06d”, i); // 不足补齐:例如000088
标志 | 详细说明 | 示例 | 结果 |
---|---|---|---|
0 | 数字前面补0(加密常用) | (“%04d”, 99) | 0099 |
空格 | 在整数之前添加指定数量的空格 | (“% 4d”, 99) | 99 |
参考链接 |
String类型装换为double类型
方法 1: 使用Double.parseDouble(String)将字符串转换为双精度
方法 2: 使用Double.valueOf(String)将字符串转换为双精度double
方法 3: 使用Double类的构造函数字符串转换为双精度double
String str3 = “999.333”;
double var3 = new Double(str3);
ArrayList和LinkedList
都实现了List接口
1、数据结构不同
ArrayList是Array(动态数组)的数据结构,LinkedList是Link(链表)的数据结构。
2、效率不同
当随机访问List(get和set操作)时,ArrayList比LinkedList的效率更高,因为LinkedList是线性的数据存储方式,所以需要移动指针从前往后依次查找。
当对数据进行增加和删除的操作(add和remove操作)时,LinkedList比ArrayList的效率更高,因为ArrayList是数组,所以在其中进行增删操作时,会对操作点之后所有数据的下标索引造成影响,需要进行数据的移动。
3、自由性不同
ArrayList自由性较低,因为它需要手动的设置固定大小的容量,但是它的使用比较方便,只需要创建,然后添加数据,通过调用下标进行使用;而LinkedList自由性较高,能够动态的随数据量的变化而变化,但是它不便于使用。
4、主要控件开销不同
ArrayList主要控件开销在于需要在lList列表预留一定空间;而LinkList主要控件开销在于需要存储结点信息以及结点指针信息。
HashSet的去重
HashSet的底层是对HashMap的操作,其去重的原理通过hashCode()与equals()方法来判断是否重复。通过实验发现自定义对象没有成功去重的原因与JDK默认的Object对象hashCode()和equals()实现有关。对于自定义对象的去重,我们可以通过重写自定义对象的hashCode()与equals()使其按照我们所想要的规则进行去重操作。
而String、Integer等都已经重写了hashcode()和equals()方法。
参考链接1
参考链接2
如何实现list集合元素的排序
用Java工具类Collections的sort()方法,对List集合元素进行排序。
Collections提供两种排序方法:
一、Collections.sort(List list);
此方法需要泛型T这个Bean实现Comparable接口,并且实现compareTo()方法排序;
二、Collections.sort(List list, Comparator<? super T> c);
此方法,在泛型T这个Bean没有实现Comparable接口的时候,多个一个参数,是一个接口我们需要实现其compare()方法排序;
详情如下:
https://www.cnblogs.com/oablog/p/9342233.html
利用数组实现队列的功能
需要的属性包括:队列元素个数,数组,队列最大长度,队头下标,队尾下标等
final、finally以及finalize
final: 在java中,final可以用来修饰类,方法和变量(成员变量或局部变量)。
finally: 一般与try catch共同使用,用来对资源的关闭等操作;
finalize: 是在java.lang.Object里定义的,也就是说每一个对象都有这么个方法。这个方法在gc启动,该对象被回收的时候被调用。
final:
•final修饰的类是不能被继承的,因为其是一个最终类;final类中所有的成员方法都会隐式的定义为final方法。
•final修饰的变量是一个常量,只能被赋值一次;
•final修饰的方法也不能重写,但能被重载;
(若父类中final方法的访问权限为private,将导致子类中不能直接继承该方法,因此,此时可以在子类中定义相同方法名的函数,此时不会与重写final的矛盾,而是在子类中重新地定义了新方法。)
•内部类只能访问被final修饰的局部变量
finally:
1、不管有没有异常,finally中的代码都会执行
2、当try、catch中有return时,finally中的代码依然会继续执行
3、finally是在return后面的表达式运算之后执行的,此时并没有返回运算之后的值,而是把值保存起来,不管finally对该值做任何的改变,返回的值都不会改变,依然返回保存起来的值。也就是说方法的返回值是在finally运算之前就确定了的。
4、如果return的数据是引用数据类型,而在finally中对该引用数据类型的属性值的改变起作用,try中的return语句返回的就是在finally中改变后的该属性的值。
5、finally代码中最好不要包含return,程序会提前退出,也就是说返回的值不是try或catch中的值
先执行try中的语句,包括return后面的表达式,
有异常时,先执行catch中的语句,包括return后面的表达式,
然后执行finally中的语句,如果finally里面有return语句,会提前退出,
最后执行try中的return,有异常时执行catch中的return。
finalize:
一个对象的finalize()方法只会被调用一次,而且finalize()被调用不意味着gc会立即回收该对象,所以有可能调用finalize()后,该对象又不需要被回收了,然后到了真正要被回收的时候,因为前面调用过一次,所以不会调用finalize(),产生问题。 所以,推荐不要使用finalize()方法,它跟析构函数不一样。
[参考链接](https://blog.csdn.net/xiangyuenacha/article/details/84234797
https://blog.csdn.net/qq_44543430/article/details/89891197
https://www.cnblogs.com/ktao/p/8586966.html)
Java创建实例的几种方法
四种方式:
(1)用new 语句创建对象,最常用。
(2)运用反射手段,调用Java.lang.Class或者java.lang.reflect.Constructor类的newInstance()实例方法。
(3)调用对象的clone()方法
(4)运用反序列化手段,调用java.io.ObjectInputStream对象的readObject()方法。
继承和多态——父类引用指向子类对象
即类似于Fu fu = new Zi();
“成员变量,静态方法看左边;非静态方法:编译看左边,运行看右边。”
在这个引用变量fu指向的对象中,他的成员变量和静态方法与父类是一致的,他的非静态方法,在编译时是与父类一致的,运行时却与子类一致(发生了复写)。
当静态时,Fu类的所有函数跟随Fu类加载而加载了。也就是Fu类的函数(是先于对象建立之前就存在了,无法被后出现的Zi类对象所复写的).
参考链接
普通类和抽象类(abstract)
普通类不能包含抽象方法,抽象类可以包含也可以不包含抽象方法;
抽象类不能被实例化,不能使用new调出构造方法;
如果一个类继承抽象类,则子类必须实现父类的抽象方法;如果没实现,则子类也需要定义为abstract类。
接口可以继承接口,
抽象类不可以继承接口,但可以实现接口
抽象类可以继承实体类,前提是实体类必需有构造函数
一个接口可以继承多个接口.
一个类可以实现多个接口
一个类只能继承一个类,不能继承多个类(单继承)
一个类在继承类的同时,也可以实现单个或多个接口
抽象类和接口(interfact)
相同点:
- 都是上层的抽象层
- 都不能被实例化
不同点:
1、默认的方法实现:可以有方法实现,接口都是抽象的,不存在方法实现;
2、抽象类实现:子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现。
接口实现:使用关键字implements来实现接口。它需要提供接口中所有声明的方法的实现。
3、构造器:抽象类可以有,接口无。
4、修饰符:抽象方法可以有public、protected和default这些修饰符,而接口只有public。
5、添加新方法:抽象方法可以提供实现,不需要改变其他diamante,接口添加方法实现类也需要改变。
什么时候使用抽象类和接口
如果你拥有一些方法并且想让它们中的一些有默认实现,那么使用抽象类;
如果你想实现多重继承作用,那么你必须使用接口。
如果基本功能在不断改变,那么就需要使用抽象类。如果不断改变基本功能并且使用接口,那么就需要改变所有实现了该接口的类。
深拷贝和浅拷贝
浅拷贝:值类型的字段会复制一份,而引用类型的字段拷贝的仅仅是引用地址,而该引用地址指向的实际对象空间其实只有一份。
深拷贝:相较于浅拷贝,除了值类型的字段会复制一份,引用类型字段所指向的对象,也会在内存中创建一个副本。
枚举Enum
public enum ServiceOrderType {
STANDARD(1,"标准"),
UNSTANDARD(2,"定制");
private int value;
private String desc;
ServiceOrderType(Integer value, String desc) {
this.value = value;
this.desc = desc;
}
public static String get(int value) {
ServiceOrderType[] orderTypes = values();
for (ServiceOrderType orderType : orderTypes) {
if (orderType.value() == value) {
return orderType.desc();
}
}
return null;
}
public String desc() {
return this.desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public int value() {
return this.value;
}
public void setValue(int value) {
this.value = value;
}
}
Date相关
分为两种java.util.Date和java.sql.Date
继承关系:java.lang.Object --> java.util.Date --> java.sql.Date
java.util.Date 就是在除了SQL语句的情况下面使用
java.util.Date 是 java.sql.Date 的父类
java.sql.Date 是针对SQL语句使用的,它只包含日期而没有时间部分
IO流
根据处理数据类型的不同分为:字符流和字节流
根据数据流向不同分为:输入流和输出流
反射
一、是什么
Java Reflaction in Action有这么一句话,可以解释。反射是运行中的程序检查自己和软件运行环境的能力,它可以根据它发现的进行改变。通俗的讲就是反射可以在运行时根据指定的类名获得类的信息。
二、为什么
我们为什么要使用反射,它的作用是什么,它在实际的编程中有什么应用。
首先我们先明确两个概念,静态编译和动态编译。
静态编译:在编译时确定类型,绑定对象,即通过。
动态编译:运行时确定类型,绑定对象。动态编译最大限度发挥了java的灵活性,体现了多
态的应用,有以降低类之间的藕合性。
我们可以明确的看出动态编译的好处,而反射就是运用了动态编译创建对象。
getDeclaringClass
该方法返回一个Class对象,返回当前class对象的声明对象class,一般针对内部类的情况,比如A类有内部类B,那么通过B.class.getDeclaringClass()方法将获取到A的Class对象.
在使用反射对象时比如Method和Field的getDeclaringClass方法将获取到所属类对象
getDeclaredClasses
该方法返回当前Class声明的public,private ,default,private的内部类
getClasses
该方法获取的是包含父类和当前类声明的public类型的内部类,注意是public的
java8新特性
1、接口的默认方法
java8允许我们给接口添加一个非抽象的方法实现,只需要使用default关键字即可,又叫做扩展方法。
2、lambda表达式
3、函数式接口
。。。。。
数据库相关
索引的数据结构
索引设计的原则
数据库事务的ACID是指什么?
ACID原则是数据库事务正常执行的四个,分别指原子性、一致性、隔离性及持久性
原子性——A:(Atomicity)
简单来说是指事务是一个独立单元,事务中的操作要么都发生,要么都不发生。
一致性——C:(Consistency)
事务前后数据的完整性必须保持一致。
隔离性——I:(Isolation)
数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
持久性——D:(Durability)
是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。
参考文档:https://blog.csdn.net/dengjili/article/details/82468576
数据库事务的隔离级别
一般的数据库,都包括以下四种隔离级别:事务的隔离级别从低到高分别是:读未提交,读已提交,可重复读,串行化;这四个隔离级别可以分别解决脏读,不可重复读,幻读的问题。
√表示可能出现;×表示不会出现 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交 | √ | √ | √ |
读提交 | × | √ | √ |
可重复读 | × | × | √ |
串行化 | × | × | × |
读未提交(Read Uncommitted)
读提交(Read Committed):SQL Server和Oracle的默认隔离级别
可重复读(Repeated Read):Mysql的默认隔离级别,当事务启动时,就不允许进行“修改操作(Update)”了,但却避免不了“幻读”,因为幻读是由于“插入或者删除操作(Insert or Delete)”而产生的。
串行化(Serializable)
总结:
为什么会出现“脏读”?因为没有“select”操作没有规矩。
为什么会出现“不可重复读”?因为“update”操作没有规矩。
为什么会出现“幻读”?因为“insert”和“delete”操作没有规矩。
“读未提(Read Uncommitted)”能预防啥?啥都预防不了。
“读提交(Read Committed)”能预防啥?使用“快照读(Snapshot Read)”,避免“脏读”,但是可能出现“不可重复读”和“幻读”。
“可重复读(Repeated Red)”能预防啥?使用“快照读(Snapshot Read)”,锁住被读取记录,避免出现“脏读”、“不可重复读”,但是可能出现“幻读”。
“串行化(Serializable)”能预防啥?排排坐,吃果果,有效避免“脏读”、“不可重复读”、“幻读”,不过效果谁用谁知道。
数据库优化(TODO)
1、选取最适用的字段属性;并尽量把字段设置为NOTNULL
2、使用连接(join)来代替子查询(Sub-Queries)
3、使用联合(UNION)来代替手动创建的临时表
4、事务
5、锁定表
6、使用外键
7、使用索引
8、优化查询等SQL语句
MySQL中IF()、IFNULL()、NULLIF()、ISNULL()函数的使用
//IF(expr1,expr2,expr3),如果expr1的值为true,则返回expr2的值,如果expr1的值为false,则返回expr3的值。
SELECT IF(TRUE,'A','B'); -- 输出结果:A
SELECT IF(FALSE,'A','B'); -- 输出结果:B
//IFNULL(expr1,expr2),如果expr1的值为null,则返回expr2的值,如果expr1的值不为null,则返回expr1的值。
SELECT IFNULL(NULL,'B'); -- 输出结果:B
SELECT IFNULL('HELLO','B'); -- 输出结果:HELLO
//NULLIF(expr1,expr2),如果expr1=expr2成立,那么返回值为null,否则返回值为expr1的值。
SELECT NULLIF('A','A'); -- 输出结果:null
SELECT NULLIF('A','B'); -- 输出结果:A
//ISNULL(expr),如果expr的值为null,则返回1,如果expr1的值不为null,则返回0。
SELECT ISNULL(NULL); -- 输出结果:1
SELECT ISNULL('HELLO'); -- 输出结果:0
线程相关
线程是CPU调度和执行的单位。
进程与线程
简单来说,进程就是执行程序的一次执行过程,是系统资源分配的单位。例如,操作系统中运行的程序就是进程。
一个进程里面包含有一个或者多个进程。
线程的状态
线程间的通信方式
锁机制:包括互斥锁、条件变量、读写锁
信号量机制(Semaphone)
信号机制(Signal)
具体参考文档
线程的创建
继承Thread类
实现Runnable接口
实现Callable接口
继承Thread类
子类继承Thread类具备多线程能力
启动线程:子对象.start();
不建议使用:避免OOP(面向对象编程)单继承局限性
示例如下:
/**
* 线程测试
* @author Administrator
*
*/
public class TestThread extends Thread{
// 主线程 main方法
public static void main(String[] args) {
// 创建一个线程对象
TestThread testThread = new TestThread();
// 启动线程,和main同步执行
testThread.start();
// 调用run方法,先执行
// testThread.run();
for (int i = 0; i < 20; i++) {
System.out.println("我在学习……" + i);
}
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("我在睡觉……" + i);
}
}
}
执行start()方法时候是同步执行结果如下:
执行run()方法时候是同步执行结果如下:顺序执行
举个栗子——通过多线程实现网络图片下载
import java.io.File;
import java.net.URL;
import org.apache.commons.io.FileUtils;
// 通过多线程实现网络图片的下载
public class TestThread2 extends Thread{
private String url;// 网络图片地址
private String name;// 图片保存名称
public TestThread2(String url, String name) {
this.url = url;
this.name = name;
}
@Override
public void run() {
WebLoadPicture webLoadPicture = new WebLoadPicture();
webLoadPicture.loadPicture(url, name);
System.out.println("图片加载了。。。"+ name);
}
public static void main(String[] args) {
TestThread2 t1 = new TestThread2("图片地址", "文件命名");
TestThread2 t2 = new TestThread2("图片地址2", "文件命名2");
TestThread2 t3 = new TestThread2("图片地址3", "文件命名3");
t1.start();
t2.start();
t3.start();
}
}
// 下载器
class WebLoadPicture{
public void loadPicture(String url, String name) {
try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (Exception e) {
System.out.println("下载失败"+ name);
}
}
}
实现Runnable接口
类实现Runnable接口具备多线程能力
启动线程:传入目标对象+Thread对象.start();new Thread(参数对象).start;
推荐使用:避免OOP单继承局限性,方便同一个对象呗多线程使用。
示例如下:
TODO
sleep和wait
1、sleep是线程中的方法,wait是Object类的方法;
2、sleep不会释放锁,相当于暂停等待;wait会立即释放锁,并会加入到等待队列中;
3、sleep方法不依赖于同步器synchronized,但是wait需要依赖synchronized关键字,放置在方法块中;
4、sleep不需要被唤醒(休眠之后退出阻塞),但是wait需要被唤醒,notify方法。
为什么wait、notify、notifyAll这些方法不在Thread类里,而是在Object里?
一个很明显的原因是JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在Thread类中,线程正在等待的是哪个锁就不明显了。简单的说,由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象。
线程池
Java中已经提供了创建线程池的一个类:Executor
而我们创建时,一般使用它的子类:ThreadPoolExecutor
线程池几个参数:
corePoolSize就是线程池中的核心线程数量,这几个核心线程,在没有用的时候,也不会被回收
maximumPoolSize就是线程池中可以容纳的最大线程的数量
keepAliveTime,就是线程池中除了核心线程之外的其他的最长可以保留的时间,因为在线程池中,除了核心线程即使在无任务的情况下也不能被清除,其余的都是有存活时间的,意思就是非核心线程可以保留的最长的空闲时间
util,就是计算这个时间的一个单位,
workQueue,就是等待队列,任务可以储存在任务队列中等待被执行,执行的是FIFIO原则(先进先出)
threadFactory,就是创建线程的线程工厂
handler,是一种拒绝策略,我们可以在任务满了之后,拒绝执行某些任务。
【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。 说明:使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。说明:Executors返回的线程池对象的弊端如下:
1)FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
2)CachedThreadPool和ScheduledThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
public class ThreadPoolUtil {
public static ThreadPool instance;
// 获取单例的线程池对象
public static ThreadPool getInstance() {
if (instance == null) {
synchronized (ThreadPoolUtil.class) {
if (instance == null) {
int cpuNum = Runtime.getRuntime().availableProcessors();// 获取处理器数量
instance = new ThreadPool(cpuNum + 1, cpuNum * 2, 60,20);
}
}
}
return instance;
}
public static class ThreadPool {
private ThreadPoolExecutor mExecutor;
private final int corePoolSize;
private final int maximumPoolSize;
private final long keepAliveTime;
private final int wattingCount;
private ThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, int wattingCount) {
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.keepAliveTime = keepAliveTime;
this.wattingCount = wattingCount;
}
public void execute(Runnable runnable) {
if (runnable == null) {
return;
}
if (mExecutor == null) {
mExecutor = new ThreadPoolExecutor(
corePoolSize, // 核心线程数
maximumPoolSize, // 最大线程数
keepAliveTime, // 闲置线程存活时间
TimeUnit.SECONDS, // 时间单位
new LinkedBlockingDeque<Runnable>(wattingCount), // 线程队列
Executors.defaultThreadFactory(), // 线程工厂
new ThreadPoolExecutor.AbortPolicy() { // 队列已满,而且当前线程数已经超过最大线程数时的异常处理策略
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {//丢弃任务并抛出RejectedExecutionException
super.rejectedExecution(r, e);
}
}
);
}
mExecutor.execute(runnable);
}
// 从线程队列中移除对象
public void cancel(Runnable runnable) {
if (mExecutor != null) {
mExecutor.getQueue().remove(runnable);
}
}
}
}
应用
ThreadPoolUtil.getInstance().execute(() -> {
//.....
});
简单数据结构与算法
排序算法(自整理)
递归与迭代(TODO)
简单地说,
递归是重复调用函数自身实现循环,即自己调用自己。
迭代是函数内某段代码实现循环,利用变量的原值推算出变量的一个新值,亦即A不停的调用B。
计数器算法
滑动窗口算法
漏桶限流算法
令牌桶限流算法
网络编程
完整的HTTP请求
https://blog.csdn.net/yezitoo/article/details/78193794
设计模式
23种设计模式。GoF23
适配器模式
接口的转换作用,从一个接口类型转换为需要的类型。
适配器Adapter实现接口ITarget,并且Adapter里has-a被适配的Adapteee,调用这里的方法。
工厂模式
还可以分为简单工厂和工厂方法模式
这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
简单工厂,由工厂内部做判断然后创建产品;
工厂方法模式:不同产品各自有不同的工厂,对外提供各自的产品工厂。
抽象工厂模式
此模式适用于产品族等场景。
抽象工厂模式(Abstract Factory Pattern)是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类。每个生成的工厂都能按照工厂模式提供对象。
何时使用:系统的产品有多于一个的产品族,而系统只消费其中某一族的产品。
如何解决:在一个产品族里面,定义多个产品。
关键代码:在一个工厂里聚合多个同类产品。
代理模式
静态代理和动态代理
动态代理的优点和美中不足
- 优点:动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转(静态代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。 如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有静态代理类也需要实现此方法。增加了代码维护的复杂度。 )。
- 缺点:诚然,Proxy 已经设计得非常优美,但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持 interface 代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫 Proxy。Java 的继承机制注定了这些动态代理类们无法实现对 class 的动态代理,原因是多继承在 Java 中本质上就行不通
策略模式
备一组算法 & 将每一个算法封装起来,让外部按需调用
例如支付时:通过枚举来优雅的选择支付类型,共用一个支付接口,不同的支付方式实现自己的逻辑,更加贴合面向对象的思想。
- 定义抽象策略角色(Strategy),接口或者抽象类------>
- 定义具体策略角色(Concrete Strategy),每种策略的实现方式------>
- 定义环境角色(Context):用于连接上下文------->
- 客户端选择调用
JMM
即java内存模型。java memory model。
Java内存模型(Java Memory Model,J M M),J M M 是建立在硬件内存模型基础上的抽象模型,并不是物理上的内存划分,简单说,为了使Java虚拟机(Java Virtual Machine,J V M)在各平台下达到一致的内存交互效果,需要屏蔽下游不同硬件模型的交互差异,统一规范,为上游提供统一的使用接口。
J M M是保证J V M在各平台下对计算机内存的交互都能保证效果一致的机制及规范。
-
主内存:共享的信息
-
工作内存:也就是单独为线程分配的空间内存,每个线程拥有自己的内存空间。线程私有的信息。基本数据类型,直接分配到工作内存;引用数据类型,引用的地址存放在工作内存,引用的对象存放在堆中(共享)。
-
工作方式:
- 线程修改私有数据,直接在工作空间修改 - 线程修改共享数据,先把数据复制到工作空间中,在工作空间中修改,完成以后,刷新内存中的数据。
三大特性的保证
特性 | volatile | synchronized | Lock | Atomic |
---|---|---|---|---|
可见性 | 可以保证 | 可以保证 | 可以保证 | 可以保证 |
原子性 | 无法保证 | 可以保证 | 可以保证 | 可以保证 |
有序性 | 一定程度保证 | 可以保证 | 可以保证 | 无法保证 |
学习资料
问题
Unsupported major.minor version 52.0是什么错误?
简单来说,就是执行代码的jdk版本 低于 编译的jdk版本
解决办法:1、先查看项目中的jdk版本。2、因为是spring的错误,可以查看jdk版本与spring版本的兼容问题。
附:JDK版本和Java编译器内部的版本号
J2SE 8 = 52,
J2SE 7 = 51,
J2SE 6.0 = 50,
J2SE 5.0 = 49,
JDK 1.4 = 48,
JDK 1.3 = 47,
JDK 1.2 = 46,
JDK 1.1 = 45
内存泄漏
无用的对象不会被回,但是仍然占有内存,长时间会导致泄露。
https://www.jb51.net/article/92311.htm