【美图-选择题考点整理-第一篇】

1. String / StringBuffer / StringBuilder 三个类

    String -- 首先,String是线程安全的吗?答案:是的。String是不可变类,所有不可变类都是线程安全的(但,线程安全的类不一定是不可变类,如StringBuffer类,靠锁实现线程安全)。String是字符串‘常量’ ==>String a = "abcd"; a = a + 1; 其中的原理是:JVM解析这段代码时,首先创建对象a,赋予一个abcd,然后再创建一个新的对象a用来执行第二句代码,而之前的对象a并没有变化。由于这种机制,每当用String操作字符串时,实际上是在不断的创建新的对象,而原来的对象就会变为垃圾被GC回收掉,这样执行效率很底。

    StringBuffer -- 线程安全类,即被多个线程操作时,JVM可以保证StringBuffer操作是线程安全的,速度比StringBuilder慢。

    StringBuilder -- 线程不安全类,但是速度最快。

    ①. String str = "This is only a" + " simple" + " test"; -- StringBuffer builder = new StringBuilder("This is only a").append(" simple").append(" test");//速度前者快

    ②. String str2 = "This is only a"; String str3 = " simple"; String str4 = " test"; String str1 = str2 +str3 + str4; //速度就很慢,这才是跟StringBuffer比较的代码

    总结:速度-- StringBuilder > StringBuffer(靠锁实现线程安全) > String(不可变类实现线程安全)

               单线程操作字符串缓冲区下操作大量数据 用 StringBuilder / 多线程操作字符串缓冲区下操作大量数据 用 StringBuffer / 操作少量数据 用 String

2. == 和 equals 总结

    ==

基本数据类型:byte,short,char,int,long,float,double,boolean,用双等号(==),比较的是他们的值。

复合数据类型(类):用(==)进行比较的时候,比较的是他们在内存中的存放地址(确切的说,是堆内存地址)。每new一次,都会重新开辟堆内存空间。

    equals

Java当中所有的类都是继承于Object这个超类的,在Object类中定义了一个equals的方法,这个方法的初始行为是比较对象的内存地址(和==一样的),但在一些类库当中这个方法被复写了,如String、Integer、Date。在这些类当中equals有其自身的实现,而不再是比较类在堆内存中的存放地址了。 所以说,对于复合数据类型之间进行equals比较,在没有覆写equals方法的情况下,他们之间的比较还是内存中的存放位置的地址值,跟双等号(==)的结果相同;如果被复写,按照复写的要求来。String中的复写equals源码大致为:先判断是否是String类->再判断长度是否相等->再逐个字符对比

    题1:(基础)

String str1 = "Hello";  

String str2 = new String("Hello");  

String str3 = str2; // 引用传递 

System.out.println(str1 == str2); // false地址不同 

System.out.println(str1 == str3); // false地址不同 

System.out.println(str2 == str3); //true同一个地址  

System.out.println(str1.equals(str2)); //true内容相同  

System.out.println(str1.equals(str3)); //true内容相同  

System.out.println(str2.equals(str3)); //true内容相同 

    题2:(提高)

String str1 = "hel";

String str2 = "lo";

String str3 = str1+str2;

String str4 = "hel" + "lo";

String str = "hello";

System.out.println(str3 == str);//false,String str3=str1+str2;会创建新的实例,也就是说str1+str2是在堆里创建的,所以结果为false了。

System.out.println(str4 == str);//true地址内容相同

    题3:(补充)

public String intern()返回字符串对象的规范化表示形式。 

一个初始时为空的字符串池,它由类 String 私有地维护。 

当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(该对象由 equals(Object) 方法确定),则返回池中的字符串。否则,将此 String 对象添加到池中,并且返回此 String 对象的引用。

它遵循对于任何两个字符串 s 和 t,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true。

存在于.class文件中的常量池,在运行期间被jvm装载,并且可以扩充。String的intern()方法就是扩充常量池的一个方法;当一个String实例str调用intern()方法时,java查找常量池中是否有相同unicode的字符串常量,如果有,则返回其引用,如果没有,则在常量池中增加一个unicode等于str的字符串并返回它的引用。

String s0=”kvill”;

String s1=new String(“kvill”);

String s2=new String(“kvill”);

System.out.println(s0==s1);//false,地址不同

s1.intern();

s2=s2.intern();

System.out.println(s0==s1);//false,虽然执行了s1.intern(),但它的返回值没有赋给s1

System.out.prntln(s0==s1.intern());//true

System.out.println(s0==s2);//true

最后再破除一个错误的理解:

有人说,“使用String.intern()方法可以将一个String类保存到一个全局的String表中,如果具有相同值的unicode字符串已经在这个表中,那么该方法返回表中已有字符串的地址,如果在表中没有相同值的字符串,则将自己的地址注册到表中”如果把这个全局的String表理解为常量池的话,最后一句话“如果在表中没有相同值的字符串,则自己的地址注册到表中”是错的


String s1=new String(“kvill”);

String s2=s1.intern();

System.out.println(s1==s1.intern());//false

System.out.println(s2==s1.intern());//true

3. Java集合部分都有哪些接口,主要体现了哪些设计模式?
        Java集合部分主要有Collection、List、Set、Map、Comparator、Iterator等
        主要体现的设计模式是策略模式和迭代模式
            策略模式主要体现在每个接口有不同的实现,可以完成互换,如List接口下有ArrayList和LinkedList,在不同的场景下可以互换。
            迭代模式主要体现在Iterator的实现,为不同的数据存储方式(数组、链表、散列表等)提供了统一的访问方式。

        Comparator体现的设计模式是什么? -- 策略模式,即不改变对象自身,而使用一个策略对象去改变它的行为。

        详细:Strategy设计模式之Comparable&Comparator接口 -- http://blog.csdn.net/zhushuai1221/article/details/51888504

                   最常用的12种设计模式 -- http://www.cnblogs.com/newsouls/archive/2011/07/28/DesignTemplage.html

        注:策略模式的优缺点是什么:
            优点:(1)将具体算法逻辑与客户类分离,(2)避免了大量的if else判断
            缺点:(1)每个算法一个类,产生了太多的类,(2)客户端要知道所有的策略类,以便决定使用哪一个。

4. 解决Hash碰撞冲突方法总结:hashCode()方法是用来保证返回唯一的hash值,当两个对象的计算值一样时,就会发生hash碰撞。解决方法如下:

    ①. 开放地址法:有一个公式:Hi=(H(key)+di) MOD m i=1,2,…,k(k<=m-1),其中,m为哈希表的表长。di 是产生冲突的时候的增量序列。

如果di值可能为1,2,3,…m-1,称线性探测再散列。

如果di取1,则每次冲突之后,向后移动1个位置。

如果di取值可能为1,-1,2,-2,4,-4,9,-9,16,-16,…k*k,-k*k(k<=m/2),称二次探测再散列。

如果di取值可能为伪随机数列,称伪随机探测再散列。

    ②. 再hash法:当产生冲突时,会再计算hash值,直到不一样为止。比如,两个字符串,第一次计算首字母hash值相同,再算第二个、第三个...缺点:计算时间增加。

    ③. 拉链法:将所有关键词的同义词的记录 存储在同一个线性链表中。

    ④. 建立公共溢出区,记录所有的发生冲突的记录。

    ★ 开放地址法与拉链法的对比:

①拉链法处理冲突简单,且无堆积现象,即非同义词决不会发生冲突,因此平均查找长度较短;(拉链法 处理简单,无堆积)

②由于拉链法中各链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况;(拉链法 结点空间动态申请 -- 即链表的元素增多链增长原理)

③开放定址法为减少冲突,要求装填因子α较小,故当结点规模较大时会浪费很多空间。而拉链法中可取α≥1,且结点较大时,拉链法中增加的指针域可忽略不计,因此节省空间;(结点规模较大时,开放地址法浪费空间多,拉链法节省空间)

④在用拉链法构造的散列表中,删除结点的操作易于实现。只要简单地删去链表上相应的结点即可。而对开放地址法构造的散列表,删除结点不能简单地将被删结 点的空间置为空,否则将截断在它之后填人散列表的同义词结点的查找路径。这是因为各种开放地址法中,空地址单元(即开放地址)都是查找失败的条件。因此在 用开放地址法处理冲突的散列表上执行删除操作,只能在被删结点上做删除标记,而不能真正删除结点。(拉链法删除元素简单,开放地址法不能真正的删除元素)

⑤指针需要额外的空间,故当结点规模较小时,开放定址法较为节省空间,而若将节省的指针空间用来扩大散列表的规模,可使装填因子变小,这又减少了开放定址法中的冲突,从而提高平均查找速度。(结点规模小时,推荐使用开放地址法,查找速度快)

5. Spring事务隔离级别(参见:http://www.cnblogs.com/yangy608/archive/2011/06/29/2093478.html)

    ①. 事务的传播属性:key属性确定代理应该给哪个方法增加事务行为。这样的属性最重要的部份是传播行为。有以下选项可供使用:

    PROPAGATION_REQUIRED--支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。(比如说,ServiceB.methodB的事务级别定义为PROPAGATION_REQUIRED, 那么由于执行ServiceA.methodA的时候,ServiceA.methodA已经起了事务,这时调用ServiceB.methodB,ServiceB.methodB看到自己已经运行在ServiceA.methodA的事务内部,就不再起新的事务。而假如ServiceA.methodA运行的时候发现自己没有在事务中,ServiceB.methodB就会为自己分配一个事务。这样,在ServiceA.methodA或者在ServiceB.methodB内的任何地方出现异常,事务都会被回滚。即使ServiceB.methodB的事务已经被提交,但是ServiceA.methodA在接下来fail要回滚,ServiceB.methodB也要回滚)

    PROPAGATION_SUPPORTS--支持当前事务,如果当前没有事务,就以非事务方式执行。

    PROPAGATION_MANDATORY--支持当前事务,如果当前没有事务,就抛出异常。

    PROPAGATION_REQUIRES_NEW--新建事务,如果当前存在事务,把当前事务挂起。

    PROPAGATION_NOT_SUPPORTED--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

    PROPAGATION_NEVER--以非事务方式执行,如果当前存在事务,则抛出异常。

    另外,PROPAGATION_NESTED -- 理解Nested的关键是savepoint。他与PROPAGATION_REQUIRES_NEW的区别是,PROPAGATION_REQUIRES_NEW另起一个事务,将会与他的父事务相互独立,而Nested的事务和他的父事务是相依的,他的提交是要等和他的父事务一块提交的。也就是说,如果父事务最后回滚,他也要回滚的。而Nested事务的好处是他有一个savepoint。也就是说ServiceB.methodB失败回滚,那么ServiceA.methodA也会回滚到savepoint点上,ServiceA.methodA可以选择另外一个分支,比如ServiceC.methodC,继续执行,来尝试完成自己的事务。但是这个事务并没有在EJB标准中定义。

    ②. 事务的隔离级别

-> ISOLATION_DEFAULT: 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别. 另外四个与JDBC的隔离级别相对应.

-> ISOLATION_READ_UNCOMMITTED: 这是事务最低的隔离级别,它充许令外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。(ISOLATION_READ_UNCOMMITTED 最低隔离级别 脏读、不可重复读、幻读)

-> ISOLATION_READ_COMMITTED: 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。

-> ISOLATION_REPEATABLE_READ: 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。

-> ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻像读。

    ③. 补充:什么是脏数据,脏读,不可重复读,幻觉读?

    脏读: 指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据, 那么另外一个事务读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的。

    不可重复读: 指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。

    幻觉读: 指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。


补充:

1. 关于线程安全(复制地址: https://zhidao.baidu.com/question/1512889958518241220.html):

    如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

    或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。

    线程安全问题都是由全局变量及静态变量引起的。 

    若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则就可能影响线程安全。比如一个 ArrayList 类,在添加一个元素的时候,它可能会有两步来完成:1. 在 Items[Size] 的位置存放此元素;2. 增大 Size 的值。在单线程运行的情况下,如果 Size = 0,添加一个元素后,此元素在位置 0,而且 Size=1;而如果是在多线程情况下,比如有两个线程,线程 A 先将元素存放在位置 0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B也向此 ArrayList 添加元素,因为此时 Size 仍然等于 0 (注意哦,我们假设的是添加一个元素是要两个步骤哦,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加 Size 的值。那好,现在我们来看看 ArrayList 的情况,元素实际上只有一个,存放在位置 0,而 Size 却等于 2。这就是“线程不安全”了。

    类要成为线程安全的,首先必须在单线程环境中有正确的行为。如果一个类实现正确(这是说它符合规格说明的另一种方式),那么没有一种对这个类的对象的操作序列(读或者写公共字段以及调用公共方法)可以让对象处于无效状态,观察到对象处于无效状态、或者违反类的任何不可变量、前置条件或者后置条件的情况。

    此外,一个类要成为线程安全的,在被多个线程访问时,不管运行时环境执行这些线程有什么样的时序安排或者交错,它必须仍然有如上所述的正确行为,并且在调用的代码中没有任何额外的同步。其效果就是,在所有线程看来,对于线程安全对象的操作是以固定的、全局一致的顺序发生的。

    正确性与线程安全性之间的关系非常类似于在描述 ACID(原子性、一致性、独立性和持久性)事务时使用的一致性与独立性之间的关系:从特定线程的角度看,由不同线程所执行的对象操作是先后(虽然顺序不定)而不是并行执行的。

    线程安全性不是一个非真即假的命题。 Vector 的方法都是同步的,并且 Vector 明确地设计为在多线程环境中工作。但是它的线程安全性是有限制的,即在某些方法之间有状态依赖(类似地,如果在迭代过程中 Vector 被其他线程修改,那么由 Vector.iterator() 返回的 iterator会抛出ConcurrentModifiicationException)。

    对于 Java 类中常见的线程安全性级别,没有一种分类系统可被广泛接受,不过重要的是在编写类时尽量记录下它们的线程安全行为。

    Bloch 给出了描述五类线程安全性的分类方法:不可变、线程安全、有条件线程安全、线程兼容和线程对立。只要明确地记录下线程安全特性,那么您是否使用这种系统都没关系。这种系统有其局限性 -- 各类之间的界线不是百分之百地明确,而且有些情况它没照顾到 -- 但是这套系统是一个很好的起点。这种分类系统的核心是调用者是否可以或者必须用外部同步包围操作(或者一系列操作)。下面几节分别描述了线程安全性的这五种类别。

    ①. 不可变 -- 不可变的对象一定是线程安全的,并且永远也不需要额外的同步。因为一个不可变的对象只要构建正确,其外部可见状态永远也不会改变,永远也不会看到它处于不一致的状态。Java 类库中大多数基本数值类如 Integer 、 String 和 BigInteger 都是不可变的。

    ②. 线程安全 -- 线程安全的对象,由类的规格说明所规定的约束在对象被多个线程访问时仍然有效,不管运行时环境如何排列,线程都不需要任何额外的同步。这种线程安全性保证是很严格的 -- 许多类,如 Hashtable 或者 Vector都不能满足这种严格的定义。

    ③. 有条件的线程安全 -- 有条件的线程安全类对于单独的操作可以是线程安全的,但是某些操作序列可能需要外部同步。条件线程安全的最常见的例子是遍历由 Hashtable 或者 Vector 或者返回的迭代器,为了保证其他线程不会在遍历的时候改变集合,进行迭代的线程应该确保它是独占性地访问集合以实现遍历的完整性。通常,独占性的访问是由对锁的同步保证的,并且类的文档应该说明是哪个锁(通常是对象的内部监视器(intrinsic monitor))。如果对一个有条件线程安全类进行记录,那么应该不仅要记录它是有条件线程安全的,而且还要记录必须防止哪些操作序列的并发访问。用户可以合理地假设其他操作序列不需要任何额外的同步。

    ④. 线程兼容 -- 线程兼容类不是线程安全的,但是可以通过正确使用同步而在并发环境中安全地使用。这可能意味着用一个 synchronized 块包围每一个方法调用,或者创建一个包装器对象,其中每一个方法都是同步的(就像 Collections.synchronizedList() 一样)。也可能意味着用 synchronized 块包围某些操作序列。为了最大程度地利用线程兼容类,如果所有调用都使用同一个块,那么就不应该要求调用者对该块同步。这样做会使线程兼容的对象作为变量实例包含在其他线程安全的对象中,从而可以利用其所有者对象的同步。许多常见的类是线程兼容的,如集合类 ArrayList 和 HashMap 、 java.text.SimpleDateFormat 、或者 JDBC 类 Connection 和 ResultSet

    ⑤. 线程对立 -- 线程对立类是那些不管是否调用了外部同步都不能在并发使用时安全地呈现的类。线程对立很少见,当类修改静态数据,而静态数据会影响在其他线程中执行的其他类的行为,这时通常会出现线程对立。线程对立类的一个例子是调用 System.setOut() 的类。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值