(6)Hashcode的作用。
关于计算机科学中Hash的理解,我觉的最好的说法是周爱民老师《大道至易》里面写的部分:
Hash是将世界上客观存在的无穷的对象映射到有限的计算机资源(如内存)上的一种算法。
我觉这个概念是精髓。
至于具体实现:
《Thinking in java》写的是:
Object使用hashCode()生成散列码,它默认是采用对象的地址来计算并生成的。
Object中源码中生成hashCode()的方法为:
public native int hashCode();
可见这是在底层用C++实现的一种算法。
Object的hashCode()方法具体是怎么实现的暂时就先不去追究了,我们先看下要怎么运用这个HashCode。
散列的价值在于,散列可以使得查询快速进行。为了证明这一点,《Thinking in java》一书给出了一个示例,这是示例展示出一个慢速的Map是怎么实现出来的。
package collection;
import java.util.List;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class SlowMap<K,V> extends AbstractMap<K, V> {
private List<K> keys = new ArrayList<K>();
private List<V> values = new ArrayList<V>();
public V put(K key, V value){
V oldValue = get(key);
if(!keys.contains(key)){
keys.add(key);
values.add(value);
}else{
values.set(keys.indexOf(key), value);
}
return oldValue;
}
public V get(Object key){
if(!keys.contains(key)){
return null;
}
return values.get(keys.indexOf(key));
}
public Set<Map.Entry<K, V>> entrySet(){
Set<Map.Entry<K, V>> set = new HashSet<Map.Entry<K,V>>();
Iterator<K> ki = keys.iterator();
Iterator<V> vi = values().iterator();
while(ki.hasNext()){
set.add(new MapEntry<K, V>(ki.next(), vi.next()));
}
return set;
}
}
由源代码可知,这个SlowMap的键和值都是放在ArrayList里面的,因此在查询一个Key对应的Value时。时间是O(n)的。
这个时候就可以引出散列了。
存储一组元素最快的数据结构是数组,所以可以用它来表示键的信息。在散列表的实现中,数组并不保存键的信息。键对象通过hashCode()方法生成HashCode,也就是哈希码,这个哈希码可以作为数组下标。因此当存取时,只要从同一个对象生成的Hashcode出发,就可以直接找到这个键对应的值对象在数组中的位置(bucket的位置)。
这时候,如果一个数组位置上有很多个值对象,就需要使用equals()方法了。也就是在比较hashCode后的一次复核。
(7) ArrayList、LinkedList、Vector的区别。
ArrayList是一个变长数组,可以使用.get(i)得到位置为i处的元素,也可以使用.size()方法得到此ArrayList的大小。
在没有泛型存在的时候,有以下代码。
package reference;
import java.util.ArrayList;
public class ArrayListCheck {
public static void main(String args[]){
ArrayList a = new ArrayList();
a.add(new Apple());
a.add(new Apple());
a.add(new Apple());
a.add(new Orange());
for(int i = 0; i < a.size(); i++){
System.out.println(((Apple) a.get(i)).id());
}
}
}
class Apple{
private static long counter;
private final long id = counter++;
public long id(){
return id;
}
}
class Orange{
}
代码运行后如下所示:
0
1
2
Exception in thread “main” java.lang.ClassCastException: reference.Orange cannot be cast to reference.Apple
at reference.ArrayListCheck.main(ArrayListCheck.java:13)
而使用了泛型的代码:
package reference;
import java.util.ArrayList;
public class ArrayListCheck {
public static void main(String args[]){
ArrayList<Apple> a = new ArrayList<Apple>();
a.add(new Apple());
a.add(new Apple());
a.add(new Apple());
a.add(new Orange()); //compilation error
for(int i = 0; i < a.size(); i++){
System.out.println(( a.get(i)).id());
}
}
}
class Apple{
private static long counter;
private final long id = counter++;
public long id(){
return id;
}
}
class Orange{
}
这段代码会在 a.add(new Orange())
这一行出现编译错误。
因此泛型的作用可以将运行时期出现的错误在编译时期就暴露出来。
以上算是题外话,就题目而言。
ArrayList可以被当成一个List的变长数组实现,因此对于基本的get()花费O(1)时间(常数时间),而对于插入操作花费O(N)时间。
LinkedList实现的是一个List的双向链表实现,因此对于插入操作只需要花费O(1)时间,而对于get()花费O(N)时间。
Vector是Java1.0/1.1中唯一可以自动扩展的序列,基本上可看做ArrayList,但是有一些不一样的地方:
Vector是同步访问的。
Vector包含了许多传统的方法,这些方法不属于集合框架。
一般来说,不应该用Vector,因为它是线程安全的,同步需要耗费大量的时间。因此不要用它。它后来也用了implements List<E>
,其实只是为了支持早期的代码。
(8)String、StringBuffer与StringBuilder的区别。
String是一个immutable的对象,也就是说一个对象一旦创建了,那么就不能改变了。
注意,final对象并不一定不能改变。比如:
package collectiontest;
public final class FinalTest {
int var = 1;
public static void main(String args[]){
FinalTest a = new FinalTest();
System.out.println(a.var);
a.var = 2;
System.out.println(a.var);
}
}
这个对象a就变了。
StringBuilder可以用以字符串拼接,在Java compiler里面,当字符串用+号连接起来时,编译器会调用StringBuilder来拼接。
如有以下代码:
package collectiontest;
public class StringTest {
public static void main(String args[]){
String s1 = "abc" ;
String s2 = "mongo";
String s3 = "aa";
String s = s1 + s2 + s3;
System.out.println(s);
}
}
编译后的汇编码:
// Compiled from StringTest.java (version 1.8 : 52.0, super bit)
public class collectiontest.StringTest {
// Method descriptor #6 ()V
// Stack: 1, Locals: 1
public StringTest();
0 aload_0 [this]
1 invokespecial java.lang.Object() [8]
4 return
Line numbers:
[pc: 0, line: 3]
Local variable table:
[pc: 0, pc: 5] local: this index: 0 type: collectiontest.StringTest
// Method descriptor #15 ([Ljava/lang/String;)V
// Stack: 3, Locals: 5
public static void main(java.lang.String[] args);
0 ldc <String "abc"> [16]
2 astore_1 [s1]
3 ldc <String "mongo"> [18]
5 astore_2 [s2]
6 ldc <String "aa"> [20]
8 astore_3 [s3]
9 new java.lang.StringBuilder [22]
12 dup
13 aload_1 [s1]
14 invokestatic java.lang.String.valueOf(java.lang.Object) : java.lang.String [24]
17 invokespecial java.lang.StringBuilder(java.lang.String) [30]
20 aload_2 [s2]
21 invokevirtual java.lang.StringBuilder.append(java.lang.String) : java.lang.StringBuilder [33]
24 aload_3 [s3]
25 invokevirtual java.lang.StringBuilder.append(java.lang.String) : java.lang.StringBuilder [33]
28 invokevirtual java.lang.StringBuilder.toString() : java.lang.String [37]
31 astore 4 [s]
33 getstatic java.lang.System.out : java.io.PrintStream [41]
36 aload 4 [s]
38 invokevirtual java.io.PrintStream.println(java.lang.String) : void [47]
41 return
Line numbers:
[pc: 0, line: 5]
[pc: 3, line: 6]
[pc: 6, line: 7]
[pc: 9, line: 8]
[pc: 33, line: 9]
[pc: 41, line: 10]
Local variable table:
[pc: 0, pc: 42] local: args index: 0 type: java.lang.String[]
[pc: 3, pc: 42] local: s1 index: 1 type: java.lang.String
[pc: 6, pc: 42] local: s2 index: 2 type: java.lang.String
[pc: 9, pc: 42] local: s3 index: 3 type: java.lang.String
[pc: 33, pc: 42] local: s index: 4 type: java.lang.String
}
可见new 了一个StringBuilder对象,调用了三次.append()方法,然后再toString()得到了最终的字符串。
以下是摘录自一个博客的String, StringBuilder, StringBuffer的区别和用法:
(1)基本原则:如果要操作少量的数据,用String ;单线程操作大量数据,用StringBuilder ;多线程操作大量数据,用StringBuffer。
(2)不要使用String类的”+”来进行频繁的拼接,因为那样的性能极差的,应该使用StringBuffer或StringBuilder类,这在Java的优化上是一条比较重要的原则。
(3)为了获得更好的性能,在构造 StringBuffer 或 StringBuilder 时应尽可能指定它们的容量。当然,如果你操作的字符串长度(length)不超过 16 个字符就不用了,当不指定容量(capacity)时默认构造一个容量为16的对象。不指定容量会显著降低性能。
(4)StringBuilder一般使用在方法内部来完成类似”+”功能,因为是线程不安全的,所以用完以后可以丢弃。StringBuffer主要用在全局变量中。
(5)相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15%
左右的性能提升,但却要冒多线程不安全的风险。而在现实的模块化编程中,负责某一模块的程序员不一定能清晰地判断该模块是否会放入多线程的环境中运行,因此:除非确定系统的瓶颈是在
StringBuffer上,并且确定你的模块不会运行在多线程模式下,才可以采用StringBuilder;否则还是用StringBuffer。
(9) Map、Set、List、Queue、Stack的特点与用法。
这几个都是属于集合(Collection), 继承关系如下所示:
Collection
├List
│├LinkedList
│├ArrayList
│└Vector
│ └Stack
└Set
└QueueMap
├Hashtable
├HashMap
└WeakHashMap
List: 必须保持元素特定的顺序。
Set: 不保存重复的元素。
Queue: 先入先出,在并发编程中很重要。(在《thing in java》第四版715页有讲解BlockingQueue)
Map: 保存的是键值对。
Stack: 先入后出,java里面是用Vector实现的。但是《thinking in java》一书对此作了批评,Bruce Eckel建议使用LinkedList来实现栈。
(10)HashMap和HashTable的区别。
作者:Aloys寒风
链接:https://www.zhihu.com/question/20581065/answer/37206277
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。在Java 2以前,一般使用Hashtable来映射键值和元素。为了使用Java集合框架,Java对Hashtable进行了重新设计,但是,为了向后兼容保留了所有的方法。Hashtable实现了Map接口,除了Hashtable具有同步功能之外,它与HashMap的用法是一样的。·在使用时一般是用ArrayList代替Vector,LinkedList代替Stack,HashMap代替HashTable,即使在多线程中需要同步,也是用同步包装类。另外在使用上还有一些小的差异,比如:HashTable的key和value都不允许为null值,而HashMap的key和value则都是允许null值的。这个其实没有好坏之分,只是Sun为了统一Collection的操作特性而改进的。HashTable有一个contains(Object value)方法,功能上与containsValue(Object value)一样,但是在实现上花销更大,现在已不推荐使用。
HashTable使用Enumeration,HashMap使用Iterator。Iterator其实与Enmeration功能上很相似,只是多了删除的功能。用Iterator不过是在名字上变得更为贴切一些。模式的另外一个很重要的功用,就是能够形成一种交流的语言(或者说文化)。有时候,你说Enumeration大家都不明白,说Iterator就都明白了
摘抄而已,不深究了。
。