~1、对象传的是引用,变量传的是变量值。 (对象可无法放入到变量中)
~2、char
~3、按字节截取字符串,不能出现半个汉字的样式。
~4 finally语句到底是在return之前还是之后执行?
~5、内部类
~6、 修饰符作用域
~7、Object
~8、this()
~9、静态方法&实例变量
~10、jdk&jre
~11、过滤器拦截器区别
~12、代理、反射、注解、切面
~13、过滤器也可以注入service的,可以在过滤器的init()初始化方法或者dofilter()方法里面手动初始化上下文然后来获取
~14、hashmap&concurrentmap
~15、B树
~16、双亲委派
~17、fail-fast(跟iterator相关)
~18、Volatile和Synchronized四个不同点:
~19、hashtable&concurrenthashmap
~20、 开放题:
~21、加密 AES,RSA
~22、类加载器工作机制:
~23、lambda
~1、对象传的是引用,变量传的是变量值。 (对象可无法放入到变量中)
数据拷贝不会改变实参内容,引用拷贝可以改变实参内容,但不会改变实参的引用地址
无论如何java都是值传递,copy一份到方法中,如果是基本数据类型就是传的copy出来的那份,对源数据没影响,如果是对象
就是传对象引用,不可能那么大一个对象传过去,那么就会影响到源数据,因为string是定义为final的,所以也不会有影响。
java所有的参数传递都是传递的副本,变量所代表的值的副本
如果 一个字符串是 String s = “abc”;它放在栈里
如果一个字符串 用创建对象的方式 String s = new String(“abc”);
那它是放在了 堆里 而如果单纯的 一个 “abc” 这个输入字符串常量 是放在方法区里(类信息、常量、静态变量、即时编译器编译后的代码)
值传递和引用传递有什么区别 转载链接:https://zhidao.baidu.com/question/295083208.html
值传递会复制一份值到当前栈行中。栈行中放方法名以及形参
声明对象的引用并不会创建对象,创建对象是通过new来创建的,当然还有其他方式。
~2、char
char i = ‘a’;
int j = (int)i;
a的ASCII码97 z:122;A-Z:65-90
String str= “”;
char c = str.charAt(i);
String.valueOf©.getBytes(“GBK”).length>1 中文汉字为两个字节
~3、按字节截取字符串,不能出现半个汉字的样式。
(思路:按字符取值,中文字符转GBK格式字节长度>1,如果遇到中文字符则字节数count-1)
String s = “我ABC汗DEF”;
System.out.println(cutstring(s,4));
System.out.println(cutstring(s,6));
public static String cutstring (String original,int count)throws Exception{
if(StringUtils.isNotBlank(original)){
original = new String(original.getBytes(),“GBK”);
if(count>0&&count<original.getBytes(“GBK”).length){
StringBuffer b = new StringBuffer();
char c;
for(int i = 0;i<count;i++){
c = original.charAt(i);
System.out.println©;
System.out.println("i "+i);
if(String.valueOf(c).getBytes("GBK").length>1){
System.out.println("count-- "+c);
count--;
}
if(i<count){
b.append(c);
}
}
return b.toString();
}
}
return original;
}
UTF-8和GBK有什么区别?
个人:gbk有所有中文 ,字符都是2字节,国家编码,纯中文省空间
utf-8有所有字符部分中文 ,英文1字节,中文3字节,国际通用,英文多省空间
字符均使用双字节来表示,只不过为区分中文,将其最高位都定成1。
至于UTF-8编码则是用以解决国际上字符的一种多字节编码,它对英文使用8位(即一个字节),中文使用24位(三个字节)来编码。对于英文字符较多的论坛则用UTF-8节省空间。
GBK包含全部中文字符;UTF-8则包含全世界所有国家需要用到的字符。
GBK是在国家标准GB2312基础上扩容后兼容GB2312的标准(好像还不是国家标准)
UTF-8编码的文字可以在各国各种支持UTF8字符集的浏览器上显示。
比如,如果是UTF8编码,则在外国人的英文IE上也能显示中文,而无需他们下载IE的中文语言支持包。 所以,对于英文比较多的论坛 ,使用GBK则每个字符占用2个字节,而使用UTF-8英文却只占一个字节。
UTF8是国际编码,它的通用性比较好,外国人也可以浏览论坛,GBK是国家编码,通用性比UTF8差,不过UTF8占用的数据库比GBK大
~4 finally语句到底是在return之前还是之后执行?
结论:
1、不管有木有出现异常,finally块中代码都会执行;
2、当try和catch中有return时,finally仍然会执行;
3、finally是在return语句执行之后,返回之前执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,不管finally中的代码怎么样,返回的值都不会改变,仍然是之前保存的值),所以函数返回值是在finally执行前就已经确定了;
4、finally中如果包含return,那么程序将在这里返回,而不是try或catch中的return返回,返回值就不是try或catch中保存的返回值了。
注意:
finally修改的基本类型是不影响返回结果的。(传值的)
修改list ,map,自定义类等引用类型时,是影响返回结果的。(传址的)对象也是传址的
但是date类型经过测试是不影响的。有点奇怪。
~5、内部类
一、成员内部类
作为外部类的一个成员存在,与外部类的属性、方法并列。
成员内部类也是定义在另一个类中,但是定义时不用static修饰。
成员内部类和静态内部类可以类比为非静态的成员变量和静态的成员变量。
成员内部类就像一个实例变量。
它可以访问它的外部类的所有成员变量和方法,不管是静态的还是非静态的都可以。
二、方法内部类(局部内部类)
定义在方法中,比方法的范围还小。是内部类中最少用到的一种类型。
像局部变量一样,不能被public, protected, private和static修饰。
只能访问方法中定义的final类型的局部变量。
方法内部类在方法中定义,所以只能在方法中使用,即只能在方法当中生成方法内部类的实例并且调用其方法
三、匿名内部类(Android运用最多)
匿名内部类就是没有名字的局部内部类,不使用关键字class, extends, implements, 没有构造方法。
什么情况下需要使用匿名内部类?如果满足下面的一些条件,使用匿名内部类是比较合适的:
a·只用到类的一个实例。
b·类在定义后马上用到。
c·类非常小(SUN推荐是在4行代码以下)
d·给类命名并不会导致你的代码更容易被理解。
在使用匿名内部类时,要记住以下几个原则:
a·匿名内部类不能有构造方法。
b·匿名内部类不能定义任何静态成员、方法和类。
c·匿名内部类不能是public,protected,private,static。
d·只能创建匿名内部类的一个实例。
e·一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类。
f·因匿名内部类为局部内部类,所以局部内部类的所有限制都对其生效。
成员内部类:不能被static修饰、可以访问外部类所有变量方法(内部类靠外部类存活)
局部内部类:不能被private、static、protected,public修饰、只能访问方法中定义的final类型的局部变量、只能在方法当中生成方法
内部类的实例并且调用其方法
匿名内部类:不能被private、static、protected,public修饰、不能有构造、静态,只能有一个实例。没有名字的局部内部类,不使用关
键字class, extends, implements, 没有构造方法。
局部内部类:只能访问方法中的final参数
静态嵌套类(静态嵌套类不能称为静态内部类 因为内部类都是非静态的)
在Java中不能使最顶层类为静态类,而只有嵌套的类可以是静态类,
非静态嵌套类通常被称为内部类。
嵌套静态类不需要外部类的引用,但是非静态嵌套类或者内部类需要外部类的引用。(Nested static class doesn’t need reference of Outer class, but Non-static nested class or Inner class requires Outer class reference.)
内部类(非静态嵌套类)可以访问外部类的静态与非静态成员。静态类不能够访问外部类的非静态成员。
创建内部类的实例,在没有外部类的实例时,无法创建。内部类可引用其所在外部类的数据与方法。因此我们不需要传递对象的引用给内部类的构造器。因为这一点,内部类使得程序变得更简洁。
内部类的实例化对象需要绑定一个外围类的实例化对象,而静态嵌套类的实例化对象不能也无法绑定外围类的实例化对象。
~6、 修饰符作用域
作用域 当前类 同一package 子孙类 其他package
public √ √ √ √
protected √ √ √ ×
friendly √ √ × ×
private √ × × ×
~7、Object
Object并非抽象,因为它可以被继承下来的方法都实现了代码,没有必须被覆盖的代码。
什么时候需要自己创建Object:有时候需要一个通用的对象或者一个轻量化的对象,也用在线程的同步化上
ArrayList mydog = new ArrayList();
Dog dog = new Dog();
mydog.add(dog);
如果是这样,当你尝试把dog对象取出并复制给dog引用时,无法通过编译,编译器无法确认它是dog
当然,mydog.bark()也不能这么做。编译器是根据引用类型来判断有哪些method可以调用,而不是根据Object确实的类型。编译器只管引用
的类型而不管对象的类型。
Object o = mydog.get(1);
Dog dog2 = (Dog)o; dog2.bark();//将类型转换成Dog
现在你知道java有多么注重引用变量的类型。你只能在引用变量的类确实有该方法的时候才能调用它。
把类的公有方法当做是合约的内容,合约是你对其他程序的承诺协议。
~8、this()
prv:this相当于对象。对象肯定不能调用属于类的静态方法。
this是对于当前类对象的引用,this如果直接调用类方法编译器会出现警告,理论上说类方法是类所共有,实例也可以访问,但一般都是
用类名来显式调用。
this是对于当前类对象的引用,不能用来调用类方法.类方法不能调用非static方法是就本类方法的直接调用而言,可以将类实例化后调用.
this()只能在构造函数中,且必须是第一行语句。super()跟this()不能兼得
~9、静态方法&实例变量
静态方法不能调用实例变量
静态方法是在无关特定类的实例的情况下执行的,静态方法是通过类名来调用的,所以静态方法无法引用到该类的任何实例变量。在此情况下
,静态方法也不会知道可以使用哪个实例变量值。
如果你尝试在静态方法内使用实例变量,编译器会认为“我不知道你说的是哪个实例的变量”,静态方法是不知道堆上有哪些实例的
静态方法也不能调用非静态方法,因为如果非静态方法里面有用到实例变量呢
静态变量:它的值对所有实例来说都相同。静态变量在类被加载时被初始化的。类会被加载是因为java虚拟机认为它该被加载了。通常java
虚拟机会加载某个类时因为第一次有人尝试要创建该类的新实例,或是使用该类的静态方法或变量。
静态变量会在该类的任何静态方法执行之前就被初始化。
静态变量为该变量所属类的成员所共享,静态变量只要一份,而不是每个实例都会有自己的一份
静态方法可以存取静态变量
~10、jdk&jre
JDK(Java Development Kit) 是 Java 语言的软件开发工具包(SDK)。
JDK是整个JAVA的核心,包括了jvm,一堆Java工具(javac/java/jdb等)和Java基础的类库(即Java API )。
有以下三种版本:
SE(J2SE),standard edition,标准版,是我们通常用的一个版本,从JDK 5.0开始,改名为Java SE。
EE(J2EE),enterprise edition,企业版,使用这种JDK开发J2EE应用程序,从JDK 5.0开始,改名为Java EE。
ME(J2ME),micro edition,主要用于移动设备、嵌入式设备上的java应用程序,从JDK 5.0开始,改名为Java ME。
JRE为Java Runtime Environment的简称包括Java虚拟机(jvm)、Java核心类库和支持文件。
JVM即Java Virtual Machine(Java虚拟机),JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模
拟各种计算机功能来实现的。
Java语言的一个非常重要的特点就是与平台的无关性。而使用Java虚拟机是实现这一特点的关键。一般的高级语言如果要在不同的平台上运行,
至少需要编译成不同的目标代码。而引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了
与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。
Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。这就是Java的能够“一次编译,到处运行”的原因。
我们开发的实际情况是:我们利用JDK(调用JAVA API)开发了属于我们自己的JAVA程序后,通过JDK中的编译程序(javac)将我们的文本
java文件编译成JAVA字节码,在JRE上运行这些JAVA字节码,JVM解析这些字节码,映射到CPU指令集或OS的系统调用。
~11、过滤器拦截器区别
转载链接:https://blog.csdn.net/reggergdsg/article/details/52962774
转载链接:https://www.cnblogs.com/panxuejun/p/7715917.html
拦截器基于java反射机制,过滤器基于回调机制
过滤器在servlet之前,拦截器在servlet之后控制器之前,可以访问spring的各个bean
本来拦截器就是spring的,而过滤器是javaee的,所以不能用到spring注入bean那一套
过滤器和拦截器的区别:
1拦截器是基于java的反射机制的,而过滤器是基于函数回调。
2拦截器不依赖与servlet容器,过滤器依赖与servlet容器。
3拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用。
4拦截器可以访问action上下文、值栈里的对象,而过滤器不能访问。
5在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次。
6拦截器可以获取IOC容器中的各个bean,而过滤器就不行,这点很重要,在拦截器里注入一个service,可以调用业务逻辑
过滤器和拦截器触发时间和地点不一样:
过滤器是在请求进入容器后,但请求进入servlet之前进行预处理的。请求结束返回也是,是在servlet处理完后,返回给前端之前。
总结:过滤器包裹住servlet,servlet包裹住拦截器。
拦截器,springmvc框架提供了一个已经实现了拦截器接口的适配器类HandlerInterceptorAdapter,继承这个类然后重写一下需要用到的方
法就行了
监听器监听不涉及流程修改的信息,过滤器过滤web访问,拦截器拦截业务流程操作
~12、代理、反射、注解、切面
注解:相当于是配置,只是用来获取配置的信息,注解本身就是用来代替xml的配置的,可以根据自定义参数值来个性化操作
切面:相当于方法的操作,只要指定切点的路径就可以对监听的范围进行操作,而不用去被切入的地方添加任何注解了
代理:静态代理只能针对一个具体的类,但是动态代理能针对一个接口类的所有实体,动态代理生成的是通用代理类,静态
代理生成的是非通用具体的代理类。aop切面也是属于代理,也就是在调用方法的前后增加自定义操作。
jdk涉及接口,cglib生成子类,静态代理我们是直接调用代理类而不是初始类(被代理类),至于动态代理应该也是
直接调用代理类,至于怎么调用代理类还需要再看,或者就是代理机制底层实现的了
反射:上面3个涉及通用性操作的都使用到了反射。运行时动态获取类及类信息也可实例化对象
切面:定义切面,切面里定义切点,切点下可以只是一个空方法,before(pointcut)或者after(poingcut)
再对切点做自定义操作.如果结合注解使用,即切点指向注解
* 选取切入点为自定义注解
@Pointcut("@annotation(com.honeywen.credit.annotation.PermissionCheck)")
然后任何使用到该注解的方法或域上都会生效
转载链接:https://blog.csdn.net/program_guys/article/details/78724090
切面+注解跟直接使用注解类似,都是想要在个别方法上使用切点
@Before(value = “permissionCheck()”)
public void before(JoinPoint joinPoint) throws NoSuchMethodException {
// 获取连接点的方法签名对象
Signature signature = joinPoint.getSignature();
if (!(signature instanceof MethodSignature)) {
throw new PermissionCheckException(“user permission check failed , stop the request!”);}
MethodSignature methodSignature = (MethodSignature) signature;
Object target = joinPoint.getTarget();
// 获取到当前执行的方法
Method method = target.getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
// 获取方法的注解
PermissionCheck annotation = method.getAnnotation(PermissionCheck.class);
~13、过滤器也可以注入service的,可以在过滤器的init()初始化方法或者dofilter()方法里面手动初始化上下文然后来获取
service的bean。有三种方式加载applicationContext,xmlapplicationcontext,filesystemapplication,classpathapplication
解决方法一:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
转载链接:HttpServletRequest req = (转载链接:HttpServletRequest)request;
转载链接:HttpServletResponse resp = (转载链接:HttpServletResponse)response;
ServletContext sc = req.getSession().getServletContext();
XmlWebApplicationContext cxt = (XmlWebApplicationContext)WebApplicationContextUtils.getWebApplicationContext(sc);
if(cxt != null && cxt.getBean(“usersService”) != null && usersService == null)
usersService = (UsersService) cxt.getBean(“usersService”);
Users users = this.usersService.queryByOpenid(openid);
方法二:
public class WeiXinFilter implements Filter{
private UsersService usersService;
public void init(FilterConfig fConfig) throws ServletException {
ServletContext sc = fConfig.getServletContext();
XmlWebApplicationContext cxt = (XmlWebApplicationContext)WebApplicationContextUtils.getWebApplicationContext(sc);
if(cxt != null && cxt.getBean(“usersService”) != null && usersService == null)
usersService = (UsersService) cxt.getBean(“usersService”);
}
~14、hashmap&concurrentmap
转载链接:https://www.cnblogs.com/chengxiao/p/6842045.html
ConcurrentHashMap1.7和1.8的不同实现 segment(RetreenLock&volatile)+HashEntry node+CAS+synchronized
转载链接:https://blog.csdn.net/y277an/article/details/77696612
concurrentmap:锁颗粒度细,桶级锁。ConcurrentHashMap采用了非常精妙的"分段锁"策略,ConcurrentHashMap的主干是
个Segment数组。
Segment继承了ReentrantLock。get方法无需加锁,由于其中涉及到的共享变量都使用volatile修饰,volatile可以保证内存可见
性,所以不会读取到过期数据。
jdk1.8,链表长度大于8时转换为红黑树
HashMap线程不安全、hashtable线程安全锁整个表,concurrentMap线程安全分离锁锁一分部,但是会导致数据一致性问题。因为
锁的是写入操作,你在写入别人可以读。
转载链接:https://www.cnblogs.com/wearebest/p/8434562.html
转载链接:https://www.cnblogs.com/John-Chen/p/4375046.html
String、Integer这样的wrapper类 都是final的。基本类型的包装类都是final的,都适合做hashmap的key
数组链表结构,数据放key的hash值,链表放相同hash值不同key的键值对,找某个value时就是先根据key的hash值计算定位到
数组位子,然后根据key.equals()来获取到键值对。如果整个map容量超过负载因子0.75(75%),hashmap会自动
扩容一倍,rehashing把原来的元素重新放到新的数据链表里,而且默认是放链尾,现在转而放链头,避免尾部遍历:
转载链接:https://blog.csdn.net/xzongyuan/article/details/72615862
尾部遍历是为了避免在新列表插入数据时,遍历队尾的位置。因为,直接插入的效率更高。
直接采用队头插入,会使得链表数据倒序
例如原来顺序是:10 20 30 40插入顺序如下
10
20 10
30 20 10
40 30 20 10
但对resize()的设计来说,本来就是要创建一个新的table,列表的顺序不是很重要。
但如果要确保插入队尾,还得遍历出链表的队尾位置,然后插入,是一种多余的损耗。
存在问题:
但是采用队头插入的方式,导致了HashMap在“多线程环境下”的死循环问题:转载链接:http://blog.csdn.net/xiaohui127/article/details/11928865
转载链接:https://www.cnblogs.com/devilwind/p/8044291.html
prv:老式hashmap扩容容易导致死锁,同一个key下的entry两个对象,一个a指向b,当cpu切换到另一条线程时,由于对头插入的特性,
b先存然后b指向a,导致死锁。
碰撞:也就是相同hash值有不同的键值对,这个时候就要把键值放到同一个hash数组下的桶里,也就是用链表来解决碰撞,但是如果
多个键值都在同一个hash下,那么性能就会降低了,jdk1.8应对这个问题就出现了当链表长度超过8时就会变成红黑树。提高查询效率。
转载链接:https://blog.csdn.net/luo_da/article/details/77507315
通过上面可知如果多个hashCode()的值落到同一个桶内的时候,这些值是存储到一个链表中的。最坏的情况下,所有的key都映
射到同一个桶中,这样hashmap就退化成了一个链表——查找时间从O(1)到O(n)。也就是说我们是通过链表的方式来解决这个Hash
碰撞问题的。
JDK 8中从O(n)到O(logn)的飞跃,可以有效地防止类似的攻击
不可变性是必要的,因为为了要计算hashCode(),就要防止键值改变,如果键值在放入时和获取时返回不同的
hashcode的话,那么就不能从HashMap中找到你想要的对象。不可变性还有其他的优点如线程安全。如果你可以仅
仅通过将某个field声明成final就能保证hashCode是不变的,那么请这么做吧。因为获取对象的时候要用到equals()
hashCode()方法,那么键对象正确的重写这两个方法是非常重要的。如果两个不相等的对象返回不同的hashcode的话,
那么碰撞的几率就会小些,这样就能提高HashMap的性能。
HashMap中用的最多的方法就属put() 和 get() 方法;HashMap的Key值是唯一的,那如何保证唯一性呢?
我们首先想到的是用equals比较,没错,这样可以实现,但随着内部元素的增多,put和get的效率将越来越低
,这里的时间复杂度是O(n),假如有1000个元素,put时最差情况需要比较1000次。实际上,HashMap很少会用
到equals方法,因为其内通过一个哈希表管理所有元素,哈希是通过hash单词音译过来的,也可以称为散列表,
哈希算法可以快速的存取元素,当我们调用put存值时,HashMap首先会调用Key的hash方法,计算出哈希码,
通过哈希码快速找到某个存放位置(桶),这个位置可以被称之为bucketIndex,但可能会存在多个元素找到了
相同的bucketIndex,有个专业名词叫碰撞,当碰撞发生时,这时会取到bucketIndex位置已存储的元素,
最终通过equals来比较,equals方法就是碰撞时才会执行的方法,所以前面说HashMap很少会用到equals。
HashMap通过hashCode和equals最终判断出Key是否已存在,如果已存在,则使用新Value值替换旧Value值,
并返回旧Value值,如果不存在 ,则存放新的键值对<K, V>到bucketIndex位置
我们可以使用CocurrentHashMap来代替Hashtable吗?这是另外一个很热门的面试题,因为ConcurrentHashMap
越来越多人用了。我们知道Hashtable是synchronized的,但是ConcurrentHashMap同步性能更好,因为它仅仅根
据同步级别对map的一部分进行上锁。ConcurrentHashMap当然可以代替HashTable,但是HashTable提供更强的线
程安全性。看看这篇博客查看Hashtable和ConcurrentHashMap的区别。
hashtable为什么键和值不能为null
因为hashtable,concurrenthashmap它们是用于多线程的,并发的 ,如果map.get(key)得到了null,
不能判断到底是映射的value是null,还是因为没有找到对应的key而为空,而用于单线程状态的hashmap却可以用
containKey 去判断到底是否包含了这个null。
hashtable为什么就不能containKey(key)
一个线程先get(key)再containKey(key),这两个方法的中间时刻,其他线程怎么操作这个key都会可能发生,
例如删掉这个key
另一个区别是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。
所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,
hashmap cpu 现象 转载链接:https://blog.csdn.net/dongxurr123/article/details/77829038
jdk1,6常量池放在方法区,jdk1.7常量池放在堆内存,jdk1.8放在元空间里面,和堆相独立。所以导致string的intern方法因为以上变化
在不同版本会有不同表现
~15、B树
转载链接:https://blog.csdn.net/zwz2011303359/article/details/63262541
B树的搜索,从根结点开始,如果查询的关键字与结点的关键字相等,那么就命中;
否则,如果查询关键字比结点关键字小,就进入左儿子;如果比结点关键字大,就进入
右儿子;如果左儿子或右儿子的指针为空,则报告找不到相应的关键字
实际使用的B树都是在原B树的基础上加上平衡算法,即“平衡二叉树”;如何保持B树
结点分布均匀的平衡算法是平衡二叉树的关键
B-树是一种平衡的多路查找树,它在文件系统中很有用,
转载链接:https://blog.csdn.net/zwz2011303359/article/details/63262541
在B-树的查找过程为:
在B- 树中查找结点
在结点中查找关键字。
MySQL底层,b-树&b+树 都是多路搜索树
因为mysql底层是为了减少IO操作,对于机械硬盘等储存器来说都是靠寻找轨道,转到扇区,然后移动机械臂到对应点上来读取资料的,这样
如果数据有序存放或者跨度小,那么将会减少机械臂寻址时间。B+树正好是少节点,每个节点下多子节点多数据,一层可以放百万数据,这样
一般三层就可以找到要找的东西了。
5.5前mysql引擎用MYISAM 5.5后用INNODB,innodb是聚集索引,即使索引跟数据结合在一起,innodb存数据就是按照b+树来存的,增删快
访问快。myisam是按照非聚集所以来存放数据的。左侧优先索引是由于组合索引建立是按照abc来建的,如果查bc则用不到索引,如果都是=
则mysql会自动调整
平衡二叉树子节点就两个,B-多子节点,B+子节点跟相邻子节点有序关联
~ 16、双亲委派
转载链接:https://www.cnblogs.com/wxd0108/p/6681618.html
(prv:让父类加载器加载,同理一直推到顶级父类,如果定级父类没找到对应加载文件再让类自己的加载器去加载)
双亲委派模型:如果一个类加载器收到了加载某个类的请求,则该类加载器并不会去加载该类,而是把这个请求委派给父类加载器,
每一个层次的类加载器都是如此,因此所有的类加载请求最终都会传送到顶端的启动类加载器;只有当父类加载器在其搜索范围内无法找到所需的类,
(也就是在指定目录路径下无法找到对应类class文件时)
并将该结果反馈给子类加载器,子类加载器会尝试去自己加载.
双亲委派模式的工作原理的是;如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,
如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,
就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式,即每个儿子都不愿意干活,每次有活就
丢给父亲去干,直到父亲说这件事我也干不了时,儿子自己想办法去完成,这不就是传说中的双亲委派模式
不是同一个父类加载器加载的两个对象比较是不会相等的。
如果没有使用双亲委派模型,由各个类加载器自行去加载的话,如果用户自己编写了一个称为java.lang.Object的类,并用自定义的类加
载器加载,那系统中将会出现多个不同的Object类,Java类型体系中最基础的行为也就无法保证,应用程序也将会变得一片混乱。
检验一个类是否合法,自定义加载器
Bootstrap ClassLoader(启动类加载器)负责将%JAVA_HOME%/lib目录中或-Xbootclasspath中参数指定的路径中的,并且是虚拟机识别
的(按名称)类库加载到JVM中
Extension ClassLoader(扩展类加载器)负责加载%JAVA_HOME%/lib/ext中的所有类库
Application ClassLoader(应用程序加载器) 负责ClassPath中的类库
转载链接:https://blog.csdn.net/weixin_38055381/article/details/80167881
双亲委派模式优势
1、避免类的重复加载,保证javaapi的安全性
采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,
当父类已经加载了该类时,就没有必要子ClassLoader再加载一次。其次是考虑到安全因素,java核心api中定义类型不会被随意替换,
假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这
个名字的类,
发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,
这样便可以防止核心API库被随意篡改。
类加载的三种方式:
new,Class.forName().newInstance(),调用某个ClassLoader实例的loadClass()
1,2都是当前类加载器,3是由用户指定的加载器。
1是静态加载,2,3是动态加载。
两个异常:静态加载抛NoClassDefFoundError,动态加载抛ClassNotFoundException
Class装载包括几个步骤:加载,实例化,连接,初始化(加载class文件,连接内存或者堆中对象生成引用地址,初始化静态变量常量)
~17、fail-fast(跟iterator相关)
机制是java集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。
例如:当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程A访问集合时,就会抛出
ConcurrentModificationException异常,产生fail-fast事件(prv跟iterator有关)
for循环一个list,调用list.remove()方法也会报异常,但是用iterator遍历,用it.next()改变指针默认是指向上一个元素的
再用it.remove()这样就不会报异常。
~18、Volatile和Synchronized四个不同点:
1 粒度不同,前者针对变量 ,后者锁对象和类
2 syn阻塞,volatile线程不阻塞
3 syn保证三大特性,volatile不保证原子性
4 syn编译器优化,volatile不优化 volatile具备两种特性:
1.保证此变量对所有线程的可见性,指一条线程修改了这个变量的值,新值对于其他线程来说是可见的,
但并不是多线程安全的。
2.禁止指令重排序优化。
Volatile如何保证内存可见性:Java的内存模型(Java Memory Model,简称JMM)
1.当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存。
2.当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。
什么是指令重排序?有两个层面:
在虚拟机层面,为了尽可能减少内存操作速度远慢于CPU运行速度所带来的CPU空置的影响,虚拟机会按照自己的
一些规则(这规则后面再叙述)将程序编写顺序打乱——即写在后面的代码在时间顺序上可能会先执行,而写在前面
的代码会后执行——以尽可能充分地利用(前提是不影响程序逻辑结果)
转载链接:https://www.cnblogs.com/takumicx/p/9302398.html
为什么要指令重排
prv:只有类似创建一个对象这种才会发生指令重排序,普通多行代码并不会随便排。
// 懒汉式单例
public class Singleton2 {
private Singleton2() {}
private static Singleton2 single = null;
public static Singleton2 getInstance() {
if(single == null){
single = new Singleton2(); }
return single;}}
1.为对象分配内存空间
2.初始化对象
3.将INSTANCE变量指向刚分配的内存地址
由于步骤2,3交换不会改变单线程环境下的执行结果,故而这种重排序是被允许的。也就是我们在初始化对象
之前就把INSTANCE变量指向了该对象。而如果这时另一个线程刚好执行到代码所示的2处
if (INSTANCE == null)
那么这时候有意思的事情就发生了:虽然INSTANCE指向了一个未被初始化的对象,但是它确实不为null了,
所以这个判断会返回false,之后它将return一个未被初始化的单例对象!
~19、hashtable&concurrenthashmap
转载链接:https://www.cnblogs.com/softhyb/p/5302996.html
当对ConcurrentHashMap进行remove操作时,并不是进行简单的节点删除操作,对比上图,当对ConcurrentHashMap的一个segment
也就是一个桶中的节点进行remove后,例如删除节点C,C节点实际并没有被销毁,而是将C节点前面的反转并拷贝到新的链表中,C节点后
面的不需要被克隆。这样来保持并发的读线程不受并发的写线程的干扰。例如现在有一个读线程读到了A节点,写线程把C删掉了,但是看上图
,读线程仍然可以继续读下去;当然,如果在删除C之前读线程读到的是D,那么更不会有影响。
转载链接:https://blog.csdn.net/dfsaggsd/article/details/50572974
在使用锁来协调多线程间并发访问的模式下,减小对锁的竞争可以有效提高并发性。有两种方式可以减小对锁的竞争:
1. 减小请求 同一个锁的 频率。
2. 减少持有锁的 时间。
ConcurrentHashMap 的高并发性主要来自于三个方面:
1. 用分离锁实现多个线程间的更深层次的共享访问。
2. 用 HashEntery 对象的不变性来降低执行读操作的线程在遍历链表期间对加锁的需求。
3. 通过对同一个 Volatile 变量的写 / 读访问,协调不同线程间读 / 写操作的内存可见性。
使用分离锁,减小了请求 同一个锁的频率。
通过 HashEntery 对象的不变性及对同一个 Volatile 变量的读 / 写来协调内存可见性,使得 读操作大多数时候不需要加锁就能成
功获取到需要的值。由于散列映射表在实际应用中大多数操作都是成功的读操作,所以 2 和 3 既可以减少请求同一个锁的频率,
也可以有效减少持有锁的时间。
通过减小请求同一个锁的频率和尽量减少持有锁的时间 ,使得 ConcurrentHashMap 的并发性相对于 HashTable 和用同步包装器包装
的 HashMap有了质的提高。
~ hashmap JDK1.7\1.8区别
使用一个Entry数组来存储数据,用key的hashcode取模来决定key会被放到数组里的位置,如果hashcode相同,或者hashcode取模
后的结果相同(hash collision),那么这些key会被定位到Entry数组的同一个格子里,这些key会形成一个链表。
在hashcode特别差的情况下,比方说所有key的hashcode都相同,这个链表可能会很长,那么put/get操作都可能需要遍历这个链表
也就是说时间复杂度在最差情况下会退化到O(n)
JDK1.8中
使用一个Node数组来存储数据,但这个Node可能是链表结构,也可能是红黑树结构
如果插入的key的hashcode相同,那么这些key也会被定位到Node数组的同一个格子里。
如果同一个格子里的key不超过8个,使用链表结构存储。
如果超过了8个,那么会调用treeifyBin函数,将链表转换为红黑树。
那么即使hashcode完全相同,由于红黑树的特点,查找某个特定元素,也只需要O(log n)的开销
也就是说put/get的操作的时间复杂度最差只有O(log n)
concurrenthashmap1.7 线程安全,用reteenlock实现的 retreenlock是基于cas乐观锁实现的 synchronize悲观锁
concurrenthashmap1.8 线程安全,基于cas跟synchronize实现的
hashmap1.7 桶实现,都是非线程安全的
hashmap1.8 node跟红黑树,都是非线程安全的
hashmap没锁、concurrenthashmap有锁
hashmap没锁,concurrenthashmap用retreenlock实现锁
用synchronize的地方可能可以用concurrenhashmap替换,volatile不能保证原子性,synchronize能保证原子性。
~20、 开放题:
1、大数据:在于分块跟排序,大分成小
2、小数据统计:在于转换成占用资源少的,小操作频繁要注意效率资源节约
~21、加密 AES,RSA
转载链接:https://www.cnblogs.com/frank-quan/p/7073457.html
对称加密是最快速、最简单的一种加密方式,加密(encryption)与解密(decryption)用的是同样的密钥(secret key)。
高级加密标准(AES–Advanced Encryption Standard)
常用的对称加密有:DES、IDEA、RC2、RC4、SKIPJACK、RC5、AES算法等
非对称加密为数据的加密与解密提供了一个非常安全的方法,它使用了一对密钥,公钥(public key)和私钥(private key)。
私钥只能由一方安全保管,不能外泄,而公钥则可以发给任何请求它的人。非对称加密使用这对密钥中的一个进行加密,
而解密则需要另一个密钥。目前最常用的非对称加密算法是RSA算法
~22、类加载器工作机制:
1.装载:将Java二进制代码导入jvm中,生成Class文件。
2.连接:a)校验:检查载入Class文件数据的正确性 b)准备:给类的静态变量分配存储空间 c)解析:将符号引用转成直接引用
3:初始化:对类的静态变量,静态方法和静态代码块执行初始化工作。
双亲委派模型:类加载器收到类加载请求,首先将请求委派给父类加载器完成,用户自定义加载器->应用程序加载器->扩展类加载器->
启动类加载器prv:顾名思义-类加载器就是加载类的,自己想想都知道要先加载二进制文件进内存,校验并分配内存空间指向内存地址,
初始化静态东西。
~23、lambda
一个Lambda表达式具有下面这样的语法特征。它由三个部分组成:第一部分为一个括号内用逗号分隔的形参,参数即函数式接口里面方法的参数;第二部分为一个箭头符号:->;第三部分为方法体,可以是表达式和代码块。语法如下:
parameter -> expression body
下面列举了Lambda表达式的几个最重要的特征:
可选的类型声明:你不用去声明参数的类型。编译器可以从参数的值来推断它是什么类型。
可选的参数周围的括号:你可以不用在括号内声明单个参数。但是对于很多参数的情况,括号是必需的。
可选的大括号:如果表达式体里面只有一个语句,那么你不必用大括号括起来。
可选的返回关键字:如果表达式体只有单个表达式用于值的返回,那么编译器会自动完成这一步。若要指示表达式来返回某个值,则需要使用大括号。
函数式接口的重要属性是:我们能够使用 Lambda 实例化它们,Lambda 表达式让你能够将函数作为方法参数,或者将代码作为数据对待。Lambda 表达式的引入给开发者带来了不少优点:在 Java 8 之前,匿名内部类,监听器和事件处理器的使用都显得很冗长,代码可读性很差,Lambda 表达式的应用则使代码变得更加紧凑,可读性增强;Lambda 表达式使并行操作大集合变得很方便,可以充分发挥多核 CPU 的优势,更易于为多核处理器编写代码。
Stream语法 转载链接:https://www.cnblogs.com/aoeiuv/p/5911692.html
两句话理解Stream:
1.Stream是元素的集合,这点让Stream看起来用些类似Iterator;
2.可以支持顺序和并行的对原Stream进行汇聚的操作;
大家可以把Stream当成一个装饰后的Iterator。原始版本的Iterator,用户只能逐个遍历元素并对其执行某些操作;包装后的Stream,用户只要给出需要对其包含的元素执行什么操作,比如“过滤掉长度大于10的字符串”、“获取每个字符串的首字母”等,具体这些操作如何应用到每个元素上,就给Stream就好了!原先是人告诉计算机一步一步怎么做,现在是告诉计算机做什么,计算机自己决定怎么做。当然这个“怎么做”还是比较弱的。
使用Stream的基本步骤:
1.创建Stream;
2.转换Stream,每次转换原有Stream对象不改变,返回一个新的Stream对象(可以有多次转换);
3.对Stream进行聚合(Reduce)操作,获取想要的结果
怎么得到Stream
最常用的创建Stream有两种途径:
1.通过Stream接口的静态工厂方法(注意:Java8里接口可以带静态方法);
2.通过Collection接口的默认方法(默认方法:Default method,也是Java8中的一个新特性,就是接口中的一个带有实现的方法)–stream(),把一个Collection对象转换成Stream
使用Stream静态方法来创建Stream
1. of方法:有两个overload方法,一个接受变长参数,一个接口单一值
Stream integerStream = Stream.of(1, 2, 3, 5);
Stream stringStream = Stream.of(“taobao”);
2. generator方法:生成一个无限长度的Stream,其元素的生成是通过给定的Supplier(这个接口可以看成一个对象的工厂,每次调用返回一个给定类型的对象)
Stream.generate(new Supplier() {
@Override
public Double get() {
return Math.random();
}
});
Stream.generate(() -> Math.random());
Stream.generate(Math::random);
三条语句的作用都是一样的,只是使用了lambda表达式和方法引用的语法来简化代码。每条语句其实都是生成一个无限长度的Stream,其中值是随机的。这个无限长度Stream是懒加载,一般这种无限长度的Stream都会配合Stream的limit()方法来用。
3. iterate方法:也是生成无限长度的Stream,和generator不同的是,其元素的生成是重复对给定的种子值(seed)调用用户指定函数来生成的。其中包含的元素可以认为是:seed,f(seed),f(f(seed))无限循环
Stream.iterate(1, item -> item + 1).limit(10).forEach(System.out::println);
这段代码就是先获取一个无限长度的正整数集合的Stream,然后取出前10个打印。千万记住使用limit方法,不然会无限打印下去。
几个常用的转换方法来解释。
1. distinct: 对于Stream中包含的元素进行去重操作(去重逻辑依赖元素的equals方法),新生成的Stream中没有重复的元素;
2. filter: 对于Stream中包含的元素使用给定的过滤函数进行过滤操作,新生成的Stream只包含符合条件的元素;
3. map: 对于Stream中包含的元素使用给定的转换函数进行转换操作,新生成的Stream只包含转换生成的元素。这个方法有三个对于原始类型的变种方法,分别是:mapToInt,mapToLong和mapToDouble。这三个方法也比较好理解,比如mapToInt就是把原始Stream转换成一个新的Stream,这个新生成的Stream中的元素都是int类型。之所以会有这样三个变种方法,可以免除自动装箱/拆箱的额外消耗;
4. flatMap:和map类似,不同的是其每个元素转换得到的是Stream对象,会把子Stream中的元素压缩到父集合中;
Stream<List> inputStream = Stream.of(
Arrays.asList(1),
Arrays.asList(2, 3),
Arrays.asList(4, 5, 6)
);
Stream outputStream = inputStream.
flatMap((childList) -> childList.stream());
flatMap 把 input Stream 中的层级结构扁平化,就是将最底层元素抽出来放到一起,最终 output 的新 Stream 里面已经没有 List 了,都是直接的数字。
5. peek: 生成一个包含原Stream的所有元素的新Stream,同时会提供一个消费函数(Consumer实例),新Stream每个元素被消费的时候都会执行给定的消费函数;
6. limit: 对一个Stream进行截断操作,获取其前N个元素,如果原Stream中包含的元素个数小于N,那就获取其所有的元素;
7. skip: 返回一个丢弃原Stream的前N个元素后剩下元素组成的新Stream,如果原Stream中包含的元素个数小于N,那么返回空Stream;
转载链接:https://blog.csdn.net/qq_14853889/article/details/79777870
需求1 :打印所有客户的名字
mans.forEach( man->{ System.out.println(man.getName()); });
需求2 : 获得所有客户名字的 list 集合
List collect = mans.stream().map(man -> man.getName()).collect(Collectors.toList());
需求3 : 获取名字是张三的客户:
List collect = mans.stream().filter(m -> “张三”.equals(m.getName())).collect(Collectors.toList());
需求4: 返回客户id 是007的 对象:
Man man = mans.stream().filter(m -> “007”.equals(m.getId()))
.findFirst().orElse(null);
需求5: 获得名字为 “张三” 客户的 银行卡 cards 集合
List cardList = mans.stream().filter(m -> “张三”.equals(m.getName()))
.flatMap(m -> m.getCards().stream())
.collect(Collectors.toList());
~ 24、Thread的start()、run()方法
start用来启用一个线程,当调用start方法后,系统才会开启一个新的线程,进而调用run方法来执行任务,而单独调用run就跟调用
普通方法是一样的,已经失去了线程的特性了。因此在启动一个线程的时候一定要使用start而不是run。
调用start方法,使线程所代表的虚拟处理机处于可执行的状态,这意味着它可以由JVM调度并执行,但并不意味着线程会立即执行。
run方法可以产生必须退出的标志来停止一个线程。
public class ThreadFlag extends Thread
{
public volatile boolean exit = false;
public void run()
{
while (!exit);
}
public static void main(String[] args) throws Exception
{
ThreadFlag thread = new ThreadFlag();
thread.start();
sleep(5000); // 主线程延迟5秒
thread.exit = true; // 终止线程thread
thread.join();
System.out.println(“线程退出!”);
}
}
在上面代码中定义了一个退出标志exit,当exit为true时,while循环退出,exit的默认值为false.在定义exit时,使用了一个Java
关键字volatile,这个关键字的目的是使exit同步,也就是说在同一时刻只能由一个线程来修改exit的值,
使用stop方法强行终止线程(这个方法不推荐使用,因为stop和suspend、resume一样,也可能发生不可预料的结果)。
3. 使用interrupt方法中断线程。
~25、volatile&synchronized
volatile用于修饰变量,是直接读写主存
volatile虽然具有可见性但是并不能保证原子性(用于单线程中的while循环)
prv:volatile 将变量值在线程内存跟主存中切换,更新线程内存到主存,从主存同步变量值到线程内存。比较的时候会判断预期值跟主存值
是否一致,一致就将目标值更新到主存,然而比较期望值主存值以及更新期望值到主存这几个步骤都没有锁在一起,所以说不能保证原子性。
synchronized可作用于一段代码或方法,既可以保证可见性,又能够保证原子性 (用于多线程且需保证原子性)
~26、接口继承多个接口,抽象类实现多个接口