复习Java第二天
Java常用API
Java API 是JDK所提供的使用类,这些类将底层的代码给封装起来了。Object类是
Java语言中的根类,它所描述的所有方法,子类都可以使用,所有类在创建对象的
时候,最终找的父类就是Object。在Object类中最常见的方法就是equals方法和
toString方法。equals方法用于比较两个对象是否相等,其实是在比较两个对象的
内存地址。在重写Object类中equals方法的时候一定要注意public boolean equals
(Object obj)的参数是Object类型。在调用对象的属性时一定要记得类型转换,在类
型转换的时候要进行类型判断。toString方法返回该对象的字符串表示。
1. String类
String类代表字符串,字符串的值在创建后不能更改。字符串本身不能改
变,但变量中记录的地址值是可以改变的,String类中有大量的重载构造方法。
String有两个兄弟是StringBuffer和StringBuilder,String赋值后不能修改
StringBuffer和StringBuilder可以修改,StringBuilder性能要高于StringBuffer
但是StringBuffer是线程安全的,而StringBuilder是非线程安全的。
2. Math
Math类是包含用于执行基本数学运算方法的数学工具类。其方法都是静态方法,并且一般都不会创建对象。
abs方法:结果都为正数。
ceil方法:结果为比参数值大的最小整数的double值。
floor方法:结果为比参数值小的最大整数的double值。
max方法:返回两个参数中最大的值。
min方法:返回两个参数中最小的值。
pow方法:返回第一个参数的第二个参数次幕的值。
round方法:返回参数值四舍五入的结果。
random方法:产生一个大于等于0.0且小于1.0的double小数。
3. Arrays
Arrays类一般用来操作数组(比如排序和搜索)的各种方法。如果指定数组引用为null,则访问此类中的方法都会抛出空指针异常NullPointerException。
4. Object
Object类是Java语言中的根类,即所有类的父类,它里面描述的方法都可以使用。在对象实例化的时候,最终找的父类就是Object。
如果一个类没有指定父类,则默认继承于Object。
Object类中包含了11个方法,最常用的两个方法是:
public static toString():返回该对象值的字符串,在自定义类中覆盖这个方法
public boolean equals(Object obj):判断两个对象是否相等,相等就返回true,不相等返回false。默认是通过比较两个对象的地址值判断两个对象是否相等。
可以重写equals方法:目的是为了直接通过比较两个对象的成员变量值来判断两个对象是否相同。
Java集合
说到集合就得说到数组,很多集合的底层原理都是依赖于数组的实现。数组一旦初始化后,长度就固定了,存储数据对象不能达到动态扩展,其次数组存储元素不方便对数组进行添加、修改、删除操作,而且数组可以存储重复元素。这个时候集合对作用显现出来了。集合分为Collection和Map两种体系。
1. Collection
java.util.Collection 是一个集合接口(集合类的一个顶级接口)。它提供了对集合
对象进行基本操作的通用接口方法。Collection接口在java类库中有很多具体的实
现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式,其
直接继承接口有List,Set与Queue 。常用的子接口有List和Set。
- List的实现类:ArrayList,Vecotr,LinKedList。
- Set的实现类:HashSet,TreeSet。
- boolean add(Object obj):添加一个元素。
- boolean addAll(Collection c):将集合c的全部元素添加原集合元素后返回ture。
- 添加功能永远返回true。
- void clear():移除所有元素。
- boolean remove(Object obj):移除一个元素。
- boolean removeAll(Collection c)移除一个集合的元素,只要有一个被移除就返回true,改变原集合,删除原集合中和c中相同的元素。
- 删除功能只有删除成功后才会返回true。
- boolean contain(object o)判断集合中是否包含指定的元素。
- boolean containsAll(Collection c)判断原集合中是否包含指定集合c的所有元素,有则true。
- boolean isEmpty()判断集合是否为空。
- Iterator iterator()迭代器,集合的专用方式,实现遍历的功能。
- Object next()获取当前元素,并移动到下一个位置。
- boolean hasNext()判断此位置是否有元素。
- int size()元素的个数。
- 数组和字符串中都是length()方法获取元素个数,集合中是size()方法
因为object包括集合、字符串、数组,所以其不能直接用length方法。- 交集功能boolean retainAll(Collection c)两个集合交集的元素给原集合,并判断原集合是否改变,改变则true,不变则false。
- 把集合转换为数组: Object [] toArray()
List集合
Collection集合的子类,有序的(存储和取出时顺序是一致的),可重复。
- void add(int index,Object obj):在指定下标位置插入数据(原索引处的元素往后)。
- Object get(int index):获取指定位置的元素。
- Object remove(int index)根据索引删除指定的元素,并返回删除的元素。
- Object set(int index,Object element)根据索引修改元素,返回被修改的元素。
List集合的子类:
- ArrayList:底层数据结构是数组,查询快增删慢,因为它可以通过下标直接遍历,因为数组的长度是固定的增删的话需要重新创建一个数组把之前的数组数据粘贴过来。线程不安全,效率高。
- Vector:底层数据结构是数组,查询快增删慢。线程安全,效率低。
- LinKedList:底层数据结构是链表,查询慢增删快,因为链表它是没有下标的不能通过foreach循环查询出来,线程不安全,效率高。
Set集合
无序(存储和取出顺序不一致,有可能会一致),但是元素唯一,不能重复
- HashSet:底层数据时哈希表。通过HashCode()和equals()两个方法来保证元素的唯一性,方法自动生成。子类LinKedHashSet底层数据结构是链表和哈希表,由链表保证元素的有序,由哈希表保证元素的唯一。
- TreeSet:底层数据结构是红黑二叉树。排序方式:自然排序,比较器排序。通过返回值是否为0来保证元素的唯一性。
- HashSet类:不保证set的迭代顺序。当存储对象时需要重写equals()和HashCode()方法
Map集合
Map是以键值对(key,value)的形式存储元素的。Map用于保存具有映射关系的数据,Map集合里保存着两组值,一组用于保存Map的key,另一组保存着Map的value,key不能重复。
Map和查字典类似,通过key找到value,通过页数找到对应的信息。比如人来说,key相当于身份证号,value保存着人的信息比如sex,age,name。
Map和Set的关系
可以说关系是很密切了,虽然Map中存放的时键值对,Set中存放的是单个对象,但如果把value看做key的附庸,key在哪里,value就在哪里,这样就可以像对待Set一样来对待Map了。事实上,Map提供了一个Entry内部类来封装key-value对,再计算Entry存储时则只考虑Entry封装的key。
如果把Map集合里的所有value放在一起来看,它们又类似于一个List,元素可以重复,每个元素可以根据索引来找,只是Map中的索引不再是整数值,而是以另一个对象作为索引。
- Map中的常用方法
void clear()
:删除该Map对象中所有键值对;boolean containsKey(Object key)
:查询Map中是否包含指定的key值;boolean containsValue(Object value)
:查询Map中是否包含一个或多个value;Set entrySet()
:返回map中包含的键值对所组成的Set集合,每个集合都是Map.Entry对象。Object get()
:返回指定key对应的value,如果不包含key则返回null;boolean isEmpty()
:查询该Map是否为空;Set keySet()
:返回Map中所有key组成的集合;Collection values()
:返回该Map里所有value组成的Collection。Object put(Object key,Object value)
:添加一个键值对,如果集合中的key重复,则覆盖原来的键值对;void putAll(Map m)
:将Map中的键值对复制到本Map中;Object remove(Object key)
:删除指定的key对应的键值对,并返回被删除键值对的value,如果不存在,则返回nullboolean remove(Object key,Object value)
:删除指定键值对,删除成功返回true;int size()
:返回该Map里的键值对个数;- 内部类Entry
- Map中包括一个内部类Entry,该类封装一个键值对,常用方法:
Object getKey()
:返回该Entry里包含的key值;Object getvalue()
:返回该Entry里包含的value值Object setValue(V value)
:设置该Entry里包含的value值,并设置新的value值。
Java8改进的HashMap和Hashtable实现类
HashMap和Hashtable的关系完全类似于ArrayList和Vector的关系。
区别:
- Hashtable是线性安全的,HashMap是线性不安全的,所以后者效率更高。
- Hashtable不允许使用null作为key和value,否则会引发异常,而HashM可以;
HashMap基本原理:
1. HashMap中几个重要的知识点:
- HashMap是无序且不安全的数据结构。
- HashMap 是以key-value对的形式存储的,key值是唯一的(可以为null),一个key只能对应着一个value,但是value是可以重复的。
- HashMap 如果再次添加相同的key值,它会覆盖key值所对应的内容,这也是与HashSet不同的一点,Set通过add添加相同的对象,不会再添加到Set中去。
- HashMap 提供了get方法,通过key值取对应的value值,但是HashSet只能通过迭代器Iterator来遍历数据,找对象。
2. JDK7与JDK8的HashMap区别:
- jdk8中添加了红黑树,当链表长度大于等于8的时候链表会变成红黑树。
- 链表新节点插入链表的顺序不同(jdk7是插入头结点,jdk8因为要把链表变为红黑树所以采用插入尾结点)。
- hash算法简化(jdk8)。
- resize的逻辑修改(jdk7会出现死循环,jdk8不会)。
3. HashMap的默认负载因子
/**
* The load factor used when none specified in constructor.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
*默认的负载因子是0.75f,也就是75% 负载因子的作用就是计算扩容阈值用,比如说使用
*无参构造方法创建的HashMap 对象,他初始长度默认是16 阈值 = 当前长度 * 0.75 就
*能算出阈值,当当前长度大于等于阈值的时候HashMap就会进行自动扩容
*/
面试的时候,面试官经常会问道一个问题:为什么HashMap的默认负载因子是0.75,而不是0.5或者是整数1呢?
两种答案:
- 阈值(threshold)=负载因子(loadFactor)x容量(capacity)根据HashM的扩容机制,他会保证容量(capacity)的值永远都是2的幕,为了保证负载因子x容量的结果是一个整数,这个值是0.75(4/3)比较合理,因为这个数和任何2的次幕乘积结果都是整数。
- 理论上来讲,负载因子越大,导致哈希冲突的概率也就越大,负载因子越小,费的空间也就越大,这是一个无法避免的利弊关系,所以通过一个简单的数学推理,可以测算出这个数值在0.75左右是比较合理的
4. HashMap的扩容机制
写数据之后会可能触发扩容,HashMap结构内,有一个记录当前数据量的字段,这个数
据量字段到达扩容阈值的话,它就会触发扩容的操作阈值(threshold) = 负载因子
(loadFactor) x 容量(capacity)当HashMap中table数组(也称为桶)长度 >= 阈值
(threshold)就会自动进行扩容。扩容的规则是这样的,因为table数组长度必须是
2的次方数,扩容其实每次都是按照上一次tableSize位运算得到的就是做一次左移1
位运算假设当前tableSize是16的话 16转为二进制再向左移一位就得到了32即16
<< 1 == 3 即扩容后的容量,也就是说扩容后的容量是当前容量的两倍,但记住
HashMap的扩容是采用当前容量向左位移一位(new tableSize = tableSize << 1)
得到的扩容后容量,而不是当前容量x2为什么计算扩容后容量要采用位移运算呢,
怎么不直接乘以2呢?因为cup毕竟它不支持乘法运算,所有的乘法运算它最终都是
再指令层面转化为了加法实现的,这样效率很低,如果用位运算的话对cup来说就非
常的简洁高效。
5. HashMap中散列表数组初始长度
/**
* The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/**
* HashMap中散列表数组初始长度为 16 (1 << 4)
* 创建HashMap的时候可以设置初始化容量和设置负载因子,
* 但HashMap会自动优化设置的初始化容量参数,确保初始化
* 容量始终为2的幂
*/
HashTable基本原理
Hashtable和HashMap类似,同样是基于哈希表实现的,同样每个元素是一个key-value对,但其内部只是通过单链表解决哈希冲突问题,而没有红黑树结构,当HashTable容量不足(超过了阀值)时,同样会进行扩容操作。Hashtable类声明如下:
public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable
它继承于Dictionary,实现了Map、Cloneable、 Serializable等接口。
Hashtable实现了Map接口,可以对它进行哈希表操作;实现了Cloneable接口,能被克隆;实现了Serializable接口,因此它支持序列化,能够通过序列化传输。
1. HashTable与HashMap的主要异同点
- 它们都是通过哈希表来实现的,而且都是通过链表来解决哈希冲突的,但是HashMap在链表达到一定长度之后,会将其转化为红黑树。
- 它们计算节点哈希值的方式不同,若key的hashcode为h,则HashMap通过h ^ (h >>> 16)来计算节点的哈希值,而HashTable则将h作为节点的哈希值。
- 它们计算节点对应数组索引下标的方式也不同,HashMap通过haseCode & (capacity - 1)是用来计算节点对应的数组下标,HashTable通过(hashCode & 0x7FFFFFFF) % capacity来计算节点对应的数组下标。hashCode & 0x7FFFFFFF的目的是为了将负的hash值转化为正值。
- HashTable的默认容量为11,而HashMap默认容量为16,Hashtable不要求底层数组的容量一定要为2的整数次幂,而HashMap则要求一定为2的整数次幂。但是,它们的默认负载因子都是0.75。
- Hashtable扩容时,会将容量变为原来的2倍加1,而HashMap扩容时,会将容量变为原来的2倍。
- Hashtable中key和value都不允许为null,而HashMap中key和value都允许为null(key只能有一个为null,而value则可以有多个为null)。若Hashtable中的key或者value为null,则程序运行时会抛出NullPointerException异常。
- HashTable中的大部分的方法都被synchronized修饰,所以HashTable是线程安全的,可以用于多线程环境中,而HashMap则不行。
总结
好啦,今天就到这里啦。小了解更详细的API的,可以去下载官方API中文官方文档哦 ,那里面包含java所有API。