社区微信群开通啦,扫一扫抢先加入社区官方微信群
社区微信群
==
和 equals
==
比较的比较的是两个变量的值是否相等,对于引用型变量表示的是两个变量在堆中存储的地址是否相同,即栈中存储的引用地址是否相同。equals
方法==
的结果一样。Stirng s1="abc";
String s2=new String("abc");
s1 == s2 ; //false
s1.equals(s2); //true
基本数据类型没有equals
方法,byte,short,char,int,long,float,double,boolean
Object本身的equals方法也是用双等号(==)进行比较的,所以比较后的结果跟双等号(==)的结果相同,而String,Integer等类重写了equals方法,所以==
比较的结果不一样。
当使用String str2="abc"
时,程序会首先在字符串池中寻找相同的对象,如果已经有一个str1
创建, 则str2
和str1
的引用相等。而只有引号内包含文本创建对象才会将创建的对象放入到字符串池,而String str=new String("abc")
创建的对象是不放在字符串池中的。
String str1 = "ab";
String str2 = "cd";
String str3 = str1+str2; //这种创建方式是不放入字符串池的,和str4的原理一样
String str4 = str1+"cd"; //这种创建方式是不放入字符串池的。这里实际上创建一个StringBulider对象
然后调用StringBulider.append()方法,然后调用StringBulider.toString()方法。所以和和str7不相等(==)
String str5 = "ab"+str2; //这种创建方式是不放入字符串池的.
String str6 = "ab"+"cd"; //这种创建方式是放入字符串池的,这种情况实际上是创建了1个对象,即"abcd"1个对象,编译阶段会直接合成一个字符串。
String str7 = "abcd";
//str6 与str7都指向了常量池中同一引用地址。
System.out.println(str6==str7); //返回ture
final String fstr="ab";
final String fstr2 = "cd";
String str8=fstr+"cd";
Strins str9 = fstr + fstr2; //此时str9 == str7 也返回true
对于final字段,编译期直接进行了常量替换,而对于非final字段则是在运行期进行赋值处理的,所以str8和
str7 指向常量池中相同位置,所以相等(==)
final修饰的变量,只有定义时指定了初始值,并且被赋值时,只是简单的算术运算和字符串连接,没有访问普通变量,没有调用方法,此时该变量在编译期间就确定值了,类似于宏变量,使用时直接进行常量替换。
下面这种情况就不相等
final String str ; //定义时未赋值,下面在赋值
str = "ab";
String str10 = str + "cd"; //str10 == str7 返回false;str此时不会进行常量替换
String s1="abc"; String ss="abc" //因为java常量池中不会存在两个相同的字符串,所以这两个相同
Sring s2=new String("abc");//或者Sring s2=new String(s1);
此时,s1,s2的堆中的值相等,但栈中的引用值不同,所以(s1==s2)==>false,(s1.equals(s2))==>true
使用new关键字一定会产生一个对象abc,和上面的"abc"不同,同时这个对象存储在堆中。所以上面产生两个对象
一个是存储在栈中的s2,一个是保存在堆中的abc,但是java不存在两个完全一样的字符串对象,所以对象abc
是引用常量字符串中的"abc",即s2-->abc对象--->常量池中的abc(见下方解释)
String s1=new String("abc");
String s2=new String("abc");
//new 出来的两个对象,==比较的是栈中存储的引用地址,肯定不等。
(s1==s2)==>false,(s1.equals(s2))==>true
String s1="abc";
String s2="abc";
(s1==s2)==>true,(s1.equals(s2))==>true
System.out.println("abc".hashCode());
System.out.println("abc".hashCode());
System.out.println(new String("abc").hashCode());
System.out.println(new String("abc").hashCode());
//以上四个的hashcode都相等
String s1 = "abc";
String s2 = new String("abc");
s2 = s2.intern(); 此时`s1==s2`返回true,
`intern()`会先检查字符串池,如果存在,就返回池里的字符串的引用;如果不存在,该方法会 把"abc"添加到字符串池中,然后再返回它的引用。
当且仅当s1.equals(s2)返回true时, s1.intern()==s2.intern()才返回true
String str=new String("abc")
创建了两个对象,String的构造器:public String(String original) { //other code ... } ,我们正是使用new调用了String类的上面那个构造器方法创建了一个对象,并将它的引用赋值给了str变量。同时我们注意到,被调用的构造器方法接受的参数也是一个String对象,这个对象正是"abc"。一个在常量池中,一个在堆中。字符串常用方法
charAt()
----返回指定索引处的字符串concat()
--将一个字符串追加到另一个字符串的末尾equalseIgnoseCase()
判断两个字符串的相等性,忽略大小写length()
返回字符串中的字符个数replace()
用新字符代替指定的字符substring()
返回字符串的一部分toLowerCase()
将字符串中的大写字符转换成小写字符返回toString()
返回字符串的值toUpperCase()
将字符串中的小写字符转换成大写字符返回trim()
删除字符串前后的空格splite()
将字符串按照指定的规则拆分成字符串数组toCharArray()
字符串转换为一个字符数组indexOf()
返回字符串第一次出现的索引//字符串和字符数组互相转换
String str1 = "hello" ; // 定义字符串
char c[] = str1.toCharArray() ; // 将一个字符串变为字符数组
String str2 = new String(c) ; // 将全部的字符数组变为String
String str3 = new String(c,0,3) ; // 将部分字符数组变为String
valueOf 是静态方法,源码如下:
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
所以使用obj.toString()方法必须不为null,valueOf可以使用null,但返回的是字符串"null"
s1.compareTo(s2) 相等时返回 0
不想等是有两种情况 长度不同和对应索引处字符不同
1. 对应索引的值不同时,则返回此索引处的ascii码差值 即charAt(k) - charAt(k)
2. 长度不同时,则短字符是长字符的开头,返回长度差 即length() - length()
虽然编译器对+进行了优化,它是使用StringBuilder的append()方法来进行处理的,但是使用append后使用了
toString(),即 str+='b',等价于 str=new StringBuilder(str).append(b).toString(),所以变慢的原因是
new StringBuilder()和 toString()
源码中最后使用了return new String() ,所以变慢
最后返回的是本身,没有创建新的对象
null不是对象,""空字符串是对象,所以null没有空间,空字符串有空间
equals()用于比较对象,而null不是对象,所以null用等号=
所以当 str=null,下面比较是错误的
if(str.equals("")||str==null){} //null不能调用equals方法,所以会报空指针异常
正确的为if(str==null ||str.equals(""))
所以判断字符串为空,首先判断是否为null,所以高效的写法为
if(str==null || str.length()<=0){}
不为空
if(str1!=null || str.length()!=0){}
Collection接口是 Set 、List 和 Queue 接口的父接口,提供了多数集合常用的方法声明,Iterator是Collection的父接口
AbstractCollection实现了Collection接口, AbstractList和AbstractSet都继承于AbstractCollection,并且AbstractList实现了List接口
AbstractSet实现了Set接口。具体的List实现类继承于AbstractList,而Set的实现类则继承于AbstractSet
add(E e)
将指定对象添加到集合中,addAll(Collection c)
将指定集合的所有元素添加进来remove(Object o)
将指定的对象从集合中移除,移除成功返回true,不成功返回falsecontains(Object o)
查看该集合中是否包含指定的对象,包含返回true,不包含返回flasesize()
返回集合中存放的对象的个数。返回值为intclear()
移除该集合中的所有对象,清空该集合。iterator()
返回一个包含所有对象的iterator对象,用来循环遍历toArray()
返回一个包含所有对象的数组,类型是ObjecttoArray(T[] t)
返回一个包含所有对象的指定类型的数组isEmpty()
判断集合是否有元素将集合中不安全的集合包装成线程同步的集合
Collection c=Collections.synchronizedCollection(new ArrayList());
List list=Collections.synchronizedList(new ArrayList());
Set s=Collections.synchronizedSet(new HashSet());
Map m=Collections.synchronizedMap(new HashMap());
Iterator it=c.iterator();
while(it.hasNext()){
it.next();
it.remove();
}
void add(E e) //将元素插入List
boolean hasPrevious()
E previous() //返回前一个元素
List特有方法
List 一个有序的序列,元素可以重复,每一个元素都有一个索引,关心的是索引,与其他集合相比,List特有的就是和索引相关的一些方法:get(int index) 、
set(index,Object elem) 用elem替换指定位置元素,并返回旧元素
add(int index,Object o) 、 indexOf(Object o)第一次出现元素o的位置。
List判断对象相等的标准是 只要通过equals()方法返回true即可
继承于AbstractList,实现了List, RandomAccess, Cloneable, java.io.Serializable这些接口。
有个数组成员变量 Object[] elementData 用于保存ArrayList中的元素,当容量超过时,设置新的容量为 (原始容量*3)/2+1
E set() //设置位置上的元素,并且返回旧元素
void trimToSize() //将容量设置为实际元素的个数
void clear() //清空ArrayList,将全部的元素设为null
Object clone() //克隆ArrayList,内部是将ArrayList拷贝到一个新的ArrayList中,并返回
//源码
// 将e添加到ArrayList的指定位置
public void add(int index, E element) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(
"Index: "+index+", Size: "+size);
ensureCapacity(size+1); // Increments modCount!!
//将数组容量增大一位,然后等价于从index开始数组元素每个后移一位
System.arraycopy(elementData, index, elementData, index + 1,size - index);
elementData[index] = element; //然后设置index位置新值
size++;
}
ArrayList遍历
Iterator iter=list.iterator();
while(iter.hasNext()){
iter.next();
}
int size=list.size();
for(int i=0;i<size;++i){
list.get(i);
}
for(Interger i : list){
value=i;
}
ConcurrentModificationException异常 当遍历List的时候同时对其修改(增、删),就会抛出此异常
public class Test {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(2);
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
Integer integer = iterator.next();
if(integer==2)
list.remove(integer);
}
}
}
Itr类的成员变量
1. cursor //表示下一个要访问的元素的索引
2. lastRet //上一个访问元素的索引
3. exceptedModcount //表示对ArrayList修改的次数的期望值,初始值为modCount,
modCount是AbstactList的成员变量,ArrayList每次调用add()和remove()时,都会对modCount进行加1
//Iterator的hasNext()方法
public boolean hasNext(){
return cursor !=size(); //判断下一个访问元素的下标是否等于ArrayList大小,不等则还有元素访问。
}
//Iterator的next()
public E next() {
checkForComodification();
try {
E next = get(cursor); //通过cursor获得下一个要访问元素的下标
lastRet = cursor++; //把cursor赋值给lastRet,然后自增,初始时cursor=0,lastRet=-1,调用一次后cursor=1,lastRet=0
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification(); //此方法会判断modCount和exceptedModCount是否相等,不相等则抛出异常,
//此时modCount=0,exceptedModCount=0
throw new NoSuchElementException();
}
}
然后调用ArrayList的remove(),此方法会对modCount加1,size减1,对以上测试程序,对于iterator,exceptedModcount=0,cursor=1,lastRet=0
对于list ,modCount=1,size=0。然后循环再调用hahNext(),此时cursor=1,size=0,所以不相等,继续执行。而再调用iterator的next,会调用
checkForComodification(),此时不相等,就抛出异常了。即调用list.remove()会导致modCount与exceptedModCount不一致。
而迭代器同样有remove()方法,虽然此方法内部调用的list的remove,但是多了一个exceptedModCount=modCount,所以不会报错。所以迭代器中删除元素
需要调用iter.remove()。
fail-fast原理
上述即使使用迭代器的remove()方法,在多线程中,当一个线程遍历,另一线程修改时,也会导致exceptedModCount和modCount不一致,抛出异常。
解决方法为加锁,或者使用CopyOnWriteArrayList
当多个线程对同一个集合进行操作的时候,某线程访问集合的过程中,该集合的内容被其他线程所改变即其它线程通过add、remove、clear等方法,改变了modCount的值;这时,就会抛出ConcurrentModificationException异常,产生fail-fast事件
ArrayList 类似于顺序表,适合随机存取,set和get;而 LinkedList 类似于链表,适合插入和删除,add和remove,只需移动指针即可。
LinkedList随机访问时,必须从首节点(或尾节点,索引小于列表长度一半时从队首开始遍历,反之从队尾)
LinkedList 继承于AbstractSequentialList类,AbstractSequentialList继承与AbstractList。实现了List,Deque等接口
AbstractSequentialList 实现了随机访问List的一些方法。get(int index)、set(int index,E element)、add(int index,E element)
和remove(int index)
LinkedList本质是双向循环链表 包含两个成员变量 header和size
header是双向链表的表头,它是双向链表节点所对应的类Entry的实例。Entry是双向循环链表数据结构,Entry包含成员变量 previous,next,element。
previous是当前节点的上一个节点,next是当前节点的下一个节点,element是当前节点所包含的值。
List遍历
使用foreach结构的类对象必须实现了Iterable接口
Vector线程安全,因为都是同步方法
Set关心唯一性,它不允许重复。
HashSet 当不希望集合中有重复值,并且不关心元素之间的顺序时可以使用此类。(内部是hashmap)
HashSet不是线程同步的。集合元素可以是null。HashSet判断两个元素相等的标准是equals()相等,并且
hashcode()返回值也相等。这两个有一个不相等,即是不重复。
如果两个对象的hashCode()返回值相等,但是equals()返回false,因为两个对象的hashcode值相同,HashSet将试图将他们保存在同一个位置,只能用链式结构保存对象,但是HashSet是根据hashcode定位元素,这样的话就会导致性能下降
HashSet内部有一个HashMap的成员变量,所有操作都是基于这个HashMap操作的。HashSet存储的实际是HashMap的key,所以不允许有重复元素,元素可以为null。
HashSet内部使用一个静态常量Object类变量PRESENT,作为HashMap的value,所以HashSet中存储的key的value值都为PRESENT
add(E e)方法
public boolean add(E e){
return map.put(e,PRESENT) == null ;
}
添加成功返回true,否则为false
因为HashMap的put方法时,若已存在key,会用新value替换旧的value,但是key不会改变,并返回旧value,此时HashSet的add()方法便返回FALSE
而没有该key时,便添加该key,并返回null,而此时的add()返回true,添加成功
contains()方法
public boolean contains(Object o) {
return map.containsKey(o); //调用HashMap的containsKey
}
遍历方法
1. 迭代器遍历
for(Iterator iter=set.iterator();iter.hasNext();){
iter.next();
}
2.toArray()转换为数组遍历
String[] strArr=(String[])set.toArray(new String[0]);
for(String str:strArr){
syso(str);
}
LinkedHashset 当不希望集合中有重复值,并且希望按照元素的插入顺序,有序进行迭代遍历时可采用此类。
TreeSet 使用树结构实现(红黑树)当不希望集合中有重复值,并且希望按照元素的自然顺序进行排序时可以采用此类。(自然顺序意思是某种和插入顺序无关,而是和元素本身的内容和特质有关的排序方式,譬如“abc”排在“abd”前面。)TreeSet判断对象相等标准是 两个对象通过compareTo(Object obj)方法比较是否返回0(内部是TreeMap)
Queue接口继承Collection接口,用于保存将要执行的任务列表。一种队列则是双端队列,支持在头、尾两端插入和移除元素,主要包括:ArrayDeque、LinkedBlockingDeque、LinkedList。另一种是阻塞式队列,队列满了以后再插入元素则会抛出异常,主要包括ArrayBlockQueue、PriorityBlockingQueue、LinkedBlockingQueue。
LinkedList 同样实现了Queue接口,可以实现先进先出的队列。
PriorityQueue 用来创建自然排序的优先级队列。
Deque接口是双端队列接口,继承自Queue接口。可以再两头插入和删除,所以相比Quene多了一些在两端操作的方法。比如addFirst(),addLast(),peekLast(),pollLast()等等
LinkedList 同样实现了Deque的接口
Map是一个键值对的集合。也就是说,一个映射不能包含重复的键,每个键最多映射到一个值。关心的是唯一的标识符。他将唯一的键映射到某个元素。当然键和值都是对象。键不能重复,值可以重复。
HashMap,TreeMap继承于AbstractMap,AbstractMap实现了Map接口,Hashtable虽然继承于Dictionary,但实现了Map接口
HashMap 当需要键值对表示,又不关心顺序时可采用HashMap,HashMap中元素的排列顺序不固定,HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行)。
Hashtable 注意Hashtable中的t是小写的,它是HashMap的线程安全版本,现在已经很少使用。
LinkedHashMap 是HashMap的子类,定义了当前对象的上一个和下一个引用,在hash表基础上形成双向循环链表,当需要键值对,并且关心插入顺序时可采用它。
LinkeedHashMap默认采用的按照put()顺序排序,新添加元素都会添加指链表末尾。
public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder)此构造方法可以设置按照访问顺序排序,即最近访问的会
从原位置删除,添加至链表末尾。所以第一个节点是最少访问的节点。
LinkedHashMap采用双向循环链表将所有Entry连接起来,这样遍历时,就不用循环table数组,只要遍历循环链表即可。
1.添加元素
LinkedHashMap并没有重写HashMap的put方法,而是重写了若key存在时调用的recordAccess方法,和key不存在直接插入的addEntry()和createEntry()方法。当按照访问顺序排序时,这几个重写方法会使最近访问的元素移至链表末尾。所以就算不使用get(),再次put()已经put()过的元素,也会使顺序改变。并且如果实现LRU算法时,要重写removeEldestEntry()方法,该方法在addEntry()中被调用,意味着超过一定大小时,就将最近未访问的节点删除掉,即第一个节点
2. 访问元素
LinkedHashMap重写了get()方法,调用该方法时,如果是按照访问顺序排序,也会调用recordAccess()方法,也会将元素移至链表末尾,改变顺序。
3. LinkedHashMap实现LRU
首先,当accessOrder为true时,才会开启按访问顺序排序的模式,才能用来实现LRU算法。我们可以看到,无论是put方法还是get方法,都会导致目标Entry成为最近访问的Entry,因此便把该Entry加入到了双向链表的末尾(get方法通过调用recordAccess方法来实现,put方法在覆盖已有key的情况下,也是通过调用recordAccess方法来实现,在插入新的Entry时,则是通过createEntry中的addBefore方法来实现),这样便把最近使用了的Entry放入到了双向链表的后面,多次操作后,双向链表前面的Entry便是最近没有使用的,这样当节点个数满的时候,删除的最前面的Entry(head后面的那个Entry)便是最近最少使用的Entry。
TreeMap 当需要键值对,并关心元素的自然排序时可采用它,继承于AbstractMap,且实现了NavigableMap接口因此,TreeMap中的内容是“有序的键值对。
在Map 中插入、删除和定位元素,HashMap是最好的选择。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。TreeMap的key必须实现Comparable接口,并且key是同一类的对象
Map接口的常用方法如下表所示:
put(K key, V value)
向集合中添加指定的键值对,若已存在则覆盖,并返回旧值,若键值之前不存在,则返回nullputAll(Map t)
把一个Map中的所有键值对添加到该集合containsKey(Object key)
如果包含该键,则返回truecontainsValue(Object value)
如果包含该值,则返回true,通过判断equals()返回trueget(Object key)
根据键,返回相应的值对象keySet()
将该集合中的所有键以Set集合形式返回values()
将该集合中所有的值以Collection形式返回remove(Object key)
如果存在指定的键,则移除该键值对,返回键所对应的值,如果不存在则返回nullclear()
移除Map中的所有键值对,或者说就是清空集合isEmpty()
查看Map中是否存在键值对size()
查看集合中包含键值对的个数,返回int类型Set entrySet()
返回键值对视图
entrySet
返回key-value的Set<Map.Entry<k,v>>集合keySet
返回key的Set集合values
返回value的Collection集合
//成员变量
1. table 一个Entry[]数组,Entry是一个单向链表,哈希表的key-value对都存储在Entry数组中
2. size HashMap的大小,保存key-value键值对的数量
3. threshold 用于判断是否需要调整HashMap的容量。threshold的值="容量*加载因子",当HashMap中存储数据的数量达到threshold时,就需要将HashMap的容量加倍。
4. loadFactor就是加载因子。
5. modCount是用来实现fail-fast机制的。
构造函数
HashMap有4个默认构造函数,此时会初始化table数组
1.
// 默认构造函数。
public HashMap() {
}
2.
// 指定“容量大小”和“加载因子”的构造函数
public HashMap(int initialCapacity, float loadFactor) {
//最大容量为2^30,超过这个容量,将被这个最大值替换。并且容量必须是2的倍数,就算不是,也会扩容至2的倍数
//比如设置初始值为17,会扩容32;初始值为15,会扩容16
3. public HashMap(Map<? extends K, ? extends V> m) {
// 使用迭代法,将形参的元素加入到HashMap中
for (Iterator<? extends Map.Entry<? extends K, ? extends V>> i = m.entrySet().iterator(); i.hasNext(); ) {
Map.Entry<? extends K, ? extends V> e = i.next();
putForCreate(e.getKey(), e.getValue());
}
}
put(key,value)方法
1. 若key为null,则调用putForNullkey(value),遍历table,若存在key为null,则用 新value 替换 旧value,返回旧value。否则key为null的hash
值为0,存放再table[0]中,返回
2. 若key不为null,则计算key的hash值,通过hash值得到key在table中的索引
3. 遍历此索引下table[index]的链表,如果发现此链表中存在 键值的hash值和key的hash值相等,并且 key值相等(==比较相等,即栈地址相等) 或者key.equals(k)返回true,
即已存在key对应的值,则用value替换旧value,并返回旧value,保证key的唯一性
4. 若不存在key对应的值,在插入之前调用addEntry()先判断HashMap中size大小,如果size大于threshold,则扩容table.length*2两倍。
然后调用createEntry(),新建一个key-value节点,按照头插法插入链表。然后返回null
put()时涉及到的问题 hash算法 扩容
1. 先调用hash()方法,此方法根据key的hashcode进行二次hash,防止hash冲突
2. 根据hash()得到的hash值,然后调用indexFor()计算在table中的索引。
static int indexFor(int h, int length) {
return h & (length-1);
}
此处的 h & (length-1) 等价于 h % length,但是 & 比 % 更高效。因为length总是等于2^n。
因为偶数的最低位为0,奇数的最低位为1。假设length不是偶数,假设为15,length-1=14;这样在和14进行&运算的时候,最低位永远是0,那么0001、0011、0101、0111、1001、1011、1101、1111位置处是不可能存储数据的,空间减少,进一步增加碰撞几率,这样就会导致查询速度慢
而当lenth为偶数,假设为16,length-1=15=1111; 2^n-1 得到的数二进制低位全为1, 在进行&时,得到的值总是和原来的hash值相同,这样的话只有hash
值相同的,才会放入同一个链表中,不同的hash值发生碰撞的几率较小,提高查询效率。
所以对于任意对象,只要它的hashCode()值相等,则hash()方法计算出来的hash码值也相等,而在计算索引时,hash值相等的会存入同一个链表。
所以当两个对象(key)的hashCode()值相等,它们就会被存入同一个链表中。
1.8以下版本,1.8加入了红黑树
1. 调用resize(newCapacity),传入新的容量,然后会初始化一个新的大Entry数组,Entry[] newTable = new Entry[newCapacity];
2. 调用transfer(newTable)方法,传入新数组。遍历旧的table数组,加入新的数组中
for (int j = 0; j < src.length; j++) { //遍历旧的Entry数组
Entry<K,V> e = src[j]; //取得旧Entry数组的每个元素
if (e != null) {
src[j] = null;//释放旧Entry数组的对象引用(for循环后,旧的Entry数组不再引用任何对象)
do {
Entry<K,V> next = e.next;
int i = indexFor(e.hash, newCapacity); //!!重新计算每个元素在数组中的位置
e.next = newTable[i]; //头插法插入新的链表
newTable[i] = e; //将元素放在数组上
e = next; //访问下一个Entry链上的元素
} while (e != null);
}
}
因为重新放入新数组中也是使用头插法,所以之前顺序为s1->s2->s3,扩容之后就为s3->s2->s1
3. 再将table指向新的newTable,修改阈值
//扩容的过程中,在多线程中链表出现循环链,可能造成死循环,详见印象笔记
get(key)方法
1. 若key为null,调用getForNullKey()方法,在table[0]处查找,找到则返回value,否则返回null
2. 若key不为null,则调用hash(key.hashcode())得到key的hash码,然后调用indexFor(),计算在table中的索引,然后遍历索引下的单链表,
若有元素和key的hash值相同,并且key值相等或者key.equals(k)返回true,则找到,返回value,否则返回null
remove(key) 删除键值为key的元素
1. 调用removeEntryForKey(key),若key为null,则key的hash值为0,否则调用hash()计算hash码,然后调用indexFor()计算索引,遍历该索引下的
链表。若为第一个元素,则table[i]指向下一个元素,否则就是执行单链表的删除,并返回该Entry对象。
2. 若该Entry对象为null,则未找到该元素,返回null,否则返回删除的value
使用自定义对象作为HashMap的key,一定要重写hashCode()和equals(),否则该key在外部被改变,hashCode()即被改变,再get()会返回null
1. 重写hashCode()是为了对同一个key,能得到相同的hashCode(),就可以定位到相应的key上
2. 重写equals()是为了向HashMap表明当前对象和key上所保存的对象是相等的,这样才真正地获得了这个key所对应的这个键值对。
containsKey(key)
1. public boolean containsKey(Object key) {
return getEntry(key) != null;
}
2. 调用getEntry(),getEntry()作用就是返回返回“键为key”的键值对。HashMap将“key为null”的元素存储在table[0]位置,“key不为null”的则调用hash()计算哈希值,然后计算索引,如果存在key对应的元素,则返回该Entry对象,否则返回null
containsValue(value)
1. 若value为null,则调用containsNullValue(),containsNullValue()两层循环每个索引下的链表,判断是否有value=null的,若有则返回true,否则返回null。
2. 若value不为null,也是双重循环遍历,判断value.equals(e.value),若有则返回true,否则返回null。
Set<Map.Entry<k,v>> entryset values() keySet()三者类似
1. 内部有一个实现Iterator接口的类HashIterator(),当我们通过entrySet()获取到的Iterator的next()方法去遍历HashMap时,实际上调用的是 此类的nextEntry()方法。而nextEntry()的实现方式,先遍历Entry(根据Entry在table中的序号,从小到大的遍历);然后对每个Entry(即每个单向链表)逐个遍历
Map遍历
Map遍历有总计四种方式,方法有两种:(Entry是Map的静态内部类)
HashMap和Hashtbale区别
集合中判断两个对象相等
Comparator位于包java.util下,而Comparable位于包java.lang下
Comparator的重写方法为public int compare(T1,T2){}; Comparable的重写方法为 public int compareTo(Object o){}
若要对一个类或者集合排序,就要实现Comparable接口,重写compareTo()方法,该类就成为可以比较的类。或者在外部实现Comparator接口,重写compare
方法,这样就不用修改原类的代码,就可以比较了。
如 String、Integer 自己就可以完成比较大小操作,已经实现了Comparable接口
当调用数组排序 Arrays.sort(Object[] arr)时,默认调用的就是compareTo()方法,所以该类必须实现Comparable接口,而
Arrays.sort(T [],Comparator)。就必须传入一个实现Comparator接口的对象
同样的Collections.sort(List<T> ) ,List中的元素必须实现Comparable接口;Collections.sort(List<T>,Comparator)就必须传入一个实现Comparator接口的对象,然后即可对list进行排序
Map<String,Interger> map = new HashMap<>();
List<Map.entry<String,Interger>> li = new ArrayList<>(map.entrySet());
Collections.sort(l, new Comparator<Map.Entry<String, Integer>>() {
public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
return (o2.getValue() - o1.getValue());
}
});
srcArray - 源数组。
srcPos - 源数组中的起始位置。
destArray - 目标数组。
destPos - 目标数据中的起始位置。
length - 要复制的数组元素的数量。
int []a={1,2,3,4,5};
int [] b=Arrays.copyOf(a,a.length);
如果第二个变量大于源数组的长度,则超出值默认为0
Arrays.asList() 返回一个List,但是该List不支持add和remove操作,其内部返回一个ArrayList对象
但是此ArrayList并不是 java.util.ArrayList, 而是Arrays的内部类。
Arrays.asList()最好传递的是引用类型,并且使用此方法后,数组就和链表链接在一起,修改其中
任何一个,另外一个也会变化。
Stack<Interger> sta=new Stack<Interger>();
1. public boolean empty() //栈是否为空,因为栈实现了Collection接口,所以可用isEmpty()
2. public E pop() //出栈
3. public E push(E item) //进栈
4. public E peek() //查看栈顶元素,但不移除
5. int search(Object o ) //返回对象在栈中的位置,以1为基数
1. boolean matches(reger) //验证字符串是否匹配
str.matches("/w+");
2. String replaceAll(regex,replacement) //用replacement替换全部匹配的字符,replace没有正则参数
//去掉前后空格方法一
String regex = "^\[(.*)\]$";
String s1 = str.replaceAll(regex, "$1");
//方法二,注:replace方法无正则匹配
String regex = "^\[|\]$";
String s1 = str.replaceAll(regex, "");
3. String[] split() //根据正则才拆分字符串,结果为一个字符串数组
String str = "asfasf.sdfsaf.sdfsdfas.asdfasfdasfd.wrqwrwqer.asfsafasf.safgfdgdsg";
String[] strs = str.split("\.");
for (String s : strs){
System.out.println(s);
}
Pattern主要方法
boolean matches = Pattern.matches(pattern, text); //静态方法
String patternString = "sep";
Pattern pattern = Pattern.compile(patternString);
String[] split = pattern.split(text);
System.out.println("split.length = " + split.length);
for(String element : split){
System.out.println("element = " + element);
}
Mathcher用法
String text = "This order was placed for QT3000! OK?";
String pattern = "(.*)(\d+)(.*)";
// 创建 Pattern 对象
Pattern pat = Pattern.compile(pattern);
// 现在创建 matcher 对象
Matcher mat = pat.matcher(text);
1. boolean matches = mat.matches(); //检测结果是否匹配,等同于以上两个方法
2. 查找所有匹配到的结果
String text =
"This is the text which is to be searched " +
"for occurrences of the word 'is'.";
String patternString = "is";
Pattern pattern = Pattern.compile(patternString); //先创建Pattern对象
Matcher matcher = pattern.matcher(text); //用Pattern对象方法创建Matcher对象
int count = 0;
while(matcher.find()) {
count++;
System.out.println("found: " + count + " : " + matcher.start() + " - " + matcher.end());
}
find() 方法返回第一个是否匹配,之后每次调用 find() 都会返回下一个。
start()和end()返回每次匹配的字串在整个文本中的开始和结束位置。end返回的是字符串末尾的后一位,多一位
3. group获得分组结果
String text = "John writes about this, and John writes about that," +
" and John writes about everything. " ;
String patternString1 = "(John)";
Pattern pattern = Pattern.compile(patternString1);
Matcher matcher = pattern.matcher(text);
while(matcher.find()) {
System.out.println("found: " + matcher.group()); //因为只有一个分组
}
//多个分组
String text =
"John writes about this, and John Doe writes about that," +
" and John Wayne writes about everything."
;
String patternString1 = "(John) (.+?) ";
Pattern pattern = Pattern.compile(patternString1);
Matcher matcher = pattern.matcher(text);
while(matcher.find()) {
System.out.println("found: " + matcher.group(1) +
" " + matcher.group(2));
}
3. 替换,等价于String类的replaceAll()方法
replaceAll(replacement) 和 replaceFirst() 方法可以用于替换Matcher搜索字符串中的一部分。replaceAll() 方法替换全部匹配的正则表达式,replaceFirst() 只替换第一个匹配的。
在处理之前,Matcher 会先重置。所以这里的匹配表达式从文本开头开始计算。即后面继续替换,也是从头开始
String text =
"John writes about this, and John Doe writes about that," +
" and John Wayne writes about everything."
;
String patternString1 = "((John) (.+?)) ";
Pattern pattern = Pattern.compile(patternString1);
Matcher matcher = pattern.matcher(text);
String replaceAll = matcher.replaceAll("Joe Blocks ");
System.out.println("replaceAll = " + replaceAll);
String text="abcd";
String pattern="abc" ,此时使用以上三个方法,都为false,因为不是完全全部匹配
mat.find() 可以在随意位置匹配,但是如果之前已经使用过find(),需要用mat.reset()重置匹配
mat.group() 返回匹配到的子字符串,等价于 str.substring(mat.start(),mat.end())
while(m.find()) {
System.out.println(m.group()); //m.group()等价于m.group(0)返回整个匹配结果
System.out.print("start:"+m.start());
System.out.println(" end:"+m.end());
}
String text="BBC ABCDAB ABCDABCDABDE";
String pattern="(AB)CD";
while(mat.find()) {
System.out.println(mat.groupCount()); //返回1
System.out.println(mat.group(1));
System.out.println("found: " + count + " : " + mat.start() + "-" + mat.end());
}
mat.group()会返回 所有ABCD
mat.group(1)返回所有AB
java的异常父类为Throwable,继承的有两大类Error,Exception。Error程序无法处理和捕获,Exception又分为IOException和RuntimeException
IOException必须try..catch,而RuntimeException(运行时异常)包括常见的空指针,数组越界,类型转换异常,无需try..catch
异常捕获时先捕获小异常,再捕获大异常。
throws抛出异常时,当前方法不知如何处理,然后就交给上一级处理,如果main方法也不知如何处理,也throws抛出异常,然后交给JVM处理
throws用来抛出异常类,用在方法上 void test() throws IOException{}。throws可以抛出多个异常类,用,分开
使用throws抛出异常就无需try..catch。然后一个方法throws异常,则调用该方法时,要么放在try..catch中,要么放在另一个throws方法中。
当有子类方法重写父类的throws异常时,子类抛出的异常必须是父类抛出异常的子类或者相同
void test(){
throw new IOException();
}
如果throw抛出的异常,不是RuntimeException异常,则该throw语句必须要么处在try块里,显示捕获异常;要么放在一个带throws方法里,由调用者处理。
初始化 父类
的静态变量和静态代码块(也叫类初始化块)
初始化 子类
的静态变量和静态代码块
初始化 父类
的普通成员变量和普通代码块,再执行父类
的构造方法
初始化 子类
的普通成员变量和普通代码块,再执行子类
的构造方法
初始化块经过编译实际上会被还原到每个构造器中,且位于构造器代码的最前面
普通话初始化块负责对对象进行初始化,静态初始化块负责对类进行初始化
只有在第一次创建对象的时候才会创建静态初始化块,此后会一直存在,所以后面创建对象时无需再类初始化
而普通初始化块,每次创建对象都会执行。等同于构造方法
class Father{
int i=5;
// 静态变量
public static String p_StaticField = "父类--静态变量";
// 变量
public String p_Field = "父类--变量";
// 静态初始化块
static {
System.out.println(p_StaticField);
System.out.println("父类--静态初始化块");
}
// 初始化块
{
System.out.println(p_Field);
System.out.println("父类--初始化块");
}
//父类构造方法
Father(){
System.out.println("Father"+this.i);
test();
}
public void test(){
System.out.println("father test"+this.i);
}
}
public class Test extends Father {
int i=50;
// 静态变量
public static String staticField = "子类静态变量";
// 变量
public String field = "子类变量";
// 静态初始化块
static {
System.out.println(staticField);
System.out.println("子类静态初始化块");
}
// 初始化块
{
System.out.println(field);
System.out.println("初始化块");
}
//子类构造方法
Test() {
System.out.println("Son"+this.i);
}
public void test(){
System.out.println("son test"+this.i);
}
public static void main(String[] args) {
System.out.println("在我之前是啥");
Test tt=new Test();
}
}
//输出为
父类--静态变量
父类--静态初始化块
子类静态变量
子类静态初始化块
在我之前是啥
-----------
父类--变量
父类--初始化块
Father5
son test0 //此步为子类实例化父类,后面说明
子类变量
子类初始化块
子类调用new 时,在调用子类构造函数之前,会先调用父类的无参构造函数,并且每一个构造函数都会调用。
即隐式调用super()。若父类一个构造函数也没有,也会隐式调用。若父类只有含参的构造函数,没有无参的构造
函数。则子类必须显示的调用父类的含参构造函数,即super(参数),且super必须位于第一句。
子类通过this()调用子类本身的其他构造函数,但是不会和super()同时出现,因为调用构造函数必须是第一句。
在静态方法中,不能使用this,this指向对象,static修饰的变量或方法优先于对象而存在。
子类实例化父类对象 此对象调用的方法必须是父类拥有的,如果被子类重写,则调用的是子类的
Father fa=new Son();
当子类实例化父类对象时,该对象只能调用父类中定义的方法和属性。
因为属性不能重写,所以该对象访问的对象是父类的
因为静态方法也不能重写,所以访问的也是父类的,因为静态方法是类在加载时就被加载到内存中的方法,在整个运行过程中保持不变,因而不能重写。
非静态方法是在对象实例化时才单独申请内存空间,为每一个实例分配独立的运行内存,因而可以重写
因为非静态方法可以被重写(包括抽象方法),所以当调用方法时,原则是调用父类的方法,但是被子类重写了,就会调用子类的方法。产生多态或者动态绑定。 如果此方法没有被重写,则只会调用父类方法,不会调用子类独有方法。
重写原则 两同两小一大
1. 方法名相同,参数类型相同
2. 子类返回类型小于等于父类方法返回类型
3. 子类抛出异常小于等于父类抛出异常
4. 子类访问权限大于等于父类访问权限
class Father(){
int i=5;
Father(){
syso("father "+i);
test();
}
pulic void test(){
syso("father test"+this.i).
}
}
class Son(){
int i=50;
Son(){
syso("son "+this.i);
}
public void test(){
syso("Son test"+this.i);
}
pulic static void main(String[] args){
Father fa=new Son();
fa.i;
}
}
// father 5
son test 0
son 50
5
//先调用父类构造方法,父类构造方法中,调用了test(),但是子类重写了此方法,所以调用的为子类的方法(多态),但是此时还没调用子类的构造方法,所以子类中i的值为0。
fa.i 调用父类的属性。
A) 接口没有提供构造方法(初始化块),抽象类中可以有构造方法(初始化块),接口是特殊的抽象类,接口和抽象类都不能实例化
B) 接口中的方法默认使用public、abstract修饰,因此不要企图使用proteced或是private修饰符去修饰方法,同时由于接口中的方法都是abstract的所以方法不能有具体实现,即方法后不能有{},抽象类中可以有实现过的方法。接口不能定义静态方法,抽象类可以定义静态方法。
C) 接口中的属性默认使用public、static、final修饰,因此不要企图使用proteced或是private修饰符去修饰属性,同时由于接口中的属性是public static final的那么定义属性时必须初始化,否则会报错。抽象类即可以定义普通成员变量也可以定义静态常量
D) 接口可以多继承
接口与实现类之间也存在多态关系,即可以用接口声明变量,然后实现类指向接口变量
多态三个条件
1. 继承
2. 重写
3. 父类引用指向子类对象
Interger ina=2;
Interger inb=2;
syso(ina == inb) //true
Interger ina=128;
Interger inb=128;
syso(ina == inb) //false
class Singleton{
private static Singleton instance; //使用当前类变量来缓存是否已创建实例,已创建则直接返回
private Singleton(){} //构造器使用private修饰,隐藏该构造器
//提供一个静态方法,用于返回实例,并且保证只返回一个对象
public static Singleton getInstance(){
//如果instance为null,说明还没创建过该对象
//如果不为null,表明已经创建该对象,直接返回该对象
if(instance == null){
instance =new Singleton();
}
return instance;
}
}
public class SingletTest{
public static void main(){
//创建对象不能通过构造器,只能通过类静态方法创建
Singleton s1=Singleton.getInstance();
Singleton s2=Singleton.getInstance();
syso(s2 == s2); //会输出true,因为只会产生一个对象
}
}
//上面是懒汉式,多线程调用会产生多个实例。下面是饿汉式
class Singleton{
//static final修饰,类加载时就初始化
private static final Singleton instance=new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
//静态内部类 懒加载 因为静态内部类不会在类加载时加载
public class Singleton{
private Singleton(){}
private static class SingletonHolder{
private static Singleton instance = new Singleton();
}
public static getInstance(){
return SingletonHolder.instance;
}
}
satic 修饰的字段在类加载的过程中的准备阶段被初始化为0或null或默认值,而后在初始化阶段(调用<clinit>才会被设定值,如果没有设定值,就是默认值)
final修饰的字段在运行时被初始化
static final 修饰的字段没有默认值,必须显示的赋值。即编译期间放入常量池
public void test(final int a){
a=5; //错误,不能修改final形参
}
final int[] arr={1,2,3,4};
Arrays.sort(arr); //可以进行排序
arr[3]=5; //可以进行赋值
arr=null; //重新赋值,非法
fianl Person p=new Person();
p.setName(); //可以重新设置属性值
final String str1="abc" + 123;
final String str2="abc"+String.valueOf(123);
str2 调用了String类的方法,所以无法在编译时确定值,所以str1不等于str2
final String str1="abc" + 123;
final String str2="abc"+String.valueOf(123);
str2 调用了String类的方法,所以无法在编译时确定值,所以str1不等于str2
String s1="abcd";
String s2="ab" + "cd";
s1 == s2 //true
String s4="ab";
String s5="cd";
String s3=s4 + s5;
s3==s1 //false s3无法在编译时确定值,s4和s5只是普通变量,不回进行常亮替换,s4和s5加上final修饰符,s3即等于s1
class CacheImmutale{
private static int MAX_SIZE=10;
//使用数组缓存已有的实例
private static CacheImmutale[] cache =new CacheImmutale[MAX_SIZE];
private static int pos=0; //记录缓存实例在缓存中的位置,cache[pos-1]是最新缓存的实例
private final String name;
private CacheImmutale(Strin name){//构造方法私有
this.name=name;
}
public String getName(){
return name;
}
public static CacheImmutale valueOf(String name){
//遍历已缓存的对象
for(int i=0;i<MAX_SIZE;++i){
//如果已有实例,则直接返回
if(cache[i]!=null && cache[i].getName().equals(name)){
return cache[i];
}
}
if(pos==MAX_SIZE){ //如果缓存池已满
cache[0]=new CacheImmutale(name); //覆盖第一个对象
pos=1;
}else{
cache[pos++]=new CacheImmutale(name); //新创建的对象缓存起来
}
}
public boolean equals(Object obj){
if(this==obj){
return true;
}
if(obj!=null && obj.getClass() == CacheImmutale.class){
CacheImmutale ci=(CacheImmutale)obj;
return this.name.equals(ci.getName());
}
return false;
}
public int hashcode(){
return name.hashcode();
}
}
CacheImmutale c1=CacheImmutale.valueOf("hello");
CacheImmutale c2=CacheImmutale.valueOf("hello");
syso(c1 == c2); //true
Interger类就采用额上述相同的策略,如果采用new 创建Integer对象每次都返回新的对象,而采用valueOf()方法,则会缓存该对象
Interger i1=new Interger(6);
Interger i2=Interger.valueOf(6);
Interger i3=Interger.valueOf(6);
syso(i1 == i2) //false
syso(i2 == i3) //true
Interger i2=Interger.valueOf(128);
Interger i3=Interger.valueOf(128);
syso(i2 == i3) //false 由于Integer值缓存-128~127之间的值,所以超过此范围不缓存
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!