一.Java中的String,StringBuilder,StringBuffer三者的区别
1.从创建的速度来看,是stringbuilder>stringBuffer>String.
String为字符串常量,而StringBuilder和StringBuffer均为字符串变量,即String对象一旦创建之后该对象是不可更改的,但后两者的对象是变量,是可以更改的。
1 String str="abc";
2 System.out.println(str);
3 str=str+"de";
4 System.out.println(str);
如果运行这段代码会发现先输出“abc”,然后又输出“abcde”。其实质为:JVM先创建一个abc的str,然后又创建一个str,把旧的和de相加,放到新的str里面,然后旧的str会被垃圾回收机制回收。而StringBuilder和StringBuffer的对象是变量,对变量进行操作就是直接对该对象进行更改,而不进行创建和回收的操作,所以速度要比String快很多。
2.线程安全性:StringBuilder是线程不安全的,而StringBuffer是线程安全的
stringbuffer多线程调用时,会有大量的同步关键字:synchronized。
二、java中的集合
1.map接口:HashMap和TreeMap
什么是map?在数组中我们是通过数组下标来对其内容索引的,而在Map中我们通过对象来对对象进行索引,用来索引的对象叫做key,其对应的对象叫做value。特点:HashMap是无序的集合而TreeMap则是有序的集合,但是都不是线程安全的。
影像HashMap 的速度因子是容量和负载因子
2.关于map集合
map里面的几个重要方法:put(k,v),将键值对放在map里面;remove(k)将对应的键值对删除;map.keyset(),是将map里面的K值全部存放到一个set集合里面。其中set集合是无序集合,将会在下面讲到。其中,map.entry(),这是一个类,这个类类似于map,一样是键值对。eg:Map.Entry<String,String> mapentry : map.entrySet 。
System.out.println("通过Map.entrySet使用iterator遍历key和value:");
Iterator<Map.Entry<String, String>> its = map.entrySet().iterator();//iterator为迭代器 简单的理解为,把entrySet里面的元素放在这个its里面。便于操作
while (its.hasNext()) {//判断是不是还存在不存在值
Map.Entry<String, String> entry = its.next();
System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
}
深入理解hashmap:
1).hashmap的数据结构是什么?实质上,他是一个数组+链表。其中K是数组,而V则是与K相关的链表。首先,Hashmap是实现的Map.entry,初始化容量是16,负载因子是0.75,如果超过了0.75则产生扩容(resize)机制。resize就是把之前的这个数组容量重新扩大,但是在扩大的过程中,需要对里面的数据进行重新排列。而根据里面数据的不同,所以创建的大小也是不同的,反正就是要控制在0.75以内。
2).为什么hashmap线程不安全?在进行put操作的时候,线程A.B都同时对同一个数组位置调用addEntry,两个线程会同时得到现在的头结点,然后A写入新的头结点之后,B也写入新的头结点,那B的写入操作就会覆盖A的写入操作造成A的写入操作丢失。在进行扩容的时候,因为是把头尾相反的指向,所以不安全。见:https://juejin.im/post/5a66a08d5188253dc3321da0。
为了在高并发的情况下正常操作,该使用什么?ConcurrentHashmap!为什么hashmap并不是线程安全的呢?因为他并没加上锁。而为什么大多数都是用的ConcurrentHashmap,而不是hashtable,因为ConcurrentHashmap是针对部分值,意思就是他只是锁了一部分表而不是一张表,而HashTable是锁的一张表。那么,在面对很多人同时访问的时候,hashtable就会让别的线程等待,浪费了大量的时间。而ConcurrentHashmap则采用的是分段锁,这样的话,就不会造成资源的浪费。
ConcurrentHashmap中的分段锁称为Segment,每一个Segment据拥有一个内部锁。但是在需要扫描一些整个map的方法,比如:size();containsValue()这类方法时,就只能是特殊的实现。虽然,ConcurrentHashmap被认为是线程安全的,但是在实际上,操作起来很有难度,原因是在于increase,这是操作的是一个ConcurrentHashmap对象,有可能出现覆盖。所以我们就采用CAS操作来避免这个问题。CAS:比较后交换。当我们要进行添加的时候,先得到位置V,然后经过操作得到V的旧值A,然后在将A改为B的过程中,如果V的地址不是A,就失败。
3).hashtable和hashmap 的区别?hashmap支持key为null,但是hashtable不支持key或者value为空;hashtable是线程安全的,而hashmap则不是;hashmap的扩容是*2,hashtable是*2+1;
关于Treemap的了解:
1).treemap是基于红黑树进行实现的,不具有调优选项,处于平衡状态。问:什么是红黑树? 红黑树又称红-黑二叉树,它首先是一颗二叉树,它具体二叉树所有的特性。同时红黑树更是一颗自平衡的排序二叉树。五大特性是?每个节点都只能是红色或者黑色;根节点是黑色;每个叶节点(NIL节点,空节点)是黑色的;如果一个结点是红的,则它两个子节点都是黑的。也就是说在一条路径上不能出现相邻的两个红色结点;从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。TreeMap的新增:从根节点开始检索-->与当前的节点进行比较,如果是大则在右边,小就在左边-->重复2找到合适的叶子节点-->找到正确的位置后插入-->对二叉树进行平衡(包括:左旋,右旋,着色着三个步骤)。TreeMap 的删除:找到被删除的节点D的子节点C,用C来代替D,然后直接删除C。C的规则:右分支最左边,左分支最右边。
TreeMap和HashMap的区别和联系?两者都不是线程安全的。其中TreeMap是基于二叉树实现,没有调优选项,总是处在平衡状态;HashMap是基于hash表的实现,可以对初始容量和负载因子进行调优;TreeMap是有序的,HashMap是无序的。总的来说,HashMap适用于在Map中插入,删除元素;而TreeMap则是适用于按照一定的顺序遍历K。
3.关于Collection集合:List和Set,Vector
List集合:有序性,元素都是有索引的,元素是可以重复的。
Set集合:无序性(存储顺序和取出顺序不一致),元素不能重复,所以必须保证元素的唯一性。
1).ArrayList和LinkedList和Vector的区别:从数据结构来说:ArrayList的数据结构是动态数组,LinkedList的数组结构是Link(链表),Vector的数据饥结构也是数组;从效率来说:ArrayList插入、删除数组很慢,LinkedList插入、删除数据快;ArrayList定位数组非常快,LinkedList定位慢,Vector和ArrayList差不多;从线程安全性来说:只有Vector是线程安全的;从扩容来说:Vector每次扩容是之前的2倍,而ArrayList只是1.5倍,两者的初始容量都是10。
2).使用场景:需要考虑到线程安全的-->Vector;需要快速插入的-->LinkedList;需要快读访问的-->ArrayList
3).HashSet和LinkedHashSet和TreeSet 的区别?TreeSet可以确保集合元素处于排序状态。TreeSet支持两种排序方式,自然排序 和定制排序,其中自然排序为默认的排序方式。LinkedHashSet集合同样是根据元素的hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序。这样使得元素看起来像是以插入顺序保存的,也就是说,当遍历该集合时候,LinkedHashSet将会以元素的添加顺序访问集合的元素。
fail-fast:机制是java集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。
例如:当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件
安全失败fail-safe:你在迭代的时候会去底层集合做一个拷贝,所以你在修改上层集合的时候是不会受影响的,不会抛出ConcurrentModification异常。
在java.util.concurrent包下的全是安全失败的。
Final在java中的作用
Final可以修饰类,修饰方法,修饰变量。
修饰的类叫最终类。该类不能被继承。
修饰的方法不能被重写。
修饰的变量叫常量,常量必须初始化,一旦初始化后,常量的值不能发生改变
重载与重写
重写:重写是子类对父类的允许访问方法的实现过程进行重新编写,返回值和形式参数都不会发生任何变化。即外壳不变,核心改变。
重写的几大规则:参数列表、返回类型必须和被重写的一致;声明为final和static的方法不能被重写;构造方法不能被重写;父类的成员方法只能被它的子类重写;
public static void main( String[] args )
{
Animals a=new Animals();
a.call();
Animals bAnimals=new Cat();
bAnimals.call();
}
public class Animals {
public void call() {
System.out.println("这是全部动物的叫声");
}
}
public class Cat extends Animals {
public void call() {
System.out.println("这是猫在叫");
}
}
或
public class Cat extends Animals {
@Override
public void call() {
super.call();
System.out.println("这是猫在叫");
}
}
重载:是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。每个重载的方法都必须有一个独一无二的参数类型列表。最常用的就是构造器的重载。
重载规则:被重载的必须改变参数列表;被重载的方法可以改变修饰符和返回类型;方法能在同一个类或者子类里面被重载;无法以返回值类型作为重载函数的区分标准。
public class App
{
public int overloadtest() {
return 0;
}
public int overloadtest(int a) {
return a ;
}
public String overloadtest(String aString) {
return aString;
}
public static void main( String[] args )
{
App app=new App();
System.out.println(app.overloadtest());
System.out.println(app.overloadtest("hello"));
System.out.println(app.overloadtest(6));
}
}