网站建设平台合同模板下载电商网站开发平台有哪些
2022/12/15 如何实现一个IOC容器
1、配置文件配置包扫描路径
2、递归包扫描获取.class文件
3、反射、确定需要交给lOC管理的类4、对需要注入的类进行依赖注入
-
配置文件中指定需要扫描的包路径
-
定义一些注解,分别表示访问控制层、业务服务层、数据持久层、依赖注入注解、获取配置文件注解
-
从配置文件中获取需要扫描的包路径,获取到当前路径下的文件信息及文件夹信息,我们将当前路径下所有以.class结尾的文件添加到一个Set集合中进行存储
-
遍历这个set集合,获取在类上有指定注解的类,并将其交给IOC容器,定义一个安全的Map用来存储这些象
-
遍历这个lOC容器,获取到每一个类的实例,判断里面是有有依赖其他的类的实例,然后进行递归注入
2022/12.16 ConcurrentHashMap原理 jdk7和jdk8版本的区别
jdk7:
数据结构:ReentrantLock+Segment+HashEntry,一个Segment中包含一个HashEntry数组,每个HashEntry又是一个链表结构
元素查询:二次hash,第一次Hash定位到Segment,第二次Hash定位到元素所在的链表的头部
锁: Segment分段锁Segment继承了ReentrantLock,锁定操作的Segment,其他的Segment不受影响,并发度为segment个数,可以通过构造函数指定,数组扩容不会影响其他的segment
get方法无需加锁,volatile保证
jdk8:
数据结构: synchronized+CAS+Node+红黑树,Node的val和next都用volatile修饰,保证可见性查找,替换,赋值操作都使用CAS
锁:锁链表的head节点,不影响其他元素的读写,锁粒度更细,效率更高,扩容时,阻塞所有的读写操作、并发扩容
读操作无锁:
Node的val和next使用volatile修饰,读写线程对该变量互相可见数组用volatile修饰,保证扩容时被读线程感知
2022/12/17 HashMap与HashTable区别
1:区别∶
-
(1)HashMap方法没有synchronized修饰,线程非安全,HashTable线程安全;
-
(2) HashMap允许- - - key和value为null,而HashTable不允许
⒉.底层实现:数组+链表实现
jdk8开始链表高度到8、数组长度超过64,链表转变为红黑树,元素以内部类Node节点存在·计算key的hash值,二次hash然后对数组长度取模,对应到数组下标,
-
·如果没有产生hash冲突(下标位置没有元素),则直接创建Node存入数组,
-
·如果产生hash冲突,先进行equal此较,相同则取代该元素,不同,则判断链表高度插入链表,链表高度达到8,并且数组长度到64则转变为红黑树,长度低于6则将红黑树转回链表
-
key为null,存在下标0的位置
数组扩容
2022/12.19/ArrayList和LinkedList区别
ArrayList:基于动态数组,连续内存存储,适给下标访问(随机访问),扩 容机制:因为数组长度固定,超出长
度存数据时需要新建数组,然后将老数组的数据拷贝到新数组,如果不是尾部插入数据还会涉汲玩素的移动(往
后复制一份,插入新元素),使用尾插法并指定初始容量可以极大提升性能、甚至超过linkedList (需要创建大量
的node对象)
LinkedList:纡链表,可以存储在分散的内存种,适给做数据插入及删除操作,不适合查询:要逐-遍历
遍历LinkedList必须使用iterator不能使用for循环,因为每次for循环体内通过get(i)取得某-元素时都需要对list重
新进行遍历,性能消耗极大。
另外不要试图使用indexOf等返回元素索引,并利用其进行遍历,使用indexlIOf对list进行了遍历,当结果为空时会
遍历整个列表。
2022/12/20 hashcode与equals
hashCode介绍
HashCode()的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode()定义在JDK的Object.java中,Java中的任何类都包含有hashCode()函数。散列表存储的是键值对(key-value),它的特点是:能根据"“键"快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)
为什么要有hashCode
以"HashSet如何检查重复"为例子来说明为什么要有hashcode:
对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,看该位置是否有值,如果没有、HashSet会假设对象没有重复出现。但是如果发现有值,这时会调用equals ()方法来检查两个对象是否真的相同。如果两者相同,HashSet就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。这样就大大减少了equals的次数,相应就大大提高了执行速度。
-
·如果两个对象相等,则hashcode—定也是相同的
-
·两个对象相等,对两个对象分别调用equals方法都返回true·两个对象有相同的hashcode值,它们也不一定是相等的
-
因此,equals方法被覆盖过,则hashCode方法也必须被覆盖
-
hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCogell则该class的两个对象无论如何都不会相学(即使这两个对象指向相同的数据)
2022/12/21/ List和Set
●List: 有序,按对象进入的顺序保存对象,可重复,允许多个NULL元素对象,可以使用Iterator取出所有元素,在逐一遍历,还可以使用get(int index)获取指定下表的元素
●Set: 无序,不可重复,最多允许有-个Null元素对象, 取元素时只能用Iterator接口取得所有元素,在逐一遍历各个元素
2022/12/22 接口和抽象类的区别
-
·1 抽象类可以存在普通成员函数,而接口中只能存在public abstract方法。
-
·2 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的。
-
3 抽象类只能继承一个,接口可以实现多个。
-
接口的设计目的,是对类的行为进行约束(更准确的说是一种"有"约束,因为接口不能规定类不可以有什么行为),也就是提供一种机制,可以强制要求不同的类具有相同的行为。它只约束了行为的有无,但不对如何实现行为进行限制。
而抽象类的设计目的,是代码复用。当不同的类具有某些相同的行为(记为行为集合A),且其中一部分行为的实现方式一致时(A的非真子集,记为B),可以让这些类都派生于一个抽象类。在这个抽象类中实现了B,避免让所有的子类来实现B,这就达到了代码复用的目的。而A减B的部分,留给各个子类自己实现。正是因为A-B在这里没有实现,所以抽象类不允许实例化出来(否则当调用到A-B时,无法执行)。
-
-抽象类是对类本质的抽象,表达的是is a的关系,比如:BMw is a car。抽象类包含并实现子类的通用特性,将子类存在差异化的特性进行抽象,交由子类去实现。
-
而接口是对行为的抽象,表达的是like a的关系。比如: Bird like a Aircraft(像飞行器一样可以飞),但其本质上is a Bird。接口的核心是定义行为,即实现类可以做什么,至于实现类主体是谁、是如何实现的,接口并不关心。
使用场景:当你关注一个事物的本质的时候,用抽象类;当你关注一个操作的时候,用接口。
抽象类的功能要远超过接口,但是,定义抽象类的代价高。因为高级语言来说(从实际设计上来说也是)每个类只能继承一个类。在这个类中,你必须继承或编写出其所有子类的所有共性。虽然接口在功能上会弱化许多,但是它只是针对一个动作的描述。而且你可以在一个类中同时实现多个接口。在设计阶段会降低难度
2022/12/23 重载和重写:
-
重载:发生在同一个类中,方法名必须相同,参数类型不同个数不同、顺序不同,方法返回值和访问修饰符可以不1同,发生在编译时。
-
重写:发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为private则子类就不能重写该方法。
pub1ic int add(int a,String b)
pub1ic String add(int a,String b)
//编译报错
2023 2/25
//int和Integer == 判断 内存 地址 引用public static void main(String args[]) { int a1 =127;Integer a2 =127;Integer a3 =127;Integer a4 =new Integer(127);System.out.println(a1==a2);//trueSystem.out.println(a1==a4);//trueSystem.out.println(a2==a3);//trueSystem.out.println(a2==a4);//false//int 类型的自动封装,与Integer声明的变量 总是指向同一地址//Integer声明的变量的值 在[-128,127]范围 地址指向相同(byte范围的数 是从常量池中获取,)//new Integer()是新建一个对象,与其他Integer的引用不同int a5 =1280;int a6 =1280;Integer a7 =1280;Integer a8 =1280;System.out.println(a5==a6);//trueSystem.out.println(a5==a7);//trueSystem.out.println(a7==a8);//false}
结论
int和任意Integer都是同一地址 ;
Integer只在127范围内才是同地址,超出就false ;
integer()与Integer,Integer()无论什么数范围 都不同地址 。
== 比较的是变量(栈)内存中存放的对象的(堆)内存地址,用来判断两个对象的地址是否相同,即是否是指相同一个对象。比较的是真正意义上的指针操作。
1、比较的是操作符两端的操作数是否是同一个对象。
2、两边的操作数必须是同一类型的(可以是父子类之间)才能编译通过。
3、比较的是地址,如果是具体的阿拉伯数字的比较,值相等则为true,如:
int a=10 与 long b=10L 与 double c=10.0都是相同的(为true),因为他们都指向地址为10的堆。
2023 2/26
Java中的异常体系
Java中的所有异常都来自顶级父类Throwable。Throwable下有两个子类Exception和Error。
Error是程序无法处理的错误,一旦出现这个错误,则程序将被迫停止运行。
Exception不会导致程序停止,又分为两个部分RunTimeException运行时异常和CheckedException检查异常。RunTimeException常常发生在程序运行过程中,会导致程序当前线程执行失败。CheckedException常常发生在程序编译过程中,会导致程序编译不通过。
GC如何判断对象可以被回收
·引用计数法:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以向收,
·可达性分析法:从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,那么虚拟机就判断是可回收对象。
引用计数法,可能会出现A引用了B,B又引用了A,这时候就算他们都不再使用了,但因为相互引用计数器=1永远无法被回收。
GC Roots的对象有:
·虚拟机栈(栈帧中的本地变量表)中引用的对象·方法区中类静态属性引用的对象
·方法区中常量引用的对象
·本地方法栈中JNI(即一般说的Native方法)引用的对象
线程的生命周期?线程有几种状态
1.线程通常有五种状态,创建,就绪,运行、阻塞和死亡状态。
⒉.阻塞的情况又分为三种:
(1)、等待阻塞:运行的线程执行wait方法,该线程会释放占用的所有资源,JVM会把该线程放入"等待池"中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify或notifyAll方法才能被唤醒,wait是object类的方法
(2)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入"锁池”中。
(3)、其他阻塞:运行的线程执行sleep或join方法,或者发出了IO请求时,JVM会把该线程置为阻塞状态。当sleep状态超时、join等待线程终止或者超时、或者/O处理完毕时,线程重新转入就绪状态。sleep是Thread类的方法
1.新建状态(New) :新创建了一个线程对象。
⒉就绪状态(Runnable)∶线程对象创建后,其他线程调用了该对象的start方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3.运行状态(Running) :就绪状态的线程获取了CPU,执行程序代码。
4.阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
ZH
sleep()、wait(). join().yield()的区别
1.锁池
所有需要竞争同步锁的线程都会放在锁池当中,比如当前对象的锁已经被其中一个线程得到,则其他线程
需要在这
个锁池进行等待,当前面的线程释放同步锁后锁池中的线程去竞争同步锁,当某个线程得到后会进入就绪队列进行等待cpu资源分配。
⒉等待池
当我们调用wait ()方法后,线程会放到等待池当中,等待池的线程是不会去竞争同步锁。只有调用了notify ()或notifyAlI()后等待池的线程才会开始去竞争锁,notify ()是随机从等待池选出一个线程放到锁池,而notifyAllp)是将等待池的所有线程放到锁池当中
1、sleep是Thread类的静态本地方法,wait则是Object类的本地方法。
2、sleep方法不会释放lock,但是wait会释放,而且会加入到等待队列中。
sleep就是把cpu的执行资格和执行权释放出去,不再运行此线程,当定时时间结束再取回cpu资源,参与cpu的调度,获取到cpu资源后就可以继续运行了。而如果s leep时该线程有锁,那么s1eep不会释放这个锁,而是把锁带着进入了冻结状态,也就是说其他需要这个锁的线程根本不可能获取到这个锁。也就是说无法执行程序。如果在睡眠期间其他线程调用了这个线程的interrupt方法,那么这个线程也会抛出interruptexception异常返回,这点和wait是一样的。
3、sleep方法不依赖于同步器synchronized,但是wait需要依赖synchronized关键字。
4、sleep不需要被唤醒(休眠之后推出阻塞),但是wait需要(不指定时间需要被别人中断)。
5、sleep一般用于当前线程休眠,或者轮循暂停操作,wait则多用于多线程之间的通信。
6、sleep会让出CPU执行时间且强制上下文切换,而wait则不一定,wait后可能还是有机会重新竞争到锁继续执行的
yield () 执行后线程直接进入就绪状态,马上释放了cpu的执行权,但是依然保留了cpu的执行资格,所以有可能cpy下次进行线程调度还会让这个线程获取到执行权继续执行
join ()执行后线程进入阻塞状态,例如在线程B中调用线程A的join (),那线程B会进入到阻塞队列,直到线程A结束或中断线程
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnab1e( i
override
public void run( i
try i
Thread.sleep(3000);
}catch (InterruptedException e) i
e.printstackTrace();
}
system.out.print1n("22222222");
}
});
t1.start(;t1.join(;
//这行代码必须要等t1全部执行完毕,才会执行system.out.print1n(""1111");
2023 2/27
Thread、Runable的区别
Thread和Runnable的实质是继承关系,没有可比性。无论使用Runnable还是Thread,都会new Thread,然后执行run方法。用法上,如果有复杂的线程操作需求,那就选择继承Thread,如果只是简单的执行一个任务
那就实现runnable。
1:会出现十张票,因为n
package org.example.多线程;/*** @author ftz-lover* @version 1.0* @date 2023/2/27 11:24*/
//会卖出多一倍的票
public class test2 {public static void main(String[] args) {
// ToD0 Auto-generated method stubnew MyThread().start();new MyThread().start();}static class MyThread extends Thread {private int ticket = 5;public void run(){while (true) {System.out.println("Thread ticket = " + ticket--);if (ticket < 0) {break;}}}}
}
对于守护线程的理解
守护线程:为所有非守护线程提供服务的线程;任何一个守护线程都是整个JVM中所有非守护线程的保姆;
守护线程类似于整个进程的一个默默无闻的小喽喽;它的生死无关重要,它却依赖整个进程而运行;哪天其他线程结束了,没有要执行的了,程序就结束了,理都没理守护线程,就把它中断了;
注意:由于守护线程的终止是自身无法控制的,因此千万不要把IO、File等重要操作逻辑分配给它;因为它不靠谱;
守护线程的作用是什么?
举例,GC垃圾回收线程:就是一个经典的守护线程,当我们的程序中不再有任何运行的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是VM上仅剩的线程时,垃圾回收线程会自动离开。它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。
应用场景:(1)来为其它线程提供服务支持的情况;(2)或者在任何情况下,程序结束时,这个线程必须正常且立刻关闭,就可以作为守护线程来使用;反之,如果一个正在执行某个操作的线程必须要正确地关闭掉否则就会出现不好的后果的话,那么这个线程就不能是守护线程,而是用户线程。通常都是些关键的事务,比方说,数据库录入或者更新,这些操作都是不能中断的。
thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个llegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
在Daemon线程中产生的新线程也是Daemon的。
守护线程不能用于去访问固有资源,比如读写操作或者计算逻辑。因为它会在任何时候甚至在一个操作的中间发生中断。