Java基础知识点总结2:https://developer.aliyun.com/article/1473660
本数据类型和包装类型的区别
- 包装类是对象,拥有方法和字段,对象的调用都是通过引用对象的地址;基本类型不是
- 包装类型是引用的传递;基本类型是值的传递
声明方式不同:
基本数据类型不需要new关键字;
包装类型需要new在堆内存中进行new来分配内存空间
存储位置不同:
基本数据类型直接将值保存在值栈中;
包装类型是把对象放在堆中,然后通过对象的引用来调用他们
初始值不同:
int的初始值为0 、 boolean的初始值为false
包装类型的初始值为null
使用方式不同:
基本数据类型直接赋值使用就好;
包装类型是在集合中使用,比如collection和Map
Integer是否可以被继承?为什么
不能。因为是用final修饰的,Integer 继承 Number 抽象类,实现了 Comparable 接口。
Number 类是常用数字类型类的公共父类,它规定了其子类(通常就是数字类)必须提供将其值转换成 int、long、float、double、byte、short 类型数据的能力。实现 Comparable 接口自然是为了比较大小。
另外,Integer 类型也是最终类,不可被继承(事实上,常用数据类型的封装类都是 final 类)。
如何解决Hash冲突问题
- 开放地址法(线性探测再散列,二次探测再散列,伪随机探测再散列)
- 再哈希法
- 拉链法(Java hashmap就是这么做的)
- 建立一个公共溢出区
静态变量和实际变量
- 静态变量前要加static关键字,而实例变量前则不加
- 实例变量属于某个对象的属性,必须创建了实例对象,其中的实例变量才会被分配空间,才能使用这个实例变量。
静态变量不属于某个实例对象,而是属于类,所以也称为类变量,只要程序加载了类的字节码,不用创建任何实例对象,静态变量就会被分配空间,静态变量就可以被使用了。
实例变量必须创建对象后才可以通过这个对象来使用,静态变量则可以直接使用类名来引用。
用静态关键字和非静态关键字的区别
关于static变量和非static变量的区别
关于static和非static变量的区别
static 修饰的变量称为类变量或全局变量或成员变量,在类被加载的时候成员变量即被初始化,与类关联,只要类存在,static变量就存在。非static修饰的成员变量是在对象new出来的时候划分存储空间,是与具体的对象绑定的,该成员变量仅为当前对象所拥有的。
static修饰的变量在加载的时候先于main方法加载在内存中的数据共享区-------方法区,而非static的变量在加载的时候,是要创建变量才加载在堆内存中的。
一个static变量单独划分一块存储空间,不与具体的对象绑定在一起,该存储空间被类的各个对象所共享。static变量值在方法区加载一次,而非static在创建对象时会加载很多次。每次创建都会拷贝一份。
对象在引用成员变量是直接通过类名.变量名调用,对象在引用实例变量时只能通过对象名.变量名调用。
在类中调用成员变量时直接调用或者以类名.变量名方式调用,实例变量则用this或者直接调用。
关于static方法和非static方法的区别
关于static方法和非static方法的区别
static修饰的方法也和static一样。先于main方法被加载到方法区,以便共享使用。
静态的static方法中不能使用this或者super关键字,因为static方法是先于对象创建之前就已经加载的方法,是属于类的方法,而this和super指向的是本类的对象或者父类的对象,非静态的方法是属于对象的,方法里可以用this和super。
static方法可以用对象.方法名来调用,也可以用类名.方法名来调用。而非静态的方法只能创建对象后时调用。
static方法是加载一次,被所有的对象所共享。而非静态方法是有多少个对象就拷贝多少次,每个对象只能调用自己的拷贝的方法。
对象调用非静态的方法时,不考虑线程安全性的问题,而调用静态方法时,要考虑安全性的问题。因为静态方法只有一份。而对象的方法是自己有自己的。
同一个类中,静态方法中只能访问类中的静态成员。而非静态方法可以访问非静态的方法(使用类名调用,或者创创建本类的对象调用)
参数传递
基本数据类型和引用类型的区别
int num = 10;String str = “hello”;
对于基本类型的数据变量,他的值是直接存储在变量中
str是一个引用类型的变量,变量中保存的是我们实际的对象在堆内存中的地址,而我们真实的对象,其实是存储在堆空间中的
==的作用
num=20str=“java”
对于基本类型 num ,赋值运算符会直接改变变量的值,原来的值被覆盖掉。
对于引用类型str,赋值运算符会改变引用中所保存的地址,原来的地址被覆盖掉。但是原来的对象不会被改变(重要)
如上图所示,“hello” 字符串对象没有被改变。(没有被任何引用所指向的对象是垃圾,会被垃圾回收器回收)
值传递和引用传递的区别
- 值传递(pass by value)指的是在把实参传递给形参的时候,将实际的参数复制一份传递给函数的形参,也就是说,在函数内部对形参的修改,不会改变原来实参的值。
- 引用传递(pass by reference)指的是在实际的函数调用时,将实际参数的地址传递给函数的形式参数,也就是说其实形参和实参保存的是堆中同一个对象的地址。如果在函数中对堆中的对象属性进行修改,实参对应的此对象的属性也会改变。
传参是值传递还是引用传递
- 基本类型作为参数传递时,是传递值的拷贝,无论你怎么改变这个拷贝,原值是不会改变的
- 对象作为参数传递时,是把对象在内存中的地址拷贝了一份传给了参数。
IO流
字符流和字节流
字节流继承inputStream和OutputStream
字符流继承自InputSteamReader和OutputStreamWriter
数据source:就是需要读取,可以使用两个体系:InputStream、Reader;
数据destination:就是需要写入,可以使用两个体系:OutputStream、Writer;
多线程
创建线程的4种方式
- 继承Thread类
- 实现Rannable接口
- 实现Callable接口
- 线程池中获取
锁
- 悲观锁:每次操作都加锁,其他线程阻塞。synchronized、ReentrantLock
- 乐观锁:操作数据时不会上锁,在更新时会判断此期间有没有线程去更新这个数据。版本号机制和CAS算法实现。
互斥锁:某一资源同时只允许一个访问者进行访问,具有唯一性和排它性。
读写锁:读写锁管理一组锁,一个是只读锁,一个是写锁。每次只有一个写线程,但是同时有多个线程并发读。
公平锁:先到先得,lock new ReentrantLock(true)
非公平锁:加锁时不考虑排队等待问题,直接尝试获取锁,获取不到自动到队尾等待;性能比公平锁高。synchronized,lock new ReentrantLock(false);
自旋锁:线程没有获取到锁时不被直接挂起,而是执行一个盲循环,这个忙循环就是所谓的自旋锁。为了减少线程被挂起的几率,因为线程的挂起和唤醒也是消耗资源的操作。
分段锁:是一种锁的设计,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。
JDK1.6,引入4种锁的状态:无锁,偏向锁,轻量级锁,重量级锁
无锁就是乐观锁。
偏向锁:偏向于第一个访问锁的线程。不存在多线程操作,不需要重复获取锁。
轻量级锁:线程竞争激烈,偏向锁就会升级为轻量级锁,通过自旋方式等待上一个线程释放锁。CAS
重量级锁:线程并发进一步加剧,线程的自旋超过一定次数,或者一个线程持有锁,一个线程在自旋,又来了第三个线程访问时,轻会变重,除了此时拥有锁的线程以外,其他线程都阻塞。
Java集合详解(List、Map、Set)
为什么要使用线程池
线程和数据库连接这些资源都是非常宝贵的资源。那么每次需要的时候创建,不需要的时候销毁,是非常浪费资源的。那么我们就可以使用缓存的策略,也就是使用线程池。
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控
如何解决线程安全问题
加锁
线程的生命周期
- New
- 尚未启动的线程的线程状态
Runnable
可运行线程的线程状态,等待CPU调度
Blocked
线程阻塞等待监视器锁定的线程状态(处于synchronized同步代码块或方法中被阻塞)
Waiting
等待线程的线程状态
(不带超时的方式:Object.wait、Thread.join、LockSupport.park)
Timed Waiting
具有指定等待时间的等待线程的线程状态
(带超时的方式:Thread.sleep、Object.wait、Thread.join、LockSupport.parkNanos、LockSupport.parkUnitl)
Terminated
终止线程的线程状态。(线程正常完成执行或出现异常)
Java中的设计模式
- 单例(Singleton)模式:某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。
- 原型(Prototype)模式:将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。
工厂方法(Factory Method)模式:定义一个用于创建产品的接口,由子类决定生产什么产品。
抽象工厂(AbstractFactory)模式:提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。
建造者(Builder)模式:将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。
代理(Proxy)模式:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。
适配器(Adapter)模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
桥接(Bridge)模式:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
装饰(Decorator)模式:动态的给对象增加一些职责,即增加其额外的功能。
外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用。
组合(Composite)模式:将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。
模板方法(TemplateMethod)模式:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
策略(Strategy)模式:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。
命令(Command)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。
职责链(Chain of Responsibility)模式:把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。
状态(State)模式:允许一个对象在其内部状态发生改变时改变其行为能力。
观察者(Observer)模式:多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。
中介者(Mediator)模式:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。
迭代器(Iterator)模式:提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
访问者(Visitor)模式:在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。
备忘录(Memento)模式:在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。
解释器(Interpreter)模式:提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。
Java中常用的设计模式及应用场景
- 单例设计模式:如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案 —枚举
工厂设计模式:工厂模式主要是为创建对象提供了接口。—beanfactory
策略模式:定义了算法族,分别封装起来,让它们之间可以互相替换。此模式让算法的变化独立于使用算法的客户。— 一件事情,有很多方案可以实现。
观察者模式:观察者模式又被称作发布/订阅模式,定义了对象间一对多依赖,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
迭代器模式:迭代器模式提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。
模版方法模式:模板方法模式定义一个操作中的算法的骨架,将一些步骤延迟到子类中,模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些步骤。
同步和异步的区别及用途
举个例子:普通B/S模式(同步)AJAX技术(异步)
同步:提交请求 -> 等待服务器处理 -> 处理完毕返回 这个期间客户端浏览器不能干任何事
异步:请求通过事件触发 -> 服务器处理(这是浏览器仍然可以作其他事情) -> 处理完毕
同步就是你叫我去吃饭,我听到了就和你去吃饭;如果没有听到,你就不停的叫,直到我告诉你听到了,才一起去吃饭。
异步就是你叫我,然后自己去吃饭,我得到消息后可能立即走,也可能等到下班才去吃饭。
同步
优点:同步是按照顺序一个一个来,不会乱掉,更不会出现上面代码没有执行完就执行下面的代码
缺点:是解析的速度没有异步的快;
异步
优点:异步是接取一个任务,直接给后台,在接下一个任务,一直一直这样,谁的先读取完先执行谁的
缺点:没有顺序 ,谁先读取完先执行谁的 ,会出现上面的代码还没出来下面的就已经出来了,会报C错
排序算法
char 型变量中能不能存储一个中文汉字?为什么
char型变量是用来存储Unicode编码的字符的,unicode编码字符集中包含了汉字,所以,char型变量中当然可以存储汉字啦。不过,如果某个特殊的汉字没有被包含在unicode编码字符集中,那么,这个char型变量中就不能存储这个特殊汉字。
补充说明:unicode编码占用两个字节,所以,char类型的变量也是占用两个字节。
怎么判断对象是否可以被回收
- 引用计数器法为每个对象创建一个引用计数,有对象引用时计数器 +1,引用被释放时计数 -1,当计数器为 0 时就可以被回收。它有一个缺点不能解决循环引用的问题;
可达性分析算法:从 GC Roots作为起点,引用链作为路径。当一个对象到 GC Roots没有任何引用链相连时,则证明此对象是可以被回收的。
说说垃圾回收算法的底层原理?(新生代、老年代)
标记清除首先标记出所有需要回收的对象,在标记完成后统一回收掉被标记的对象 (老年代)
- 优点:实现简单,不需要对象进行移动
- 缺点:第一个是执行效率不稳定,第二个是内存空间的碎片化问题
- 标记复制
将可用内存按容量划分为大小相等的两块,每次使用其中的一块,这一块用完了就将还存活者的对象复制到另一块上面 (新生代)
- 优点:按顺序分配内存即可,实现简单、运行高效,不用考虑内存碎片
- 缺点:内存缩小到原来的一半.
(eden区和幸存区大小比例是8:1)- 标记整理
让所有存活的对象都移向内存空间的一端,然后直接清理掉边界以外的内存 (老年代)
- 优点:解决了标记-清理算法存在的内存碎片问题
- 缺点:仍需要进行局部对象移动,一定程度上降低了效率
垃圾回收器的作用
释放和重用资源是垃圾回收算法的具体实现
Serial收集器(复制算法):新生代单线程收集器,标记和清理都是单线程,优点是简单高效;
ParNew收集器 (复制算法):新生代收并行集器,实际上是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现;
Parallel Scavenge收集器 (复制算法):新生代并行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 用户线程时间/(用户线程时间+GC线程时间),高吞吐量可以高效率的利用CPU时间,尽快完成程序的运算任务,适合后台应用等对交互相应要求不高的场景;
Serial Old收集器 (标记-整理算法):老年代单线程收集器,Serial收集器的老年代版本;
Parallel Old收集器 (标记-整理算法):老年代并行收集器,吞吐量优先,Parallel Scavenge收集器的老年代版本;
CMS(Concurrent Mark Sweep)收集器(标记-清除算法):老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最短GC回收停顿时间
G1(Garbage First)收集器 (标记-整理算法): Java堆并行收集器,G1收集器是JDK1.7提供的一个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会产生内存碎片
Servlet的执行流程
- 页面提交请求到servlet
- 容器创建请求和响应对象
容器根据URL查找具体的servlet
容器加载并实例化servlet
执行servlet的初始化方法(init())
容器创建一个新的线程处理该请求
容器调用servlet的service(服务方法)方法(同时将请求和响应对象作为参数)
servlet调用模型的业务逻辑进行处理
servlet将处理结果保存到指定对象中(request、session、application)
servlet将请求转发给相应的jsp(处理完成后,会调用destroy()方法(销毁方法))
jsp动态生成响应(使用响应对象向流写入html或其他内容)
容器将响应(通过web服务器)返回给客户端