边写边学,有待提高
1、请你简单描述一下正则表达式及其用途。
在编写处理字符串的程序时,经常会有查找符合某些复杂规则的字符串的需要。正则表达式就是用于描述这些规则的工具。换句话说,正则表达式就是记录文本规则的代码。计算机处理的信息更多的时候不是数值而是字符串,正则表达式就是在进行字符串匹配和处理的时候最为强大的工具
2、Java中是如何支持正则表达式操作的。
Java中的String类提供了支持正则表达式操作的方法,包括:matches()、replaceAll()、replaceFirst()、split()。此外,Java中可以用Pattern类表示正则表达式对象,它提供了丰富的API进行各种正则表达式操作,如:
String testString = "abcabcabcdefabc";
String [] regexs = new String []{"abc+","(abc)+","(abc){2,}"};
for (String regex: regexs) {
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(testString);
System.out.println("test regex: " + regex);
while(m.find()){
System.out.println("match " + m.group() + " at position " + m.start() + "-" + (m.end() -1));
}
}
输出结果:
test regex: abc+
match abc at position 0-2
match abc at position 3-5
match abc at position 6-8
match abc at position 12-14
test regex: (abc)+
match abcabcabc at position 0-8
match abc at position 12-14
test regex: (abc){2,}
match abcabcabc at position 0-8
几个正则表达式的含义:
abc+:匹配abc或者abcc或者abccc等。
(abc)+:根据贪婪原则,匹配1次或者多次连续的abc,匹配最长的字符串。
(abc){2,}:abc至少连续出现2次,匹配abcabc或者abcabcabc等。
测试一个字符串是否匹配某个正则表达式,可以使用下面的方法:
String testString = "abcabcabcdefabc";
System.out.println(Pattern.matches("abc+", testString));
System.out.println(Pattern.matches("abc+", "abccc"));
输出结果为:false 和 true。
3、请你比较一下Java和JavaScript。
JavaScript 与Java是两个公司开发的不同的两个产品。Java 是原Sun Microsystems公司推出的面向对象的程序设计语言,特别适合于互联网应用程序开发;而JavaScript是Netscape公司的产品,为了扩展Netscape浏览器的功能而开发的一种可以嵌入Web页面中运行的基于对象和事件驱动的解释性语言。JavaScript的前身是LiveScript;而Java的前身是Oak语言。
下面对两种语言间的异同作如下比较:
基于对象和面向对象:Java是一种真正的面向对象的语言,即使是开发简单的程序,必须设计对象;JavaScript是种脚本语言,它可以用来制作与网络无关的,与用户交互作用的复杂软件。它是一种基于对象(Object-Based)和事件驱动(Event-Driven)的编程语言,因而它本身提供了非常丰富的内部对象供设计人员使用。
解释和编译:Java的源代码在执行之前,必须经过编译。JavaScript是一种解释性编程语言,其源代码不需经过编译,由浏览器解释执行。(目前的浏览器几乎都使用了JIT(即时编译)技术来提升JavaScript的运行效率)
强类型变量和类型弱变量:Java采用强类型变量检查,即所有变量在编译之前必须作声明;JavaScript中变量是弱类型的,甚至在使用变量前可以不作声明,JavaScript的解释器在运行时检查推断其数据类型。
代码格式不一样
4、请你介绍一下Java中如何跳出当前的多重嵌套循环
在最外层循环前加一个标记如A,然后用break A;可以跳出多重循环。
(Java中支持带标签的break和continue语句,作用有点类似于C和C++中的goto语句,但是就像要避免使用goto一样,应该避免使用带标签的break和continue,因为它不会让你的程序变得更优雅,很多时候甚至有相反的作用,所以这种语法其实不知道更好)
public static void main(String[] args) {
int count = 0;
A:
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
if (i == 5) {
System.out.println("count = " +count);
break A;
}
count++;
}
}
}
输出结果:
count = 50
5、请你讲讲&和&&的区别。
&运算符有两种用法:(1)按位与;(2)逻辑与。&&运算符是短路与运算。
&&之所以称为短路运算是因为,如果&&左边的表达式的值是false,右边的表达式不会进行运算。
例如在验证用户登录时判定用户名不是null而且不是空字符串,应当写为:username != null &&!username.equals(""),二者的顺序不能交换,更不能用&运算符,因为第一个条件如果不成立,根本不能进行字符串的equals比较,否则会产生NullPointerException异常。
6、int和Integer有什么区别?什么是自动拆装箱?
Java是一种近乎纯洁的面向对象编程语言,但为了编程方便还是引入了基本数据类型,但是为了能够将这些基本数据类型当做对象来操作,Java为每个基本数据类型都引入了对应的包装类型(wrapper class),int的包装类型为integer,从Java 5开始引入了自动拆装箱机制,使得两者可以互相转化。
Java为每个基本数据类型都提供了包装类型:
原始类型: boolean,char,byte,short,int,long,float,double
包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double
Integer a= 127 与 Integer b = 127 相等吗
对于对象引用类型:==比较的是对象的内存地址。
对于基本数据类型:==比较的是值。
如果整型字面量的值在-128 到 127 之间,那么自动装箱时不会 new 新的 Integer 对象, 而是直接引用常量池中的 Integer 对象,超过范围 a1==b1 的结果是 false.
public static void main(String[] args) {
Integer a = new Integer(3);
Integer b = 3; //将3自动装箱成Integer类型
int c = 3;
System.out.println(a == b); // false 两个引用没有引用同一对象
System.out.println(a == c); // true a 自动拆箱成 int 类型再和 c 比较
System.out.println(b == c); // true
Integer a1 = 128;
Integer b1 = 128;
System.out.println(a1 == b1); // false 生成新的Integer对象
Integer a2 = 127;
Integer b2 = 127;
System.out.println(a2 == b2); // true 直接从常量池拿
}
7、请你讲讲如何输出一个某种编码的字符串?如iso8859-1等
public static String tranStr() {
String tempStr = "";
try {
tempStr = new String(tempStr.getBytes("ISO-8859-1"), "GBK");
tempStr = tempStr.trim();
}
catch (Exception e) {
System.err.println(e.getMessage());
}
return tempStr;
}
8、说一下string、stringBuffer和stringBuilder的区别。
可变性
string中使用字符数组保存字符串,private final char value[],所以string对象是不可变的。
stringBuffer和stringBuilder都继承自AbstractStringBuilder类,在AbstractStringBuilder类中用同样使用字符数组保存字符串,不同的是在AbstractStringBuilder类中的字符数组char[] value没有用final关键字修饰,这两种对象都是可变的。
线程安全性
string中的对象是不可变的,也就是常量,是线程安全的。
AbstractStringBuilder类是StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。
Stringbuffer 对方法重写时加了同步锁,或对调用的方法加了同步锁(synchronized),是线程安全的;
StringBuilder 并没有对方法进行加 同步锁,所以是非线程安全的。
性能
对string对象每次进行改变时,都会新生成一个对象,然后将指针指向新的string对象;
StringBuffer每次都会对StringBuffer对象本身进行操作,不是新生成对象并改变引用;
相同情况下使用 StirngBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。因为StringBuffer 加了synchronized关键字。
StringBuffer 中还存在toStringCache字段,注解的含义是 toString返回的最后一个值的缓存,在修改StringBuffer时清除。在大部分方法中都会先将toStringCache设为null。
/**
* A cache of the last value returned by toString. Cleared
* whenever the StringBuffer is modified.
*/
private transient char[] toStringCache;
9、请你谈谈大O符号(big-O notation)
大O符号表示性能在渐进意义(n–>无穷)下的最坏情况。
大O描述当数据结构中的元素增加时,算法的规模和性能在最坏情景下有多好。
一般可以描述一个算法的时间复杂度和空间复杂度。
10、请你讲讲数组(Array)和列表(ArrayList)的区别?什么时候应该使用Array而不是ArrayList?
区别
Array的长度是固定的,ArrayList的长度是动态变化的;
Array可以包含基本数据类型和对象类型,ArrayList只能包含对象类型;
ArrayList提供了更多的方法和特性,比如addAll(),removeAll(),iterator()等等;
对于基本数据类型,集合采用自动装箱机制来减少编码工作量,当处理固定大小的基本数据类型时,这种效率较慢。
应用场景
当长度不固定时,采用ArrayList;
当长度固定且为基本数据类型时,采用Array,不需要自动拆装箱。
11、请你解释值传递和引用传递。
Java中只有值传递
值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量。
引用传递是对对象变量类型而言的,传递的是对象地址,所以引用对象改变时原对象也改变。
int a = 5;
int b = a;
b= 4;
System.out.println("a:"+ a);
System.out.println("b:"+ b);
输出
a:5
b:4
class Person{
private String name;
private int age;
}
Person p1 = new Person();
p1.setName("name1");
p1.setAge(11);
Person p2 = p1;
p2.setName("name2");
p2.setAge(12);
System.out.println("p1name :" + p1.getName() + " p1age :" + p1.getAge());
System.out.println("p2name :" + p2.getName() + " p2age :" + p2.getAge());
输出
p1name :name2 p1age :12
p2name :name2 p2age :12
12、请你解释为什么会出现4.0-3.6=0.40000001这种现象?
2进制的小数无法精确的表示10进制的小数,计算机对10进制小数进行运算时要先转化成2进制小数,这个过程中出现了误差。
10进制的数在内存中通过补码的形式进行存储,正数的原反补码相同,负数的反码是原码除符号位的其余位取反,补码是反码+1。
13、请你说说Lambda表达式的优缺点。
先简单看一下什么是Lambda表达式。
Lambda表达式也可称为闭包,它是推动Java 8 发布的最重要新特性。Lambda 允许把函数作为一个方法的参数。lambda 表达式只能引用标记了 final 的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)
public interface vo{
void convert(int i);
}
int a = 1;
vo b = (n)-> System.out.println( a + 2 );
b.convert(1);
输出结果为:3
int a = 1;
vo b = (n)-> System.out.println( a + 2 );
b.convert(1);
a=2;
此时无法通过编译,因为lambda表达式中的参数在上下文有变化。
优点:简洁,容易并行计算,可能代表未来的编程趋势。
缺点:若不用并行计算,很多时候计算速度没有比传统的for循环快(并行计算有时需要预热才显示出效率优势),不容易调试,若其他程序员没有学过 lambda 表达式,代码不容易让其他语言的程序员看懂。
14、说说Java 8的新特性
1、函数式接口
Java 8引入的一个核心概念是函数式接口(Functional Interfaces),通过在接口中添加一个抽象方法,这些方法可以直接在接口中运行,如果一个接口中定义了唯一一个抽象方法,那么这个接口就是函数式接口。同时引入了一个新的注解:@FunctionalInterface 。可以把它放到一个接口前,表示这个接口是一个函数式接口。这个注解时非必须的,只要接口中只定义了一个抽象方法,虚拟机会自动判断,不过最好在接口上使用注解 @FunctionalInterface 进行声明。如果在接口中添加了@FunctionalInterface 注解,只允许有一个抽象方法,否则会报错
2、Lambda表达式
函数式接口的重要属性是:我们能使用Lambda表达式来实例化它们,Lambda表达式能让你讲函数作为方法的参数,或将代码作为数据对待。Lambda表达式的引入给开发者带来了很多优点:在Java 8之前,匿名内部类,监听器和事件处理器的使用都显得很冗长,代码可读性很差,Lambda表达式的应用使得代码变得很紧凑,可读性增强;Lambda表达式使得并行操作大集合变得很方便,可以充分发挥多核CPU的优势,更易于为多核处理器编写代码。
3、集合之流式操作(Stream)
Java 8引入了流式操作(Stream),通过该操作可以实现对集合(Collection)的并行处理和函数式操作。根据操作返回的结果不同,可以将流式操作分为中间操作和最终操作两种。最终操作返回一特定类型的结果,中间操作返回流本身,这样就可以将多个操作依次串联起来。根据流的并发性,流可以分为串行和并行两种。流式操作实现了集合的过滤、排序、映射等操作。
Stream 和 Collection 集合的区别:Collection 是一种静态的内存数据结构,而Stream 是有关计算的。前者主要面向内存,储存在内存中,后者主要面向CPU,通过CPU实现计算。
串行和并行的流
流有串行和并行两种,串行流的操作是在一个线程中依次完成的,并行流的操作是在多个线程上同时执行。串行流和并行流可以相互切换:通过 stream.sequential() 返回串行的流,通过 stream.parallel() 返回并行的流。并行流可以很大程度上提高程序的执行效率。
中间操作:
该操作会保持 stream 处于中间状态,允许做进一步的操作。它返回的还是的 Stream,允许更多的链式操作。常见的中间操作有:
filter():对元素进行过滤;
sorted():对元素排序;
map():元素的映射;
distinct():去除重复元素;
subStream():获取子 Stream 等。
最终操作:
该操作必须是流的最后一个操作,一旦被调用,Stream 就到了一个终止状态,而且不能再使用了。常见的终止操作有:
forEach():对每个元素做处理;
toArray():把元素导出到数组;
findFirst():返回第一个匹配的元素;
anyMatch():是否有匹配的元素等。
15、"=="和"equals"的区别
“==” 比较两个对象时,比较的是对象的内存地址是否相等,如果两个对象引用完全相同,则该操作符返回true。
“==” 比较两个基本数据类型时,比较的是值是否相等。
比较String时要使用equals。
equals方法是基类Object中的实例方法,因此对所有继承于Object的类都会有该方法,如果不重写equals方法,比较的是引用是否相同。
Object类中equals方法的实现:
public boolean equals(Object obj) {
return (this == obj);
}
重写equals方法主要分3步骤:
1、先比较引用是否相同(是否为同一对象)
2、再比较是否为同类型
3、最后比较内容是否一致
16、equals和hashCode的联系。
两个对象equals比较为true,他们的hashCode一定相等;
两个对象的hashCode相等,他们的equals比较不一定为true;
重写equals方法,一定要重写hashCode方法;Object若不重写hashCode()的话,Object 的 hashCode方法是本地方法,也就是用 c 语言或 c++ 实现的,该方法直接返回对象的内存地址。
17、Map的分类和常见情况。
Map有四个主要常用的实现类,不是只有4个实现类
1、HashMap
JDK7中HashMap的底层实现是数组+链表;JDK8中HashMap的底层实现是数组+链表+红黑树。
JDK8中如果哈希单向链表中的节点数超过8个,则会转化为红黑树结构;如果红黑树上的节点数小于6个,则会转化为单向链表结构。
HashMap(JDK8)的put方法
原理:
1、先计算key的hash值
2、根据hash值找到数组下标
3、如果数组为空则插入,否则往链表尾部添加元素
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
put方法调用了一个putVal的方法,这个方法的目的就是将值放到对应的位置,这个对应的位置是怎么找到的呢?
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i; //定义一个tab数组,n是当前table数组长度,i是当前table数组下标
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;//这个if是当数组为null或长度为0时进行初始化操作resize
if ((p = tab[i = (n - 1) & hash]) == null)//(n - 1) & hash]查找下标,查找这个位置是否是空
tab[i] = newNode(hash, key, value, null);//如果为空时则调用newNode构造函数直接new出来,next指向null
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;//当添加的元素key值相同时,将p赋值给此时新建节点e
else if (p instanceof TreeNode)//如果是树节点,则增加到树节点上
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {//如果当前节点的next指向null
p.next = newNode(hash, key, value, null);//将p节点指向新添加的节点
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);//如果节点数超过TREEIFY_THRESHOLD ,则进行树化
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;//如果找不到指向null节点的则继续循环
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;//如果e的节点不为空的话返回e的value作为oldValue
}
}
++modCount;
if (++size > threshold)//如果大于这个值就进行扩容操作;2倍扩容。
resize();
afterNodeInsertion(evict);
return null;
}
首先,获取key的hash值,调用如下方法
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
解释一下,当key是null的时候,key的hash值为0;不为null时,先获取key的hashCode赋值给h,再将h向右移动16位,进行异或运算。得出hash值。这是是异或运算符 ^ ,而不是 & 和 | 运算符呢?
0 0 1 1
0 1 0 1
当进行&运算时,只有两者都为1才是1,得出的结构会更加偏向0 0:1的概率是3:1
当进行|运算时,只要有1就是1,得出的结果更偏向1 0:1的概率是1:3
当进行^运算时,两者相同为0,不同为1,得出的结果更加公平 0:1的概率是1:1
接下来是获取put进来的元素的数组下标,(n - 1) & hash (n代表数组长度),还有一种方法是hash%n,但是效率不如前者,JDK8中使用的是前者;获取下标后将该对象插入数组。
假如插入的两个key的数组下标相同,这种情况叫做哈希冲突(也叫哈希碰撞),此时将会形成链表,将后插入的数据插入到链表的尾部(JDK7是头部插入),新增元素后如果hashMap的容量达到threshold进行2倍扩容;扩容后,将重新计算链表中节点的位置;数组长度n变大。
查找的原理:
1、先计算key的hash值
2、根据hash值找到数组下标
3、如果找到则返回,否则遍历链表找到对应节点
删除的原理:
1、先计算key的hash值
2、根据hash值找到数组下标
3、如果找到则删除,否则遍历链表找到对应节点,然后删除节点
HashMap是线程不安全的,如果多线程环境建议使用ConcurrentHashMap
2、LinkedHashMap
是HashMap的一个子类,但它保持了记录的插入顺序,遍历时先插入的先得到,也可以在构造时带参数,按照应用次数排序,遍历时没有HashMap快。
不过有个例外,当HashMap的容量很大,实际数量很小时,遍历起来会比LinkedHashMap慢,因为HashMap的遍历速度和它的容量有关,LinkedHashMap遍历速度只和数据多少有关。
3、TreeMap
实现了sortMap接口,能够把保存的记录按照键排序(默认升序),也可以指定排序比较器,遍历时得到的数据是排过序的。
4、HashTable
HashTable和HashMap比较相似,但是它继承Dictionary类,而且是线程安全的;但是它的写入速度比较慢,不允许键或值为null。
18、将你讲讲final,finally,finalize的区别。
final用于修饰类、属性和方法。
被final修饰的类不能被继承。
被final修饰的方法不能被重写。
被final修饰的变量,如果是基本数据类型,一旦初始化则无法改变;如果是引用类型的变量,则在初始化之后无法指向另一个对象。
finally用于异常处理。
finally作为异常处理的一部分,它只能用在try/catch语句中,并且附带一个语句块,表示这段语句最终一定会被执行(不管有没有抛出异常),经常被用在需要释放资源的情况下。这句话有一定的问题,假如在try/catch语句中存在System.exit(0) 方法,将不会执行finally中的语句。
finalize
finalize是Object类中定义的,也就是说每个对象中都存在这个方法。这个方法在gc启动,该对象被回收时调用,其实gc可以回收大部分的对象(凡是new出来的对象,gc都能搞定,一般情况下我们又不会用new以外的方式去创建对象),所以一般是不需要程序员去实现finalize的。
特殊情况下,需要程序员实现finalize,当对象被回收的时候释放一些资源,比如:一个socket链接,在对象初始化时创建,整个生命周期内有效,那么就需要实现finalize,关闭这个链接。
使用finalize还需要注意一个事,调用super.finalize();
一个对象的finalize()方法只会被调用一次,而且finalize()被调用不意味着gc会立即回收该对象,所以有可能调用finalize()后,该对象又不需要被回收了,然后到了真正要被回收的时候,因为前面调用过一次,所以不会调用finalize(),产生问题。 所以,推荐不要使用finalize()方法,它跟析构函数不一样。
19、将你讲讲synchronized和lock。
类别 | synchronized | lock |
---|---|---|
存在层次 | Java的关键字,在jvm层面上 | 是一个类 |
锁的释放 | 1、以获取锁的线程执行完同步代码,释放锁 2、线程执行发生异常,jvm会让线程释放锁 | 在finally中必须释放锁,不然容易造成线程死锁 |
锁的获取 | 假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待 | 分情况而定,Lock有多个锁获取的方式 |
锁的状态 | 无法判断 | 可以判断 |
锁的类型 | 可重入 不可中断 非公平 | 可重入 可中断 可公平/非公平(两者皆可) |
锁的性能 | 少量同步 | 大量同步 |
晚上接着写