集合
| 类型 | 描述 |
|---|---|
| ArrayList | 基于数组,查改快,增删慢(需要移动插入位置之后的数),线程不安全 |
| LinkedList | 基于双向列表,增删快(改变指针即可),查询慢(需要遍历),线程不安全 |
| Vector | ArrayList的线程安全版,锁操作导致性能低于ArrayList |
Set:存储无序且值不同的元素,hashCode相同&&(短路与,遇到false就停止)equals相同视为同一元素
| 类型 | 描述 |
|---|---|
| HashSet | HashTable实现,无序 |
| TreeSet | 二叉树实现,自定义数据类型必须实现Comparable |
| LinkedHashSet | HashTable实现数据存储,双向列表记录顺序 |
Map
| 类型 | 描述 |
|---|---|
| HashMap | 数组+链表,线程不安全 |
| HashTable | HashMap线程安全版本(类比表锁,颗粒度大) |
| TreeMap | 二叉树实现,自定义数据类型必须实现Comparable |
| LinkedHashMap | HashTable实现数据存储,双向列表记录顺序 |
HashMap详解
- HashMap是数组+列表的数据结构。
- 其中Entry由key、value、hash值、next指针组成
- HashMap常用参数
- capacity:当前数组容量,默认16,不够扩2倍
- loadFactor(负载因子):默认0.75
- threshold:扩容阀值(触发扩容条件),capacity*loadFactor
- JDK<1.8:定位到的数组不含链表,一次寻址即可,算法复杂度O(1),含有链表,需要用equals逐一比较,这个过程的算法复杂度O(n)
- JDK>= 1.8:链表改成红黑树,找到Entry后遍历寻找元素的复杂度从O(n)->O(logn)
为什么线程不安全
- 哈希碰撞:哈希碰撞时候,线程A判断节点的next指针为null,这时候时间分片用完了,线程B进来了,也判断为null,然后线程A和B都建下一个节点,必然造成其中一个丢成
- 哈希扩容:当多个线程同时检测到总数量超过阀值的时候就会同时调用resize操作,各自生成新的数组并rehash后赋给该map底层的数组table,结果最终只有最后一个线程生成的新数组被赋给table变量,其他线程的均会丢失。而且当某些线程已经完成赋值而其他线程刚开始的时候,就会用已经被赋值的table作为原始数组,这样也会有问题
为什么HashMap 的长度是2的幂次方
如果长度是2的幂次方,则hash%length = hash&(length-1)成立,与运算代替乘法运算,性能提升
HashTable 和 HashMap 区别
HashMap线程不安全,HashTable线程安全,Hashtable内部方法基本都通过synchronized修饰,保证了可见性。但是Hashtable效率太低,当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态
ConcurrentHashMap
JDK1.7的时候(类似页锁),对Table进行了分割分段,每个Segment(ReentrantLock)锁住一部分Table,默认分配16个Segment,效率也就提升16倍。到了JDK1.8的时候(类似行锁),直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。有几个node提升多少倍
ReetrantLock和synchronized
两者都是可重入锁,即两者都是同一个线程每进入一次,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。区别的是ReentrantLock提供了创建公平锁的构造函数,ReentrantLock比较灵活,加锁解锁由用户自己控制,一般记得在finally里释放锁
ReetrantReadWriteLock
readLock()获取读锁,writeLock获取写锁,读锁,此时多个线程可以获取读锁,写锁,此时只有一个线程能获得写锁,但读锁和写锁是互斥的,读锁释放后才执行写锁的方法,这个可以类比数据库中的读写锁,(in share mode/for update),这里不在赘述
异常
- Error:系统内部错误/资源耗尽导致,不能在运行中被动态处理,出现Error程序会自动结束
- RuntimeException:可以捕获处理
- CheckedException:编译阶段会检查此类异常,强制要求用户去捕获处理
throw/throws
- throw:作用在方法内,明确抛出一个异常
- throws:作用在方法外,定义可能抛出的异常
反射机制
动态获取类和对象的信息,以及动态调用对象的方法
API
- Class类:用于获取类的属性、方法等信息
- Field类:表示类的成员变量,用于获取和设置类中的属性值
- Method类:表示类的方法,用于获取方法的描述信息或者执行某个方法
- Constructor类:表示类的构造方法
案例
- 编译时类型和运行时类型
// 编译时类型为Person,运行时类型为Student,反射机制实现
Person person = new Student();
log.info("person的运行时class类型:{}",person.getClass().toString());
- Class和父Class的获取
Person person = new Student();
log.info("获取person的Class方式一:{}",person.getClass().toString());
log.info("[强制处理异常ClassNotFoundException]获取person的Class方式二:{}",Class.forName("com.example.demo.Student").toString());
log.info("获取person的父Class方式:{}",person.getClass().getSuperclass().toString());
- Field的获取以及修改
Student student = new Student();
Class aClass = student.getClass();
Field[] declaredFields = aClass.getDeclaredFields();
for ( Field field: declaredFields) {
log.info("字段类型:{}",field.getType());
log.info("字段名称:{}",field.getName());
}
Field field = aClass.getDeclaredField("age");
boolean accessible = field.isAccessible();
log.info("age 是否允许访问:{}",accessible);
field.setAccessible(true);
log.info("age value修改前:{}",field.get(student));
field.set(student,2);
log.info("age value修改后:{}",field.get(student));
// 还原
field.setAccessible(accessible);
- Method的获取以及使用
Student student = new Student();
Class aClass = student.getClass();
Method[] declaredMethods = aClass.getDeclaredMethods();
log.info("Method:");
for ( Method method: declaredMethods) {
log.info("Method Name:{}",method.getName());
}
Method method = aClass.getDeclaredMethod("setAge", Integer.class);
method.invoke(student,2);
log.info(student.toString());
- 创建对象的方式
Class aClass = Class.forName("com.example.demo.Student");
Student s1 = (Student) aClass.newInstance();
log.info("存在默认空构造器:{}", s1.toString());
Constructor c = aClass.getDeclaredConstructor(String.class, Integer.class);
Student s2 = (Student) c.newInstance("zhao", 18);
log.info("使用构造器:{}", s2.toString());
注解
标准元注解:负责注解其他注解
- @Target:注解所修饰的对象范围
- TYPE:类、接口、枚举
- FIELD:域
- METHOD:方法
- PARAMETER:参数
- CONSTRUCTOR:构造器
- LOCAL_VARAIABLE:局部变量
- ANNOTATION:声明注解
- PACKAGE:包
- TYPE_PARAMETER:普通变量
- TYPE_USE:标注名称
- @Retention:标注保留级别
- SOURCE:源文件有效
- CLASS:Class文件有效
- RUNTIME:运行时有效
- @Documented:是否被javadoc工具记录
- @Inherited:继承(标注同时用于子类)
案例
定义注解(作为对象的元数据,作出一些说明)
@Target(ElementType.FIELD)
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface AirConditioningProvider {
String address() default "";
}
定义被注解对象
public class Green {
@AirConditioningProvider(address="珠海市")
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
定义注解处理器
@Slf4j
public class AirConditioningUtil {
public static void getName(Class<?> classz) throws Exception {
Field name = classz.getDeclaredField("name");
AirConditioningProvider annotation = name.getAnnotation(AirConditioningProvider.class);
log.info("空调产地:" + annotation.address());
}
public static void main(String agrs[]) throws Exception {
AirConditioningUtil.getName(Green.class);
}
}
Spring中的常见注解
- @Resource & @Autowired
- 相同点
- 都可以实现注入bean
- 写在字段/setter处
- 不同点
- @Autowired是spring的注解,而@Resource不是,但是spring支持
- @Autowired是byType(根据类型)注入,默认情况下要求依赖必须存在,配置required=false表示依赖可为null,如果要实现byName(根据名称)需要集合@Qualifier
// 1 @Autowired @Qualifier("userService")- @Resource
- name & type:名称+类型,找不到/不唯一抛出异常
- name:根据名称,找不到/不唯一抛出异常
- type:根据类型,找不到/不唯一抛出异常
- 不指定:byName,找不到则回退为原始类型
// 等价1 @Resource("userService")
- 相同点
- @Repository & @Component & @Service & @Controller
- 相同点
- 作用相同:注入组件
- 不同点
- 意义不同
- @Component:通用bean
- @Controller:表现层bean
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component- @Service:业务层bean
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component- @Repository:数据访问层bean
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component
- 意义不同
- 相同点
内部类
- 静态内部类(StaticInnerClass):定义在类中的静态类,适用于和外部类关系密切且不依赖外部类实例的类
- 成员内部类(MemberInnerClass):定义在类中的非静态类
- 局部内部类(PartInnerClass):定义在类的方法中的非静态类
- 匿名内部类:通过继承一个父类或者实现一个接口直接定义且使用
- 一句话总结:静态内部类只能访问外部类的静态变量和静态方法,成员内部类不能定义静态方法和静态变量(final修饰除外),局部内部类和成员内部类要求一致
泛型
泛型即参数化类型,编译时自动转为具体类型(类型擦除),编译期就能够判断类型是否安全,同时类型转化是自动、隐式的,提高了代码的安全性和重用性
| 类型 | 描述 |
|---|---|
| E-Element | 集合中使用,表示在集合中存放的元素 |
| T-Type | JAVA类 |
| K-Key | 键 |
| V-Value | 值 |
| N-Number | 数值类型 |
| ? | 不确定的类型 |
- 上限:<? extends T>,?是T的子类
- 下限:<? super T>,?是T的父类
案例
// 泛型接口
public interface IGeneral<T> {
T id();
}
// 泛型类
public class GeneralClass<T> implements IGeneral<Long> {
private T t;
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
@Override
public Long id() {
return 1314L;
}
// 泛型方法
<T> void generalMethod(T... array) {
for (T t : array) {
if (t instanceof Long) {
log.info("Long");
} else {
log.info("Other");
}
}
}
public static void main(String args[]) {
GeneralClass<Long> longGeneralClass = new GeneralClass<>();
longGeneralClass.generalMethod(longGeneralClass.id());
}
}
序列化
保存对象及其状态以便下次使用
要点
- 实现Serializable接口
- 对象ID保持一致(private static final long serialVersionUID)
- 序列化不保存静态变量
- 序列化父类变量时候需要父类实现Serializable接口
- Transient修饰可以阻止序列化,反序列化后变成该类型初始值
案例
@Data
@Builder
public class Student implements Serializable {
// 序列化UID
private static final long serialVersionUID = 1L;
private String name;
// 不会被序列化
private transient Integer age;
// 不会被序列化
private static Integer sex = 1;
public static void main(String args[]) throws Exception {
FileOutputStream fileOutputStream = new FileOutputStream("Student.out");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(Student.builder().name("zhao").age(18).build());
objectOutputStream.flush();
objectOutputStream.close();
FileInputStream fileInputStream = new FileInputStream("Student.out");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
Student student = (Student) objectInputStream.readObject();
System.out.println(student.toString());
}
}