内存分配的方式不不一样
通过字面量创建字符串对象会分配到常量池中 若再次使用该字面量创建新字符串时候,不在创建新对象,而是从常量池中获取
通过new一个字符串对象会分配到堆内存中
常量池在Java用于保存在编译器已确定的,已编译的class文件中的一份数据.它包括了关于类,方法,接口等中的常量,也包括字符串常量,如String s = “Java” 这种声明方式; 当然也可扩充,执行器产生的常量也会放入常量池,故认为常量池是JVM的一块特殊的内存空间
3.做字符串拼接的时候可以使用String吗?不建议直接使用String做拼接,最好使用StringBuilder或者StringBuffer来做拼接
4.StringBuilder和StringBuffer有什么区别呢?共同之处:
1.都继承了AbstractStringBuilder这个抽象类,实现了CherSequence接口
2.其append方法都是super.append(str),调用了父类AbstractStringBuilder的append(String str)方法
3.初始容量都是16,扩容机制都是"旧容量*2+2"
4.底层都是char[]字符数组实现,且字符数组都是可变的,这点不同于String
不同之处
1.StringBuffer从JDK1.0就有了,StringBuilder是JDK5.0才出现
2.StringBuilder是非线程安全的,效率更高。StringBuffer是线程安全的,效率低一些,但安全。
3.只是StringBuffer 中的方法大都采用了 synchronized 关键字进行修饰,因此是线程安全的,而 StringBuilder 没有这个修饰,可以被认为是线程不安全的。
4.在单线程程序下,StringBuilder效率更快,因为它不需要加锁,不具备多线程安全而StringBuffer则每次都需要判断锁,效率相对更低。
不再抛出任何异常;
仅抛出部分异常;
抛出子类异常;
不可以抛出额外异常;
不可以抛出父类异常;
final 用于修饰类,变量,方法,修饰类表示该类不可以被继承,修饰方法表示该方法不可以被重写,修饰变量表示不可以重新赋值
finally一般作用在try-catch捕获异常代码块中,不论代码是否发生异常,finally中的代码一定会被执行,通常用来关闭一些资源时候使用
finalize 属于Object类中的一个方法,当对象被收回的时候,会调用此方法
相同部分:
List 与 Set 具有相似性,他们都是单列元素的集合,所以他们有一个功能共同的父接口,叫Collection
区别:
Set里边不允许有重复的元素,所谓重复,即不能有两个相等的对象.
假设Set集合中有了一个A对象,现在我要向Set集合中再存入一个B对象,但是B对象与A对象equals相等,则B对象存储不进去,所以,Set集合的add方法有一个boolean的返回值,当集合中没有某个元素,此时add方法可成功加入该元素,则返回true,当集合含有与某个元素equals相等的元素时,此时add方法无法加入该元素,则返回值结果为false.
Set取元素时候,没法说取第几个,只能以Iterator接口取得所有的元素,再逐一遍历各个元素.
List表示有先后顺序的集合, 注意, 不是那种按照年龄,按大小, 按价格之类的排序
当我们多次调用当我们多次调用 add(Obj e)方法时,每次加入的对象就像火车站买票有排队顺序一样,按先来后到的顺序排序。有时候,也可以插队,即调用 add(int index,Obj e)方法,就可以指定当前对象在集合中的存放位置.一个对象可以被反复存入List中,每调用一次add方法,这个对象就被插入进集合中一次,其实并不是把这个对象本身存储到集合中,而是在集合中用一个索引变量指向这个对象,当这个对象被add多次时候,即相当于集合中有多个索引指向了这个对象.
List除了可以以Iterator接口取得所有的元素,再逐一遍历各个元素之外,还可以调用get(index i)来明确的说明取第几个元素
总结:
List以特定次序来持有元素,可以有重复元素, Set无法拥有重复元素,内部排序
另外,再Set集合中, hashset集合比较两个对象是否相等,首先看Hashcode方法是否相等,然后看equals方法是否相等. new两个Student插入到HashSet中,看HashSet的size,实现hashcode和equals方法后再看size
泛型,在C++中被称为模板,就是一种抽象的编程方式,当我们定义类和方法的时候,可以用一种通用的方式进行定义,而不必写出具体的类,这些未知的东西会在真正使用的时候再确定
对于集合类来说,他们可以存放格中类型的元素,如果在存放之前,就能确定元素的类型,那么就可以更加直观.也让代码更简洁.示例代码如下:
public class Test4 {
public static void main(String[] args) {
List list=new ArrayList();//无泛型创建链表对象
list.add("a");
list.add("b");
Iterator it=list.iterator();//获取链表迭代器
while (it.hasNext()) {
Object obj=it.next();//获取元素,类型为Object
String s=(String) obj;
System.out.println(s);
}
Listlist2=new ArrayList<>();//String泛型链表对象
list2.add("c");
list2.add("d");
//用foreach进行遍历,类型转换为String
for (String str : list2) {
System.out.println(str);
}
}
}
上面代码中, List 没有使用泛型,他所存放的元素都是Object类型,在使用的时候还需要进行类型的强制转换
泛型链表对象list2,在创建的时候就提供了元素的类型,因此,在获取元素的时候,类型会自动转换,这也是集合使用泛型的最直接的好处.
说明: java的泛型是停留在编译层的,也就是说JVM在对待泛型数据的时候,依然会把他们看成是Object类型,只不过在使用这些元素的时候,JVM会自动帮助我们进行相应的类型转换
总结: 集合使用泛型之后,可以达到元素类型明确的目的,避免了手动类型转换的过程,同时,也让我们更加明确容器保存的是什么类型的数据
9.常用的List集合有哪些呢? 有什么区别?常用的List集合有ArrayList. linkedList. Voctor
ArrayList的一些特点及原理
排列有序,可重复,底层使用数组存储,读取速度快,增删慢,getter()和setter()方法快,线程不安全,当容量不够时候,ArrayList会扩容当前容量的一半
从源码可以看出,ArrayList本质就是数组,当采用无参的构造函数初始化的时候,最初容量是0,等到采用add()操作时,会将容量设为默认的容量10
通常情况下,数组列表的容量扩容是增加当前容量的一半,即扩容之后的容量是原容量的1.5倍,而且并不是可以无限扩容的,为了防止内存溢出,源码做了最大容量的限制,相关的新增删除的操作没有加锁或者synchronized关键字,因此线程不安全
linkedList的一些特点及原理
排列有序,可重复,底层使用的是双向循环链表数据结构存储.查询速度慢,增删快,add()和remove()方法快,线程不安全
linkedList内部定义了内部类Node作为双向链表的数据节点,因此双向链表的操作的特点就是linkedList操作的特点,并且相关的新增删除的操作没有加锁或者synchronized关键字,因此线程不安全
Vector的一些特点及其原理
排序有序,可重复,底层使用数组存储,读取速度快,增删慢,线程安全,sychronized关键字加在方法上,效率低,默认容量是10,当容量不够时候,Vector默认扩展一倍容量
Vector的内部和ArrayList一样也是使用数组存储,因此这两个类很多相似点不同的是:
- ArrayList在内部定义数组的时候使用了transient关键字,而 Vector没有使用该关键字,因此在对象这两个类定义的对象在序列化的时候是不同的
- ArrayList默认的扩容是当前容量的一半, 而Vector默认的扩容是当前容量的一倍
- ArrayList的相关操作是线程不去安全的, 而Vector在相关的操作方法前加了Synchronized关键字,因此是线程安全的。
队列是用于存储一组元素的数据结构,但是存取元素必须遵循先进先出的原则
栈也是用于存储一组元素,存取必须遵循先进后出的原则.
在JDK1.7 的时候是数组+链表
在JDK1.8 的时候是数组+链表+红黑树
当链表的长度大于8的时候,就会变为红黑树.
HashTable
- 底层数组+链表实现, 无论key还是value都不能为null, 线程安全, 实现线程安全的方式实在修改数据时候锁住了整个HashTable, 效率低, ConcurrentHashMap做了相关优化
- 初始size为11, 扩容: newsize = oldsize*2+1
- 计算index的方法: index= (hash & 0x7FFFFFFF) % tab.length
HashMap
- 底层数组+链表实现, 可以存储null键和null值, 线程不安全
- 初始size为16, 扩容: newsize = oldsize*2, size一定为2的n次幂
- 扩容针对整个Map.每次扩容时候,原来数组中的元素依次重新计算存放位置,并重新插入
- 插入元素后才判断该不该扩容,有可能无效扩容(插入后扩容,如果没有再次插入,就会产生无效扩容)
- 当Map中元素总数超过Entry数组的75%, 触发扩容操作,为了减少链表长度,元素分配更均匀
- 计算index方法: index = hash & (tab.length -1)
- HashMap的初始值还要考虑加载因子:
- 哈希冲突: 若干Key的哈希值按数组大小取模后,如果落在同一个数组下标上,将组成一条Entry链,对Key的查找需要遍历Entry链上的每个元素执行equals()比较。
- 加载因子 为了降低哈希冲突的概率,默认当HashMap中的键值对达到数组大小的75%时,即会触发扩容。因此,如果预估容量是100,即需要设定100/0.75=134的数组大小。
- 空间换时间 如果希望加快Key查找的时间,还可以进一步降低加载因子,加大初始大小,以降低哈希冲突的概率。



