Java中数组(Array)和列表(ArrayList)的区别
- 可以将 ArrayList想象成“会自动扩增容量的Array”
- Array最高效,但容量固定且无法动态改变;
- ArrayList:默认容量为10,容量可动态增长;但牺牲效率;
- 使用建议:基于效率和类型检验,应尽可能使用Array,无法确定数组大小时使用ArrayList
面向对象的五大基本原则
- 单一职责原则SRP(Single Responsibility Principle-spr)
- 是指一个类的功能要单一,不能包罗万象
- 开放封闭原则OCP(Open-Close Principle)
- 一个模块在扩展性方面应该是开放的而在更改性方面应该是封闭的
- 里式替换原则LSP(the Liskov Substitution Principle)
- 子类应当可以替换父类并出现在父类能够出现的任何地方
- 依赖倒置原则DIP(the Dependency Inversion Principle)
- 具体依赖抽象,上层依赖下层。
- 接口分离原则ISP(the Interface Segregation Principle)
- 模块间要通过抽象接口隔离开,而不是通过具体的类强耦合起来
java为什么是跨平台的
java代码编译之后 生成.class的字节码,字节码不能被机器直接识别。java为不同的硬件平台提供了JVM,由具体的JVM将字节码文件翻译成平台能够执行的代码。
java位运算符
- & 与运算:两个都为1,结果为1,否则全为0
- | 或运算:只要1个为1就为1,否则为0
- ^异或运算:两个相同为0,不同为1
ArrayList和LinkedList哪个占用空间大
- 一般情况下,LinkedList的占用空间更大,因为LinkedList使用双向链表每个节点要维护指向前后地址的两个节点,所以占用空间多一些;
- 但也不是绝对,如果刚好数据量超过ArrayList默认的临界值,ArrayList会进行1.5倍的扩容,浪费将近原来数组一半的容量,因此空间占用也是不小的。
- 不过,因为ArrayList的数组变量是用transient关键字修饰,如果ArrayList做序列化操作,ArrayList这部分多余的空间不会被序列化。
ArrayList多线程访问带来的问题以及解决措施
ArrayList 线程安全问题blog.csdn.netInteger缓存
- java为Integer类型做了缓存,如果值在[-128,127]之间,则会从缓存中取一个对象赋值给当前引用,如果超过这个范围会新new一个对象。
String的常量池
- String s="xx";时查看常量池有没有xx这个值对应的对象,如果有将引用赋值给s,如果没有则新建一个对象
- String s = new String("xx");直接在堆内存中new一个对象
Java 到底是值传递还是引用传递?
- 调用方法时的参数传递,基本上就是赋值(=)操作。
- 基本数据类型变量名称存的就是值;引用数据类型,变量名称存的是值对应的对象在内存中的地址。
- 基本数据类型,赋值操作(=)直接修改变量名称中存的值;引用数据类型,赋值操作(=)是将对象的地址值赋值给另一个变量
为什么重写equals还要重写hashcode?
- Object 的 hashcode 方法是本地方法用 c 或 c++ 实现的,该方法直接返回对象的内存地址。如果没有重写hashCode(),则具有相同内容的两个对象因为内存地址不同,也不会判断为相等。(而hashmap想让部分值的hashCode值一样,所以就要重写)
- 如果两个对象相同(即用equals比较返回true),那么要保证这两个对象的hashCode一定要相等;如果两个对象的hashCode相同,它们并不一定相同因为还要判断对象中的内容是否相等。
==和equals
- 在引用类型中,"=="比较两个引用是否指向堆内存里的同一个地址(同一个对象),而equals是一个普通的方法,该方法返回的结果依赖于自身的实现。
- String重写了equals()方法
java创建对象的方式
- new关键字创建对象
- 利用反射创建对象
- 利用类对象的newInstance()方法创建对象,但只能使用无参构造
- 利用类对象获得指定的Construct对象,在利用Construct对象的newInstance()方法创建对象,可以指定构造方法
- 调用clone()方法;实现Cloneable接口并实现其定义的clone方法。jvm就会创建一个新的对象,将前面对象的内容全部拷贝进去。用clone方法创建对象并不会调用任何构造函数。
- 利用反序列化;当我们序列化和反序列化一个对象,jvm会给我们创建一个单独的对象。在反序列化时,jvm创建对象并不会调用任何构造函数。
java异常分类
- Error
- 是程序中无法处理的错误,表示运行应用程序中出现了严重的错误。此类错误一般表示代码运行时JVM出现问题。通常有Virtual MachineError(虚拟机运行错误)、NoClassDefFoundError(类定义错误)等。比如说当jvm耗完可用内存时,将出现OutOfMemoryError。此类错误发生时,JVM将终止线程。非代码性错误。因此,当此类错误发生时,应用不应该去处理此类错误。
- Exception
- 运行时异常(不受检异常):RuntimeException类表示JVM在运行期间可能出现的错误。编译器不会检查此类异常,并且不要求处理异常,比如空指针,数组下标越界。此类异常属于不可查异常,一般是由程序逻辑错误引起的,在程序中可以选择捕获处理,也可以不处理。
- 非运行时异常(受检异常):编译器会检查此类异常,如果程序中出现此类异常,比如说IOException,必须对该异常进行处理,要么使用try-catch捕获,要么使用throws语句抛出,否则编译不通过。
介绍一下map的分类和常见的情况
- Map有四个实现类,分别是HashMap Hashtable LinkedHashMap 和TreeMap.
- Map主要用于存储健值对,根据键得到值,因此不允许键重复(重复了覆盖了)
- Hashmap 根据键的HashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度,遍历时,取得数据的顺序是完全随机的。HashMap最多允许一个null键(因为键不能重复嘛),可以有多个null值;HashMap不支持线程的同步,即任一时刻可以有多个线程同时写HashMap;可能会导致数据的不一致。
- HashMap使用数组长度n和键的hashCode值求逻辑与计算键存放的位置,即:i=(n-1)&hashcode(key),不是通过取余的方式,其中键的hashCode值计算方式被改成了hash=key.hashCode() ^(key.hashCode()>>>16)也就是实际上的hash值将hashCode低16位与高16位做了异或运算,相当于混合了高位和低位,增加了随机性。如果不这样的话,当n很小时(n-1)&hash的与运算只用到了hash值的低位,当不同的hash值低位相同时,高位不同的时候也会发生冲突,是不太好的。
- (n-1)&hash值范围介于0-min(n-1,hash)之间:将做与运算的两个数都转成二进制,& 的效果是当两个二进制数对应位的数都为 1 时,结果才为 1,否则为 0。当 hash 的值最大,即全为 1 的时候,(n-1)&hash 才等于 min(n-1,hash)。
- Hashtable与 HashMap类似不同的是:键和值都不能为空;它支持线程的同步,即任一时刻只有一个线程能写Hashtable,因此也导致了 Hashtable在写入时会比较慢。
- LinkedHashMap 是HashMap的一个子类,保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的.也可以在构造时用带参数,按照应用次数排序。在遍历的时候会比HashMap慢,不过有种情况例外,当HashMap容量很大,实际数据较少时,遍历起来可能会比 LinkedHashMap慢,因为LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而HashMap的遍历速度和他的容量有关。
- TreeMap实现SortMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator 遍历TreeMap时,得到的记录是排过序的。
- 用的最多的是HashMap,如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。如果需要输出的顺序和输入的相同,那么用LinkedHashMap 可以实现,它还可以按读取顺序来排列
static关键字
- 简而言之:方便在没有对象的时候调用方法或成员变量
- static不能修饰类,但可以修饰内部类,这样就可以不创建内部类对象直接调用内部类的方法
- static修饰的成员变量在类加载时被初始化,放在方法区中,该类的所有对象共享这个变量
- static修饰的方法在类加载时调入内存,可以不创建对象就调用。
类初始化顺序
- 基类静态代码块和基类静态变量(并列优先级,按照代码先后顺序执行)
- 派生类静态代码块和派生类静态变量(并列优先级,按照代码先后顺序执行)
- 基类普通变量和基类普通代码块(并列优先级,按照代码先后顺序执行)
- 基类构造函数
- 派生类普通代码块和派生类普通变量(并列优先级,按照代码先后顺序执行)
- 派生类构造函数
final关键字
- final修饰的类不能被继承,类中的方法默认也是final,类中的成员变量可以根据需要确定是否用final修饰
- final修饰的方法不能被继承类修改
- final修饰的基本数据类型,初始化后不能更改;修饰的引用变量,初始化后地址不能更改,地址指向的对象内容可以更改
String StringBuffer 和 StringBuilder 的区别? String 为什么是不可变的?
- String类中使用final关键字修饰保存字符串的字符或字节数组,所以String是不可变的。
- StringBuilder和StringBuffer都继承自父类AbstractStringBuilder,而父类用来保存字符串的数组char[] value没有用final关键字修饰,所以是可变的
- 线程安全性分析
- String对象不可变,也就是常量,故线程安全
- StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,故线程安全
- StringBuilder没有对方法加同步锁,故线程不安全
- 性能分析
- StringBuilder>StringBuffer>String
重载和覆盖
- 重载发生在同一个类中指方法的方法名称可以相同但是参数类型或者数量顺序要不同;重载和返回值以及权限修饰符无关
- 方法覆盖体现在继承中,指子类重新定义了从父类中继承的方法。方法覆盖时方法名,参数列表和返回值类型必须相同,其中返回值类型可以是父类返回值的子类,权限修饰符要大于等于父类。
请判断,两个对象值相同(x.equals(y) == true),但却可有不同的hash code,该说法是否正确,为什么?
- 不对,如果两个对象x和y满足x.equals(y) == true,它们的哈希码(hash code)应当相同。Java对于eqauls方法和hashCode方法是这样规定的:(1)如果两个对象相同(equals方法返回true),那么它们的hashCode值一定要相同;(2)如果两个对象的hashCode相同,它们并不一定相同。当然,你未必要按照要求去做,但是如果你违背了上述原则就会发现在使用容器时,相同的对象可以出现在Set集合中,同时增加新元素的效率会大大下降(对于使用哈希存储的系统,如果哈希码频繁的冲突将会造成存取性能急剧下降)。
interface(接口)和abstract(抽象类的)异同
- 抽象类不能实例化
- 有抽象方法的类必须是抽象类,抽象类中可以有非抽象方法
- 子类必须全部实现父类的抽象方法,否则子类也要定义为抽象类
- 构造方法和静态方法不能用abstract修饰。
- 接口中的方法全部默认为public abstract类型
- 接口中的变量默认为public static final类型
- 接口不能有构造方法,但是抽象类可以有
- 接口不能有普通成员变量,但抽象类可以有
- 接口中的方法只能是public修饰,而抽象类则public protected都可以
- 接口中不能有静态方法,抽象类可以有
- 接口中的成员变量必须是public static修饰,而抽象类不是
静态方法内调用非静态成员为什么是非法的
- 由于静态方法调用时可以不创建对象,通过类名调用;而非静态成员变量都是对象创建时初始化的,依附于某一个特定的对象。也就是说非静态成员变量依附于特定的对象,而通过类名调用静态方法时对象是不存在的,非静态成员变量自然就不存在,因此是非法的。
无参构造什么都没干,要他有何用?
- java在执行子类构造方法前会先执行父类的构造方法,如果没有用super()指定哪个父类的构造方法,会调用无参构造方法,因此如果不写无参构造编译会报错。所以加了个啥事也不干的无参构造
StackOverflowError
- 每一个 JVM 线程都拥有一个私有的 JVM 线程栈,用于存放当前线程的 JVM 栈帧(包括被调用函数的参数、局部变量和返回地址等)。如果某个线程的线程栈空间被耗尽,没有足够资源分配给新创建的栈帧,就会抛出 java.lang.StackOverflowError 错误。
- VM 线程栈存储了方法的执行过程、基本数据类型、局部变量、对象指针和返回值等信息,这些都需要消耗内存。一旦线程栈的大小增长超过了允许的内存限制,就会抛出 java.lang.StackOverflowError 错误
- 引发 StackOverFlowError 的原因如下:
- 无限递归循环调用(最常见)。
- 执行了大量方法,导致线程栈空间耗尽。
- 方法内声明了海量的局部变量。
- 常见的解决方法如下:
- 修复引发无限递归调用的异常代码, 通过程序抛出的异常堆栈,找出不断重复的代码行
- 排查是否存在类之间的循环依赖。
- 排查是否存在在一个类中对当前类进行实例化,并作为该类的实例变量。
- 通过 JVM 启动参数 -Xss 增加线程栈内存空间;例如通过配置 -Xss2m 将线程栈空间调整为 2 mb。
成员变量和局部变量
- 成员变量属于类,局部变量定义在方法内部
- 局部变量不可以用public,private等访问修饰符修饰而成员变量可以
- 成员变量随着对象的消亡而消亡;局部变量随着方法的调用而自动消失
- 局部变量存放在栈中,成员变量存放在堆中。
- 局部变量不会自动赋值,而成员变量会自动以类型的默认值赋初值
final finally finalize
- final修饰的类不能被继承,类中所有成员方法隐式的定义为final方法
- final成员变量表示常量,必须要显示初始化(变量声明的时候初始化或者构造方法初始化),初始化后便不能发生变化
- final修饰引用类型时,初始化后不能再让其指向其他对象,但该引用所指向的对象的内容是可以发生变化的。
- finally是异常处理的一部分,只能用在try/catch语句中表示这段语句无论如何都会执行
- finalize()是在java.lang.Object里定义的,每一个对象都有这个方法。这个方法在gc启动,该对象被回收的时候被调用。特殊情况下,需要程序员实现finalize,当对象被回收的时候释放一些资源,比如:一个socket链接,在对象初始化时创建,整个生命周期内有效,那么就需要实现finalize,关闭这个链接。使用finalize还需要注意一个事,调用super.finalize();一个对象的finalize()方法只会被调用一次,而且finalize()被调用不意味着gc会立即回收该对象,所以有可能调用finalize()后,该对象又不需要被回收了,然后到了真正要被回收的时候,因为前面调用过一次,所以不会调用finalize(),产生问题。 所以,推荐不要使用finalize()方法,它跟析构函数不一样。
反射原理
- java类的执行需要经历如下过程
- 编译:java文件编译后生成.class字节码文件
- 加载:在需要时类加载器根据类的全限定名找到.class文件,读取类的二进制字节流到JVM内部,并存储在方法区,然后将其转换为一个与目标类型对应的java.lang.Class对象实例
- 连接:验证格式(class文件规范)语义(final类是否有子类)操作;准备:静态变量赋初值和内存空间;解析:符号引用转化为直接引用分配地址
- 初始化:有父类的先初始化父类,然后初始化自己;将static修饰代码执行一遍
- 反射就是利用第二步jvm中的.class文件来进行操作的;.class文件中包含java类的所有信息,不知道某个类的具体信息时,可以使用反射获取class,然后进行各种操作。
- java反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象都能够调用他的任意方法和属性,并且能改变他的属性;简单来说:反射就是把java类中的各个成分映射成一个个java对象,并且可以进行操作。
反射获取类对象class的三种方法
- 实例对象调用getClass()方法来获取,应用在参数传递的是一个对象,当不知道具体是什么类时Class c1 = 对象.getClass()
- 类名.class的方式得到,该方法最为安全可靠,程序性能高,并且说明任何一个类都有一个隐含的静态成员变量class,使用方法Class c1=Person.Class;
- Class.forName(全限定名),Class c1 = Class.forName("reflex.Person")
- 参考自
collection,collections
- collection是单列集合的接口,提供了对结合对象进行基本操作的方法;有子接口List和Set
- collections是针对集合操作的工具类,包含对集合进行排序和二分查找的静态方法方法。
什么是面向切面编程AOP
- AOP:在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。
- 面向切面编程(AOP是Aspect Oriented Program的缩写) ,面向对象的特点是继承、多态和封装。而封装就要求将功能分散到不同的对象中,这在软件设计中往往称为职责分配。也就是说,让不同的类设计不同的方法。这样代码就分散到一个个的类中去了。这样做的好处是降低了代码的复杂程度,使类可重用。 但是在分散代码的同时,也增加了代码的重复性。为什么呢?例如,在两个类中,可能都需要在每个方法中做日志。按面向对象的设计方法,必须在两个类的方法中都加入日志的内容。也许他们是完全相同的,但就是因为面向对象的设计让类与类之间无法联系,而不能将这些重复的代码统一起来。 也许有人会说,那好办啊,我们可以将这段代码写在一个独立的类独立的方法里,然后再在这两个类中调用。但是,这样一来,这两个类跟我们上面提到的独立的类就有耦合了,它的改变会影响这两个类。那么,有没有什么办法,能让我们在需要的时候,随意地加入代码呢?这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。 一般而言,我们管切入到指定类指定方法的代码片段称为切面,而切入到哪些类、哪些方法则叫切入点。有了AOP,我们就可以把几个类共有的代码,抽取到一个切片中,等到需要时再切入对象中去,从而改变其原有的行为。 这样看来,AOP其实只是OOP的补充而已。OOP从横向上区分出一个个的类来,而AOP则从纵向上向对象中加入特定的代码。有了AOP,OOP变得立体了。如果加上时间维度,AOP使OOP由原来的二维变为三维了,由平面变成立体了。从技术上来说,AOP基本上是通过代理机制实现的。 AOP在编程历史上可以说是里程碑式的,对OOP编程是一种十分有益的补充。
数据库读写分离
关于数据库读写分离_cyan_grey的博客-CSDN博客blog.csdn.net多线程
创建线程的方式
- 继承Thread类
- 覆写Runnable接口,实现run()方法
- 覆写Callable接口,实现call()方法,该方法有返回值
- 通过线程池启动多线程
线程池
- 线程池的优势
- 通过重用已经存在的线程,降低线程创建和销毁对系统资源的消耗
- 有任务到达时,复用已有的线程,无需等待线程的创建,提高系统的响应速度
- 方便线程并发量的管控。如果对创建线程数量不进行限制,会导致占用内存过多而产生的OOM,并且会造成CPU过度切换。CPU切换线程是有成本的,需要保持当前线程的现场,在执行时恢复其现场
- 提供更加强大的功能,延时定时线程池
- Java线程池详解
同步代码块中的加锁对象究竟是谁
抛出答案:多个线程使用的是同一个实例对象,是同一个实例对象。我们通过Thread类new一个新线程的时候,Thread类的构造方法需要一个Runnable接口的实现类对象作为参数,而若想达到多个线程共享资源的效果,构造方法的参数对象需要是同一个对象。其实共享资源就是多个线程使用【同一个】对象中的资源,这个对象就是synchronize代码块的【锁对象】。
Thread类定义
在使用同步代码块时必须指定一个需要同步的对象,但一般都将当前对象(this)设置成同步对象
//同步代码块
run()方法和start()方法的区别
- start()方法用来启动一个线程,使线程所代表的虚拟处理机处于可运行状态。
- 只有调用了start()方法,才是真正的多线程,不同线程run()方法里面的代码交替执行。run()方法是由jvm直接调用的。
- 如果没有调用start(),直接调用run()方法,那么代码还是同步执行的,即:一个线程的run方法执行完毕才执行另一个线程的run方法,是顺序调用的不是多线程机制。
Runnable接口和Callable接口的区别
- Runnable接口中的run()方法返回值是void,它做的事情只是纯粹地去执行run()方法中的代码;Callable接口中的call()方法有返回值,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。
- 这是一个很有用的特性,多线程相比单线程更难、更复杂的一个重要原因就是因为多线程充满着未知性,例如:某条线程是否执行了?执行了多久?执行的时候期望的数据是否已经赋值完毕?如果没有返回值我们无法得知,能做的只是等待这条多线程的任务执行完毕。而Callable+Future/FutureTask却可以获取多线程运行的结果,可以在等待时间太长没获取到需要的数据的情况下取消该线程的任务,真的是非常有用。
单例模式
- 懒汉式单例:私有构造方法+静态方法实现;线程不安全的,可以通过synchronized或者静态内部类改造成线程安全的
- 饿汉式单例:私有构造方法+静态final修饰的成员变量实现;线程安全
23种设计模式
23种设计模式汇总整理_一个本科小生的奋斗史-CSDN博客blog.csdn.netMySQL索引底层采用B+树的优点:
- 不存在哈希表的数据碰撞问题,解决了哈希表不支持的范围查找,数据排序问题
- 在极端情况下避免了二叉查找树退化为线性表的问题,例如ID的自增
- 改善了平衡二叉树一个节点只能存储一个数据,一次磁盘IO尽可能多的读取数据,减少了磁盘IO
- 优秀检索速度,时间复杂度:B 树的查找性能等于 O(h*logn),其中 h 为树高,n 为每个节点关键词的个数;
- 每个节点存储多个索引,尽可能的减少了磁盘 IO,加快了检索速度;
- 叶子结点存储数据并且使用链表连接起来是有序的,可以支持范围查找。
反射中,Class.forName 和 ClassLoader 区别。
ClassLoader.loadClass()与Class.forName()的区别 - 张国平的个人空间 - OSCHINA - 中文开源技术交流社区my.oschina.netClass.forName(className)方法,调用的方法是Class.forName(className,true,classloader);注意看第2个boolean参数,它表示,在loadClass后必须初始化。比较下我们前面准备jvm加载类的知识,我们可以清晰的看到在执行过此方法后,目标对象的 static块代码已经被执行,static参数也已经被初始化。
再看ClassLoader.loadClass(className)方法,其实他调用的方法是ClassLoader.loadClass(className,false);还是注意看第2个 boolean参数,该参数表示目标对象被装载后不进行链接,这就意味这不会去执行该类静态块中间的内容。因此2者的区别就显而易见了。
两种动态代理jdk和cjlib的区别
AOP的拦截功能是由java中的动态代理来实现的。说白了,就是在目标类的基础上增加切面逻辑,生成增强的目标类
jdk动态代理是由java内部的反射机制来实现的,cglib动态代理底层则是借助asm来实现的。
总的来说,反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)
dk动态代理是jdk原生就支持的一种代理方式,它的实现原理,就是通过让target类和代理类实现同一接口,代理类持有target对象,来达到方法拦截的作用。缺点:必须保证target类实现了一个接口并且被拦截的方法在接口中要有声明