一、switch表达式支持的数据类型
(1)基本类型:
byte、short、char、int
(2)基本类型对应的包装类型
byte、short、character、integer
(3)枚举类型 Eumn
(4)JDK7之后的String类型
二、按值传递和按引用传递
(1)按值传递:指的是在方法调用时,传递的参数是按值的拷贝传递。
按值传递重要特点:传递的是值的拷贝,也就是说传递后就互不相关了。
public class Test {
private void test1(int a){
a = 5;
System.out.println("test1方法中的a="+a);
}
public static void main(String[] args) {
Test t = new Test ();
int a = 3;
t.test1(a);//传递后,test1方法对变量值的改变不影响这里的a
System.out.println(”main方法中的a=”+a);
}
运行结果如下:
test1方法中的a=5
main方法中的a=3
(2)按引用传递:指的是在方法调用时,传递的参数是按引用进行传递,其实传递的引用的地址,也就是变量所对应的内存空间的地址。
按引用传递的重要特点:传递的是值的引用,也就是说传递前和传递后都指向同一个引用(也就是同一个内存空间)。
public class Test {
private void test1(A a){
a.age = 20;
System.out.println("test1方法中的age="+a.age);
}
public static void main(String[] args) {
Test t = new Test ();
A a = new A();//此时的a是A的一个实例,值为age值为0
a.age = 10;//此时age置为10
t.test1(a);//变量a所引用的内存空间地址,按引用传递给test1方法中的a变量,此时main方法的变量a和test1方法的变量a指向同一个空间
System.out.println(”main方法中的age=”+a.age);
}
}
class A{
public int age = 0;
}
运行结果如下:
test1方法中的age=20
main方法中的age=20
注:“在Java里面参数传递都是按值传递”这句话的意思是:按值传递是传递的值的拷贝,按引用传递其实传递的是引用的地址值,所以统称按值传递。
注:在Java里面只有基本类型和按照下面这种定义方式的String是按值传递,其它的都是按引用传递。就是直接使用双引号定义字符串方式:String str = “hundsun-刘鹏”;
三、Java的静态变量,成员变量,静态代码块,构造块的加载顺序
(1)如果类还没有被加载:
1、先执行父类的静态代码块和静态变量初始化,并且静态代码块和静态变量的执行顺序只跟代码中出现的顺序有关。
2、执行子类的静态代码块和静态变量初始化。
3、执行父类的实例变量初始化(例如:int a;初始化就是0,引用类型就是null)
4、执行父类的构造代码块
5、执行父类的构造函数
6、执行子类的实例变量初始化(例如:int a;初始化就是0,引用类型就是null)
7、执行子类的构造代码块
8、执行子类的构造函数
(2)如果类已经被加载:
则静态代码块和静态变量就不用重复执行(静态修饰的成员只在类加载时加载一次,被所有实例对象共享),再创建类对象时,只执行与实例相关的变量初始化和构造方法。
注意:父类静态代码(静态代码块和静态变量)>子类静态代码>父类实例变量>父类构造代码块>父类构造函数>子类实例变量>子类构造代码块>子类构造函数
四、Integer和int
(1)Integer:Integer是int的包装类;Integer变量必须实例化后才能使用,而int变量不需要;Integer实际是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象;而int则是直接存储数据值;默认值为Null;
(2)int:int则是java的一种基本数据类型;默认值为0;
注:Integer变量和int变量比较时,只要两个变量的值是向等的,则结果为true(因为包装类Integer和基本数据类型int比较时,Integer会自动拆箱为int,然后进行比较,实际上就变为两个int变量的比较)
Integer i = new Integer(100);
int j = 100;
System.out.print(i == j); //true
注:非new生成的Integer变量和new Integer()生成的变量比较时,结果为false。(因为非new生成的Integer变量指向的是java常量池中的对象,而new Integer()生成的变量指向堆中新建的对象,两者在内存中的地址不同)
Integer i = new Integer(100);//引用对象
Integer j = 100;//常量池中对象
System.out.print(i == j); //false
注:对于两个非new生成的Integer对象,进行比较时,如果两个变量的值在区间-128到127之间,则比较结果为true,如果两个变量的值不在此区间,则比较结果为false
Integer i = 100;
Integer j = 100;
System.out.print(i == j); //true
-------------------------------------------------------------------------------
Integer i = 128;
Integer j = 128;
System.out.print(i == j); //false
java在编译Integer i = 100 ;时,会翻译成为Integer i = Integer.valueOf(100),而valueOf方法在API中定义如下:
public static Integer valueOf(int i){
assert IntegerCache.high >= 127;//缓存数据[-128~127],在此区间的Integer 类型数据在常量池中
if (i >= IntegerCache.low && i <= IntegerCache.high){
return IntegerCache.cache[i + (-IntegerCache.low)];
}
return new Integer(i);
}
基本数据类型存放位置:方法参数、局部变量存放在栈内存中的栈桢中的局部变量表;常量存放在常量池中
Integer存放位置:常量池(Integer 未实例化声明的[-128~127]区间的数据)、堆内存(实例化对象时)
五、JAVA设计模式六大原则
(1)开闭原则
(2)里氏替换原则
(3)迪米特法则
(4)依赖注入原则
(5)接口隔离原则
(6)单一职责原则
六、Object中的方法
(1)clone方法:创建并返回此对象的一个副本
(2)getclass方法:返回此 Object 的运行类
(3)equals方法:判断两个对象是否相同(地址和数值是否都相等,意义不大),一般会被子类重写
(4)finalize方法:当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。因为无法确定该方法什么时候被调用,很少使用。
(5)hashcode方法:用于获取对象的哈希值(对象的物理地址),重写了equals方法一般都要重写hashCode方法
(6)notify方法:唤醒在此对象监视器上等待的单个线程
(7)notifyAll方法:唤醒在此对象监视器上等待的所有线程。
(8)toString方法:返回该对象的字符串表示,一般会被子类重写
(9)wait方法:用于让当前线程失去操作权限,当前线程进入等待序列,直到被唤醒
七、JAVA技术未来发展方向
(1)模块化:模块化是解决应用系统与技术平台越来越复杂、越来越庞大而产生的一系列问题的一个重要途径。
(2)混合语言:当单一的JAVA语言无法满足当前软件的复杂需求时,越来越多基于jvm的语言被应用到软件项目中。Java平台上的多语言混合编程正成为主流,每种语言都可以针对自己擅长的方面更好的解决问题。
(3)多核并行:处理并行编程。
(4)进一步丰富语法:JDK1.5之后引入了自动装箱、泛型、增强for、动态注解、枚举、可变参数等语法特性;JDK1.7还支持了String类型作为switch的表达式等;使得Java语言的精确性和易用性有了很大的进步。
(5)64位虚拟机:Java程序运行在64位的虚拟机中需要消耗更多的内存;且64位虚拟机的运行速度落后于32位的虚拟机,但随着硬件的发展,终究会过渡到64位。
八、Java事件机制
java事件机制包括三个部分:事件、事件监听器、事件源。
(1)事件:一般继承自java.util.EventObject类,封装了事件源对象及跟事件相关的信息
(2)事件监听器。实现java.util.EventListener接口,注册在事件源上,当事件源的属性或状态改变时,取得相应的监听器调用其内部的回调方法。
(3)事件源。事件发生的地方,由于事件源的某项属性或状态发生了改变(比如BUTTON被单击、TEXTBOX的值发生改变等等)导致某项事件发生。因为事件监听器要注册在事件源上,所以事件源类中应该要有盛装监听器的容器(List,Set等等)。
注:事件机制demo地址(github):
九、Java线程池
十、StringUtils工具类的基础方法
(1)isEmpty:是否为空或者长度为0;
(2)isNotEmpty:是否不为空或者长度不为0;
(3)isBlank:是否为空或者长度为0或者是空白符;
(4)isNotBlank:是否不为空或者长度不为0或者不是空白符;
(5)trim:去除首尾的空白符(control characters, char <= 32);
(6)trimToEmpty:去除首尾的空白符后(control characters, char <= 32),如果变为null或"",则返回"";
(7)trimToNull:去除首尾的空白符(control characters, char <= 32)后,如果变为null或"",则返回Null;
(8)strip:去除首尾的空白符(没长度限制);
(9)stripToNull:去除首尾的空白符后,如果变为null或"",则返回Null;
(10)stripToEmpty:去除首尾的空白符后,如果变为null或"",则返回"";
(11)equals:判断内容是否相等(object方法,一般被重写,否则判断引用变量指向的地址是否相等);
(12)indexOf:a在A第一次出现的位置;
(13)contains:A是否包含a;
(14)substring:字符串截取-开始结束位置;
(15)right、left、mid:字符串截取-具体截取长度;
十一、String类为什么是final的?
1、这样String类不能被继承(final修饰了),所以就不会被修改,这就避免了因为继承引起的安全隐患;
2、String类在程序中出现的频率比较高,如果为了避免安全隐患,在它每次出现时都用final来修饰,这无疑会降低程序的执行效率,所以干脆直接将其设为final一提高效率;
十二、string、stringbuilder、stringbuffer区别?
1、String是内容不可变的( String 是不可变的对象, 因此在每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象),而StringBuffer、StringBuilder都是内容可变的。
2、StringBuffer是同步的,数据安全的,但是效率低; StringBuilder是不同步的,数据不安全,相比于来说,效率高。
十二、 HashMap的源码,实现原理,底层结构。
底层一个散列表(通过"拉链法"实现的哈希表),它存储的内容是键值对(key-value)映射(无序,且key、value都可以为null);
1、HashMap中的key-value都是存储在Entry数组中的。
2、Entry 实际上就是一个单向链表;Entry 实现了Map.Entry 接口,即实现getKey(), getValue()等基本的读取/修改key、value值的函数。
3、通过拉链法解决哈希冲突的。
HashMap 继承于AbstractMap,实现了Map、Cloneable、Serializable接口。
HashMap 的实现不是同步的,这意味着它不是线程安全的。
HashMap 的实例有两个参数影响其性能:“初始容量” 和 “加载因子(默认0.75)”。
最大条目数:加载因子与当前容量的乘积;--->重建内部数据结构(大约两倍的桶数)
HashMap的构造函数:
// 默认构造函数。
HashMap()
// 指定“容量大小”的构造函数
HashMap(int capacity)
// 指定“容量大小”和“加载因子”的构造函数
HashMap(int capacity, float loadFactor)
// 包含“子Map”的构造函数
HashMap(Map<? extends K, ? extends V> map)
hashMap的三种遍历方式:
1、遍历HashMap的键值对:根据entrySet()获取HashMap的“键值对”的Set集合。通过Iterator迭代器遍历“第一步”得到的集合。
2、遍历HashMap的键:根据keySet()获取HashMap的“键”的Set集合。通过Iterator迭代器遍历“第一步”得到的集合。
3、遍历HashMap的值:根据value()获取HashMap的“值”的集合;通过Iterator迭代器遍历“第一步”得到的集合。
二十、 rehash与hash
1、hash:常用在put方法中,用于计算对象的哈希值;
2、rehash:在创建hashMAP的时候可以设置来个参数,一般默认
初始化容量:创建hash表时桶的数量
负载因子:负载因子=map的size/初始化容量
当hash表中的负载因子达到负载极限的时候,hash表会自动成倍的增加容量(桶的数量),并将原有的对象
重新的分配并加入新的桶内,这称为rehash。
十二、hashtable和hashmap的区别?
1、HashMap是非线程安全的,HashTable是线程安全的。
2、HashMap的键和值都允许有null值存在,而HashTable则不行。
3、因为线程安全的问题,HashMap效率比HashTable的要高。
4、ConcurrentHashMap是线程安全的HashMap的实现,可以满足线程并发无阻塞的操作集合对象。
十三、你知道HashMap的工作原理吗?
HashMap是基于hashing的原理,我们使用put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。当我们给put()方法传递键和值时,我们先对键调用hashCode()方法,返回的hashCode用于找到bucket(水桶)位置来储存Entry对象。
注:处理哈西冲突的另一种方法——拉链法。就是建立哈希表后,在每个位置下联一条链表,将数据用哈希函数处理后放入相应的链表中。
简单来说,HashMap由数组+链表组成的,数组是HashMap的主体,
链表则是主要为了解决哈希冲突而存在的。
HashMap数组的每一个元素不止是一个Entry对象,
也是一个链表的头节点。每一个Entry对象通过Next指针指向它的下一个Entry节点。
当新来的Entry映射到冲突的数组位置时,只需要插入到对应的链表即可:
image.png
十三、说说你知道的几个Java集合类?
Java API中所用的集合类,都是实现了Collection接口;
Collection<–List<–Vector
Collection<–List<–ArrayList
Collection<–List<–LinkedList
Collection<–Set<–HashSet
Collection<–Set<–HashSet<–LinkedHashSet
Collection<–Set<–SortedSet<–TreeSet
1、Vector(同步、性能差点)与ArrayList(不同步,性能好点)都是一个基于Array上的链表,
2、LinkedList不是基于Array的, 它每一个节点(Node)都包含两方面的内容:1.节点本身的数据(data);2.下一个节点的信息(nextNode)
3、List总结:
基于Array的List(Vector,ArrayList)适合查询,
而LinkedList(链表)适合添加,删除操作。
List基本上都是以Array为基础,Set则是在HashMap的基础上来实现的:
4、HashSet:线程非安全的,可以为null,无序;
5、LinkedHashSet:HashSet的一个子类,用一个链表来维护元素的插入顺序。
6、TreeSet:实现了SortedSet接口(有序的);
7、EnumSet:枚举类型集合;有序的;EnumSet并没有提供任何构造函数,要创建一个EnumSet集合对象,只需要调用allOf等方法。
8、Set集合总结
1、HashSet和TreeSet是Set集合中用得最多的集合,HashSet总是比TreeSet集合性能好,因为HashSet不需要额外维护元素的顺序。
2、LinkedHashSet需要用额外的链表维护元素的插入顺序,因此在插入时性能比HashSet低,但在迭代访问(遍历)时性能更高。因为插入的时候即要计算hashCode又要维护链表,而遍历的时候只需要按链表来访问元素。
3、EnumSet元素是所有Set元素中性能最好的,但是它只能保存Enum类型的元素。
十四、描述一下ArrayList和LinkedList各自实现和区别?
1、ArrayList:实现了List接口,基于array实现的,底层是一个数组;线程不安全的,适合用于数据查询操作;
2、LinkedList:基于链表实现的,底层是一个链表;每个节点包含节点数据与下一个节点的信息,因此适合用作插入与删除操作。
十五、Java中的队列都有哪些,有什么区别?
Queue接口与List、Set同一级别,都是继承了Collection接口;
1、无界与有界:无界指队列的元素数量是没有限制的,
可以无限添加。而有界指队列中的容量有限制,
当元素数达到一定数目之后就无法添加了。
2、阻塞与非阻塞:非阻塞队列如果队列已满的添加
与队列为空时的获取会直接返回结果,
可以说非阻塞队列是只能实时的。
阻塞队列则意味着其在队列已满的添加
与队列为空时的获取是可以选择直接获取结果
或者是进入等待到达一定条件在获取结果的。
重点就在于这个可不可以等待。
3、单端与双端:单端队列只允许新元素加到队列末尾,
然后从队列头取出。
双端队列则在队列的头尾都可进行存取操作。
1、双端队列Deque:
LinkedBlockingDeque:有界双端阻塞队列;
ConcurrentLinkedDeque:无界双端非阻塞队列;
2、非阻塞队列AbstractQueue:
PriorityQueue :(无界单端非阻塞优先级队列)实质上维护了一个有序列表;
ConcurrentLinkedQueue:(无界单端非阻塞队列)基于链接节点的、线程安全的队列。并发访问不需要同步。因为它在队列的尾部添加元素并从头部删除它们;
3、阻塞队列BlockingQueue:不是立即从队列中添加或者删除元素,线程执行操作阻塞,直到有空间或者元素可用。
* ArrayBlockingQueue :一个由数组支持的有界队列。
* LinkedBlockingQueue :一个由链接节点支持的可选有界队列。
* PriorityBlockingQueue :一个由优先级堆支持的无界优先级队列。
* DelayQueue :一个由优先级堆支持的、基于时间的调度队列。
* LinkedTransferQueue :无界限单端阻塞队列。
* SynchronousQueue :一个利用 BlockingQueue 接口的简单聚集(rendezvous)机制。
十六、反射中,Class.forName和ClassLoader.loadClass的区别?
1、Class.forName:
Class.forName(className)方法,内部实际调用的方法是 Class.forName(className,true,classloader);
第2个boolean参数表示类是否需要初始化, Class.forName(className)默认是需要初始化。
一旦初始化,就会触发目标对象的 static块代码执行,static参数也也会被再次初始化。
2、ClassLoader.loadClass:
ClassLoader.loadClass(className)方法,内部实际调用的方法是 ClassLoader.loadClass(className,false);
第2个 boolean参数,表示目标对象是否进行链接,默认false表示不进行链接;
不进行链接意味着不进行包括初始化等一些列步骤,那么静态块和静态对象就不会得到执行;
十六、Java类装载过程?
1、装载:通过累的全限定名获取二进制字节流,将二进
制字节流转换成方法区中的运行时数据结构,在内存中生
成Java.lang.class对象;
2、链接:执行下面的校验、准备和解析步骤,其中解析
步骤是可以选择的;
校验:检查导入类或接口的二进制数据的正确性;(文件
格式验证,元数据验证,字节码验证,符号引用验证)
准备:给类的静态变量分配并初始化存储空间;
解析:将常量池中的符号引用转成直接引用;
3、初始化:激活类的静态变量的初始化Java代码和静态
Java代码块,并初始化程序员设置的变量值。
十七、Java7、Java8的新特性?
一、Java7的新特性:
1、switch中使用string;
以前在switch中只能使用byte、int、short、char(四种基本类型)或enum;
2、自动资源管理:Java中某些资源是需要手动关闭的,如InputStream,Writes,Sockets,Sql classes等,新特性可自动关闭;
3、改进的通用实例创建类型推断;
泛型的类型推断:
Map<String, List<String>> anagrams = new HashMap<>();
4、数字字面量下划线支持(针对int、与long):
int one_million = 1_000_000;
5、二进制字面量:
int binary = 0b1001_1001;
6、简化可变参数方法调用:
使用一个不可具体化的可变参数并调用一个*varargs* (可变)方法时,编辑器会生成一个“非安全操作”的警告;JDK 7将警告从call转移到了方法声明的过程中。
二、Java8的新特性:
1、Lambda 表达式(闭包):Lambda 表达式主要用来定义行内执行的方法类型接口(方法的具体定义)
public class Java8Tester {
public static void main(String args[]) {
MathOperation multiplication = (int a, int b) -> {
return a * b;
};
interface MathOperation {
int operation(int a, int b);
}
}
}
2、方法引用:
方法引用通过方法的名字来指向一个方法,方法引用使用一对冒号 :: 。
3、新增函数式接口( java.util.function):
函数式接口:有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。
十八、 Java内存泄露的问题调查定位:jmap,jstack的使用等?
1、jhat:生成堆内存信息快照:dump文件;
2、jstack:java stack和native stack的信息。可以轻松得知当前线程的运行情况。
3、jstat:可以观察到classloader,compiler,gc相关信息。可以时时监控资源和性能 。
4、jmap:得到运行java程序的内存分配的详细情况。例如实例个数,大小等
十九、 抽象类和接口的区别?
接口是对动作的抽象,抽象类是对根源的抽象。抽象类表示的是,这个对象是什么。接口表示的是,这个对象能做什么。
所以,在高级语言上,一个类只能继承一个类(抽象类),但是可以实现多个接口。
区别:
1、抽象类和接口都是用来抽象具体对象的. 但是接口的抽象级别最高;
2、抽象类可以有具体的方法和属性, 接口只能有抽象方法(接口方法都是默认抽象的)和不可变常量;
3、抽象类主要用来抽象类别,接口主要用来抽象功能;
4、接口只能做方法申明,抽象类中可以做方法申明,也可以做方法实现。
5、抽象类要被子类继承,接口要被类实现。
6、接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。
7、接口可继承接口,并可多继承接口,但类只能单根继承。
相同点:
1、抽象类和接口都不能直接实例化;
2、抽象类可以有具体的方法和属性, 接口只能有抽象方法(接口方法都是默认抽象的)和不可变常量;
3、抽象类主要用来抽象类别,接口主要用来抽象功能;
4、抽象类中,且不包含任何实现,派生类必须覆盖它们。接口中所有方法都必须是未实现的。
5、抽象类里的抽象方法必须全部被子类所实现,如果子类不能全部实现父类抽象方法,那么该子类只能是抽象类。同样,一个实现接口的时候,如不能全部实现接口方法,那么该类也只能为抽象类。
二十、hashCode() 与 equals() 生成算法、方法怎么重写?
object中的函数:
hashCode() :native关键字修饰,说明这个方法是个原生函数(C++编写的),返回一个int的哈希值;
equals():比较两个引用所指向的对象的内存地址是否一致;
为什么要重写:
我们在定义类时,我们经常会希望两个不同对象的某些属性值相同时就认为他们相同,
所以我们要重写equals()方法,按照原则,我们重写了equals()方法,
也要重写hashCode()方法,要保证上面所述的b,c原则;
所以java中的很多类都重写了这两个方法,例如String类,包装类;
b. 如果两个对象equals()返回值为true,则他们的hashCode()也必须返回相同的int值
c. 如果两个对象equals()返回值为false,则他们的hashCode()返回值也必须不同
二十一、String 类的常用方法?
1、字节、字符与string的转换,string提供了相应的构造函数进行转换。
2、字符串比较:
equals("anoterString");
equalsIgnore("不区分大小写");
compareTo("判断两个字符串的大小");
3、字符串的查找:
contains(anoterString);
indexOf("由前向后找");
lastIndexOf("由后向前找");
starsWith("是否以此开头");
endsWith("是否以此结尾");
4、字符串替换:
repaceAll(String old,String replacement);
repaceFirst(String old,String replacement)
5、字符串的截取:
substring(int beginIndex);
substring(int beginIndex,int endIndex);
6、字符串拆分:
split(String regex):按照指定的字符串进行全部拆分;
split(String regex,int limit):前面拆,后面不拆作为整体;
7、其他方法:
concat(String str):拼接(+);
toLowerCase():转换为小写字母;
isEmpty():判断是否是空字符串("");
二十二、Java 的引用类型有哪几种?
1、强引用:Java中默认声明的就是强引用,只要强引用存在,垃圾回收器将永远不会回收被引用的对象,哪怕内存不足时,JVM也会直接抛出OutOfMemoryError,不会去回收。如果想中断强引用与对象之间的联系,可以显示的将强引用赋值为null,这样一来,JVM就可以适时的回收对象了。
2、软引用:软引用是用来描述一些非必需但仍有用的对象。在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。这种特性常常被用来实现缓存技术,比如网页缓存,图片缓存等。
3、弱引用:弱引用的引用强度比软引用要更弱一些,无论内存是否足够,只要 JVM 开始进行垃圾回收,那些被弱引用关联的对象都会被回收。
4、虚引用:虚引用是最弱的一种引用关系,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,它随时可能会被回收(gc的时候,用途是说明一个对象是否被标记回收)。
二十三、讲讲IO里面的常见类,字节流、字符流、接口、实现类、方法阻塞。
IO是面向流的,NIO是面向缓冲区的。
1、常见接口及其实现类:
image.png
2、字节流:字节流处理单元为1个字节,操作字节和字节数组;
OutputStream、InputStream两个专门操作字节流的抽象类;
常用的子类有:FileInputStream,ByteArrayInputStream、等;
3、字符流:字符流处理的单元为2个字节的Unicode字符,分别操作字符、字符数组或字符串;
Reader、Writer两个专门操作字符流的抽象类;
常用的子类有:CharArrayReader、PipedReader、FilterReader是抽象类、StringReader、InputStreamReader等;
4、方法阻塞:如果一个线程收到IO请求,若该IO请求的资源未准备就绪,那么该线程就进入阻塞挂起状态,直到收到资源准备就绪的反馈才继续执行该线程;
非阻塞:IO请求资源不可用时,IO请求离开返回,返回数据标识资源不可用。
二十四、讲讲NIO。
1、NIO即New IO;同步非阻塞IO;非阻塞体现在用户线程发起IO请求后,会直接得到返回结果,即便在数据未就绪的情况下,也能马上得到失败信息。NIO可让您只使用一个(或几个)单线程管理多个通道(网络连接或文件)即多路复用,但代价是解析数据可能会比从一个阻塞流中读取数据更复杂。
2、与之相对应的是Java中传统的I/O;传统的IO包提供的是同步阻塞IO;当用户线程发出IO请求后,内核会去查看数据是否已经就绪,若未就绪,则用户线程会处于阻塞状态(让出CPU)。
区别:
1、java IO的各种流是阻塞的。NIO是同步非阻塞IO;
2、IO是面向流的,NIO是面向缓冲区的。
3、IO流是单向的,直接面向字节流,通过InputStream、OutputStream来完成数据的输入输出;而NIO是双
向的,通过建立通道(Channel),然后将数据装在缓冲区(Buffer)在通道上进行传输。针对不同类型的数
据有不同的Buffer。
NIO类库:
1、缓冲区Buffer:抽象类,NIO库中,所有的数据都是用缓冲区处理的。在读取数据时,它是直接读取到缓冲区中的,在写入数据时,写入到缓冲区中。任何时候该访问NIO中的数据,都是通过缓冲区进行操作。
2、通道Channel:通道和流的不同之处在于通道是双向的,流只是在一个方向上移动,而通道可以用于读写或者二者的同时进行。
3、多路复用器selector:不断地轮询注册在其上的channel,如果某个channel上发生了读或者写的事件,这个channel就处于就绪状态,会被selector轮询出来,然后通过selectionkey可以获取就绪channel的集合,在进行后续的I/O操作。
二十五、String 编码UTF-8 和GBK的区别?
1、GBK:GBK是国家编码,通用性比UTF8差,不过UTF8占用的数据库比GBK大;
2、UTF-8:UTF8是国际编码,它的通用性比较好,外国人也可以浏览论坛;
二十六、什么时候使用字节流、什么时候使用字符流?
1、字节流可用于任何类型的对象,包括二进制对象,而字符流只能处理字符或者字符串;
- 字节流提供了处理任何类型的IO操作的功能,但它不能直接处理Unicode字符,而字符流就可以。
综上所述:文档类能用文字体现的用字符流,其他的都用字节流;
字符流本质上还是字节流:字符流处理的单元为2个字节的Unicode字符;所以它对多国语言支持性比较好;
二十七、递归读取文件夹下的文件,代码怎么实现?
/**
* 递归读取文件夹下的 所有文件
*
* @param testFileDir 文件名或目录名
*/
private static void testLoopOutAllFileName(String testFileDir) {
if (testFileDir == null) {
//因为new File(null)会空指针异常,所以要判断下
return;
}
File[] testFile = new File(testFileDir).listFiles();
if (testFile == null) {
return;
}
for (File file : testFile) {
if (file.isFile()) {
System.out.println(file.getName());
} else if (file.isDirectory()) {
System.out.println("-------this is a directory, and its files are as follows:-------");
testLoopOutAllFileName(file.getPath());
} else {
System.out.println("文件读入有误!");
}
}
}
二十八、session和cookie的区别和联系,session的生命周期,多个服务部署时session管理。
1、前提:HTTP是一种无状态的协议,为了分辨链接是谁发起的,需自己去解决这个问题。不然有些情况下即使是同一个网站每打开一个页面也都要登录一下。而Session和Cookie就是为解决这个问题而提出来的两个机制。
2、Cookie:
1、Cookies是服务器在客户端本地机器上存储的小段文本并随每一个请求发送至同一服务器,是在客户端保持状态的方案。
2、Cookie的主要内容包括:名字,值,过期时间,路径和域。
3、key, value形式。过期时间可设置的,如不设,则浏览器
关掉就消失了,存储在内存当中,
否则就按设置的时间来存储在硬盘上的,过期后自动清除,
比方说开关机关闭再打开浏览器后他都会还存在,
前者称之为Session cookie 又叫 transient cookie,
后者称之为Persistent cookie(暂时) 又叫 permenent(永久) cookie。
路径和域就是对应的域名,a网站的cookie自然不能给b用。
3、Session
1、存在服务器的一种用来存放用户数据的类HashTable结构。
2、浏览器第一次发送请求时,服务器自动生成了一HashTable和一Session ID来唯一标识这个HashTable,并将其通过响应发送到浏览器。浏览器第二次发送请求会将前一次服务器响应中的Session ID放在请求中一并发送到服务器上,服务器从请求中提取出Session ID,并和保存的所有Session ID进行对比,找到这个用户对应的HashTable。
3、当用户在应用程序的 Web页间跳转时,存储在 Session 对象中的变量不会丢失而是在整个用户会话中一直存在下去。
4、Session的实现方式和Cookie有一定关系。建立一个连接就生成一个session id,把session id存在Cookie中,每次访问的时候将Session id带过去就可以识别了.
4、session与cookie的区别
1、存储数据量方面:session 能够存储任意的 java 对象,cookie 只能存储 String 类型的对象;
2、一个在客户端一个在服务端。因Cookie在客户端所以可以编辑伪造,不是十分安全。
3、Session过多时会消耗服务器资源,大型网站会有专门Session服务器,Cookie存在客户端没问题。
4、建议将登陆信息等重要信息存放为SESSION,其他信息如果需要保留,可以放在COOKIE中
5、单点登入的原理
单点登录的原理是后端生成一个 session ID,设置到 cookie,
后面所有请求浏览器都会带上cookie,
然后服务端从cookie获取 session ID,查询到用户信息。
所以,保持登录的关键不是cookie,而是通过cookie 保存和传输的 session ID,本质是能获取用户信息的数据。
除了cookie,还常用 HTTP 请求头来传输。
但这个请求头浏览器不会像cookie一样自动携带,需手工处理
6、session多服务器间共享
1、将 session维护在客户端,利用 cookie,但客户端存在风险数据不安全,且可以存放的数据量较小,所以将session 维护在客户端还要对 session 中的信息加密。
2、
7、token
1、 token的意思是“令牌”,是用户身份的验证方式,最简单的token组成:uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名)。
2、Session的状态是存储在服务器端,客户端只有session id;而Token的状态是存储在客户端;
3、如果你的用户数据可能需要和第三方共享,或者允许第三方调用 API 接口,用 Token 。如果永远只是自己的网站,自己的 App,用什么就无所谓了。
4、 Session 是一种HTTP存储机制,目的是为无状态的HTTP提供的持久机制。
所谓Session 认证只是简单的把User 信息存储到Session 里,
因为SID 的不可预测性,暂且认为是安全的。这是一种认证手段。
而Token机制的话,提供的是认证和授权,认证是针对用户,
授权是针对App 。其目的是让 某App有权利访问某用户 的信息。
二十九、jsp和servlet的区别?
1、JSP的本质就是Servlet,JVM只能识别java的类,不能识别JSP的代码,Web容器将JSP的代码编译成JVM能够识别的java类。
2、Servlet的应用逻辑是在Java文件中,从Java代码中动态输出HTML,而JSP的情况是Java和HTML可以组合成一个扩展名为.jsp的文件。JSP侧重于视图,Servlet主要用于控制逻辑。
3、JSP 工作原理:
JSP页面在执行的时候都会被服务器端的JSP引擎转换为Servelet(.java),然后又由JSP引擎调用Java编译器,将Servelet(.java)编译为Class文件(.class),并由Java虚拟机(JVM)解释执行。
三十、.jvm性能调优都做了什么?
1、控制GC的行为.GC是一个后台处理,但是它也是会消耗系统性能的,因此经常会根据系统运行的程序的特性来更改GC行为;
2、控制JVM堆栈大小.一般来说,JVM在内存分配上不需要你修改,但是当你的程序新生代对象在某个时间段产生的比较多的时候,就需要控制新生代的堆大小.同时,还需要控制总的JVM大小避免内存溢出;
3、控制JVM线程的内存分配.如果是多线程程序,产生线程和线程运行所消耗的内存也是可以控制的。
堆大小设置:
JVM 中最大堆大小有三方面限制:
1、相关操作系统限制;
2、系统的可用虚拟内存限制;
3、系统的可用物理内存限制;
典型设置:
1、-Xmx3550m -Xms3550m:
设置JVM最大可用内存为3550M,与初始内存,一般设置成相同的值,避免每次垃圾回收完成后JVM重新分配内存。
2、-Xmn2g:设置年轻代大小为2G;
整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小;
3、-XX:MaxPermSize=64m
持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。
4、-Xss128k:设置每个线程的堆栈大小。
相同物理内存下,减小这个值能生成更多的线程。
但是操作系统对一个进程内的线程数还是有限制的,
不能无限生成,经验值在3000~5000左右。
5、-XX:NewRatio=4:设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5;
6、-XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的大小比值;
则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6
7、-XX:MaxTenuringThreshold=0:设置垃圾最大年龄。
垃圾回收器选择:
1、串行收集器:
串行收集器只适用于小数据量的情况;
开启选项:-XX:+SerialGC
2、并行收集器:
主要以到达一定的吞吐量为目标,适用于科学技术和后台处理等。
-XX:+UseParallelGC:选择垃圾收集器为并行收集器(年轻代);
-XX:+UseParallelOldGC:配置年老代垃圾收集方式为并行收集;
-XX:ParallelGCThreads=20:配置并行收集器的线程数(最好与处理器数目一致);
XX:+UseAdaptiveSizePolicy:设置此选项后,并行收集器会自动选择年轻代大小和相应的Survivor区比例;
-XX:MaxGCPauseMillis=100:设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值。
3、并发标记清除收集器CMS:
响应时间优先的并发收集器;减少垃圾收集时的停顿时间。适用于应用服务器、电信领域等。
基于标记清除法,会有内存碎片;对CPU资源要求较高;
开启选项:-XX:+UseConcMarkSweepGC;
-XX:CMSFullGCsBeforeCompaction:此值设置运行多少次GC以后对内存空间进行压缩、整理。
4、G1收集器:
解决内存碎片问题;对CPU资源要求较高;提供设置预期停顿时间,来限制垃圾回收的时间,防止应用雪崩。对内存的压缩性较好;
引入分区的思路(更加的灵活):将内存划分为一个个相等大小的内存分区,回收时则以分区为单位进行回收,存活的对象复制到另一个空闲分区中。
由于都是以相等大小的分区为单位进行操作,因此G1天然就是一种压缩方案(局部压缩);
开启选项:-XX:+UseG1GC
辅助信息(命令行参数:具体GC信息):
1、-XX:+PrintGCDetails:
2、-XX:+PrintGC:
三十、单例模式
单例模式:一个类只有一个实例,即一个类只有一个对象实例。(打印任务可以有多个,但是正在打印输出的任务只能有一个)
1、懒汉式:在类加载时不初始化。
//多线程场景下的懒汉模式
public class SingletonDemo2 {
private static SingletonDemo2 instance;
private SingletonDemo2(){}
public static synchronized SingletonDemo2 getInstance(){
if (instance == null) {
instance = new SingletonDemo2();
}
return instance;
}
}
//懒汉式双重检测
public class Singleton {
private static volatile Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
2、饿汉式:在类加载时就完成了初始化,所以类加载比较慢,但获取对象的速度快。
//饿汉式延迟加载:类加载的时候就实例化,没有多线程同步问题
public class SingletonDemo5 {
//内部类:SingletonHolder类没有被主动使用
private static class SingletonHolder{
private static final SingletonDemo5 instance = new SingletonDemo5();
}
private SingletonDemo5(){}
//显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance
public static final SingletonDemo5 getInsatance(){
return SingletonHolder.instance;
}
}
单例模式的优缺点:
优点:
1、单例模式在内存中只有一个实例,减少内存开支,特别是一个对象需要频繁地创建销毁时。
2、 单例模式只生成一个实例,可以减少系统的性能开销,
当一个对象产生需要比较多的资源时,如读取配置,
则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
(例如mybatis的sqlSessionFactory就是设计为单利模式的,一次来控制数据库的连接实例对象只有一个实例,
防止繁琐的建立数据库连接,降低数据库性能)
3、 单例模式可以避免对资源的多重占用,例如一个写文件操作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。
4、单例模式可以在系统设置全局的访问点,优化和共享资源访问。
缺点:
1、单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。
2、单例对象如果持有Context,那么很容易引发内存泄漏,此时需要注意传递给单例对象的Context最好是Application Context。
三十一、如果想不被 GC 怎么办?
单例对象不会被GC回收,因此不想被GC,直接设置为单例对象就行了。
1、垃圾收集算法使用根搜索算法(或者引用计数法):通过根(GC Roots)的引用作为起点,从根开始搜索,经过一系列的路径,如果存在可达java堆中的对象的路径,那么这个对象就是“活”的,是不可回收的。
2、可以作为根的对象有:
1、虚拟机栈中的引用的对象;
2、本地方法栈的引用的对象;
3、方法区中类静态属性的引用的对象;
4、方法区中常量引用的对象;
显然,java中单例模式创建的对象,被自己类中的静态属性所引用,单例对象不会被jvm垃圾收集。
若想回收单例类:将单例类回收,那么堆中的对象就会失去到根的路径,单例类对象必然会被垃圾收集掉。
三十一、jvm卸载类的判定条件:
1、该类所有的实例都已经被回收;
2、加载该类的ClassLoader已经被回收;
3、该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法。
三十二、如果想在 GC 中生存 1 次怎么办?
想在对象生命周期中至少被GC一次后存活,最简单的方法是重写Object的finalize()。
1、Finalize调用流程:
GC时,当对象变成(GC Roots)不可达时,若该对象重写了finalize方法并且未执行过finalze方法,
则将其放入队列,由一低优先级线程执行该队列中对象的finalize方法;否则直接将其回收。
执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活”。
2、system.gc()
system.gc()并不是你调用就马上执行的,程序自动结束时不会执行垃圾回收的。
对象被回收时,要经过两次标记,第一次标记,如果finalize未被重写,或者finalize被调用过,那么垃圾回收并不会去执行finalize,
否则会执行finalize方法;第二次标记,如果对象不能在finalize中成功拯救自己,那真的就要被回收了.
三十三、数组多大放在 JVM 老年代?老年代中数组的访问方式?
1、
2、
三十四、简单工厂(静态工厂)模式
简单工厂模式的实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类(产品类继承自一个父类或接口)的实例。
public class SimpleNoodlesFactory {
public static final int TYPE_LZ = 1;//兰州拉面
public static final int TYPE_PM = 2;//泡面
public static final int TYPE_GK = 3;//干扣面
//INoodles是一个抽象类
public static INoodles createNoodles(int type) {
switch (type) {
case TYPE_LZ:
//LzNoodles继承于INoodles
return new LzNoodles();
case TYPE_PM:
//PaoNoodles继承于INoodles
return new PaoNoodles();
case TYPE_GK:
default:
//GankouNoodles继承于INoodles
return new GankouNoodles();
}
}
}
**
* 简单工厂模式使用方式
*/
INoodles noodles = SimpleNoodlesFactory.createNoodles(SimpleNoodlesFactory.TYPE_GK);
noodles.desc();
特点优点:
1、一个具体的类,有一个重要的create()方法,利用if或者 switch创建产品并返回。
2、 create()方法通常是静态的,所以也称之为静态工厂。
3、能够根据外界给定的信息,决定究竟应该创建哪个具体类的对象。
缺点:
1、 扩展性差(我想增加一种新类型的实例,除了新增对应类,还需要修改工厂类方法);
2 、不同的产品需要不同额外参数的时候不支持;
3、简单工厂模式违背了“开闭原则”,就是违背了“系统对扩展开放,对修改关闭”的原则,因为当我新增加一个产品的时候必须修改工厂类,相应的工厂类就需要重新编译一遍。
三十四、工厂方法模式
工厂方法模式中抽象工厂类负责定义创建对象的接口,
具体对象的创建工作由继承抽象工厂的具体类实现。
提供一个用于创建对象的接口(工厂接口),让其实现类(工厂实现类)决定实例化哪一个类(产品类),并且由该实现类创建对应类的实例。
三要素:
1、提供一个产品类的接口。产品类均要实现这个接口(也可以是abstract类,即抽象产品)。
2、提供一个工厂类的接口。工厂类均要实现这个接口(即抽象工厂)。
3、由工厂实现类创建产品类的实例。工厂实现类应有一个方法,用来实例化产品类。
优点:
1、可以一定程度上解耦,消费者和产品实现类隔离开,只依赖产品接口(抽象产品),产品实现类如何改动与消费者完全无关。
适用场景:
1、消费者不关心它所要创建对象的类(产品类)的时候。
package c.build_type_factory_method;
import a.build_type.Animal;
/**
* 工厂方法模式:
* 工厂方法模式中抽象工厂类负责定义创建对象的
* 接口,具体对象的创建工作由继承抽象工厂的具
* 体类实现。
* 优点:
* 客户端不需要在负责对象的创建,从而明确了各个
* 类的职责,如果有新的对象增加,只需要增加一个
* 类和具体的工厂类即可,不影响已有的代码,后期
* 维护容易,增强了系统的扩展性。
* 缺点:
* 需要额外的编写代码,增加了工作
*/
public interface AnimalFactory02 {
public abstract Animal createAnimal();
}
//工厂实现类1
package c.build_type_factory_method;
import a.build_type.Animal;
import a.build_type.Dog;
public class DogFactory implements AnimalFactory02{
@Override
public Animal createAnimal() {
return new Dog();
}
}
//工厂实现类2
package c.build_type_factory_method;
import a.build_type.Animal;
import a.build_type.Cat;
public class CatFactory implements AnimalFactory02{
@Override
public Animal createAnimal() {
return new Cat();
}
}
//工厂方法测试
package c.build_type_factory_method;
import a.build_type.Animal;
public class AnimalTest02 {
public static void main(String[] args) {
// 需要狗
AnimalFactory02 f01 = new DogFactory();
Animal d = f01.createAnimal();
d.eat();
// 需要猫
AnimalFactory02 f02 = new CatFactory();
Animal c = f02.createAnimal();
c.eat();
}
}
三十六、抽象工厂模式、
为创建一组相关或相互依赖的对象提供一个接口,而且无需指定他们的具体类。
抽象工厂模式是工厂方法模式的升级版本;
工厂方法模式针对的是一个产品等级结构;
而抽象工厂模式则是针对的多个产品等级结构。
在编程中,通常一个产品结构,表现为一个接口或者抽象类,
也就是说,工厂方法模式提供的所有产品都是衍生自同一个接口或抽象类,
而抽象工厂模式所提供的产品则是衍生自不同的接口或抽象类。
interface IProduct1 {
public void show();
}
interface IProduct2 {
public void show();
}
class Product1 implements IProduct1 {
public void show() {
System.out.println("这是1型产品");
}
}
class Product2 implements IProduct2 {
public void show() {
System.out.println("这是2型产品");
}
}
interface IFactory {
public IProduct1 createProduct1();
public IProduct2 createProduct2();
}
class Factory implements IFactory{
public IProduct1 createProduct1() {
return new Product1();
}
public IProduct2 createProduct2() {
return new Product2();
}
}
public class Client {
public static void main(String[] args){
IFactory factory = new Factory();
factory.createProduct1().show();
factory.createProduct2().show();
}
}
工厂方法模式针对的是一个产品等级结构;而抽象工厂模式则是针对的多个产品等级结构。
优点:
1、具有工厂方法模式的优点外(解耦、消费者和产品实现类隔离开,只依赖产品接口);
2、最主要的优点就是可以在类的内部对产品族进行约束。
缺点:
1、 产品族的扩展将是一件十分费力的事情,
假如产品族中需要增加一个新的产品,则几乎所有的工厂类都需要进行修改。
所以使用抽象工厂模式时,对产品等级结构的划分是非常重要的。
适用场景:
1、当需要创建的对象是一系列相互关联或相互依赖的产品族时,便可以使用抽象工厂模式。一个继承体系中,如果存在着多个等级结构。
三十六、装饰者模式、
定义:装饰者模式动态的将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案;
缺点:使用装饰模式会产生比使用继承关系更多的对象。更多的对象会使得查错变得困难,特别是这些对象看上去都很相像。
三十七、观察者模式、
观察者模式也被称为发布-订阅(Publish/Subscribe)模式;观察者模式定义了一种一对多的依赖关系,一个主题对象可被多个观察者对象同时监听。当这个主题对象状态变化时,会通知所有观察者对象并作出相应处理逻辑。
//抽象观察者(Observer)接口
public interface Observer {
public void update(String msg, TextView tv);
}
//具体观察者(Person)类
public class Person implements Observer {
// 用户名
private String name;
public Person(String name) {
this.name = name;
}
@Override
public void update(String msg, TextView tv) {
tv.setText(tv.getText()+name+":"+ msg +"\n");
}
}
//抽象主题(Subject)接口
public interface Subject {
/**
* 增加观察者
* @param observer
*/
public void attach(Observer observer);
/**
* 删除观察者
* @param observer
*/
public void detach(Observer observer);
/**
* 通知观察者
*/
public void notify(String message, TextView v);
}
//具体主题(XiaosongSubject)类
public class XiaosongSubject implements Subject {
//用于保存订阅了小嵩的博客的用户
private List<Observer> mPersonList = new ArrayList<>();
@Override
public void attach(Observer observer) {
mPersonList.add(observer);
}
@Override
public void detach(Observer observer) {
mPersonList.remove(observer);
}
@Override
public void notify(String message, TextView tv) {
for (Observer observer : mPersonList) {
observer.update(message,tv);
}
}
}
网友评论