X32专项练习部分16
HashMap和Hash Table的区别
/*
Hashtable 和 HashMap 的区别是:
正确答案: B C D E
Hashtable 是一个哈希表,该类继承了 AbstractMap,实现了 Map 接口
HashMap 是内部基于哈希表实现,该类继承AbstractMap,实现Map接口
Hashtable 线程安全的,而 HashMap 是线程不安全的
Properties 类 继承了 Hashtable 类,而 Hashtable 类则继承Dictionary 类
HashMap允许将 null 作为一个 entry 的 key 或者 value,而 Hashtable 不允许
A、错,Hashtable 是一个哈希表,该类继承自Dictionary类,实现了Map接口
B、错,HashMap是基于哈希表实现的,每一个元素是一个key-value对,
其内部通过单链表解决哈希冲突问题,容量不足(超过了阀值)时,同样会自动增长
该类继承AbstractMap,实现Map接口
C、对,Hashtable是线程安全的,而HashMap是线程不安全的
Hashtable中的方法是synchronize的
而HashMap中的方法在缺省情况下是非synchronize的。
D、讲的没错,但这跟Hashtable和HashMap的区别没关系
E、比较细节了,记住就行,HashTable如果放null会报空指针异常
哈希值的使用不同
HashTable直接使用对象的hashCode
而HashMap重新计算hash值
hashCode是jdk根据对象的地址
或者字符串
或者数字算出来的int类型的数值。
Hashtable计算hash值,直接用key的hashCode()
而HashMap重新计算了key的hash值
Hashtable在求hash值对应的位置索引时,用取模运算
HashMap会重新计算hash值,下面是源码
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
// key如果是null,新hashcode是0,否则计算新的hashcode
}
对于为什么重新计算哈希值
这一点很难理解
后期学计算机组成原理会涉及到这一点
扩容方式不同
Hashtable不要求底层数组的容量一定要为2的整数次幂
而HashMap则要求一定为2的整数次幂
Hashtable扩容时,将容量变为原来的2倍加1,而HashMap扩容时,将容量变为原来的2倍
线程安全区别
在hashmap做put操作的时候会调用到以上的方法
现在假如A线程和B线程同时对同一个数组位置调用addEntry
两个线程会同时得到现在的头结点,然后A写入新的头结点之后,B也写入新的头结点
那B的写入操作就会覆盖A的写入操作造成A的写入操作丢失
*/
下三角矩阵对角元素在1维数组中的存放位置
/*
将一个n×n的对称矩阵A的下三角部分按行存放在一个一维数组B中,
A[0][0]存放在B[0]中,那么第i行的对角元素A[i][i]在B中的存放位置是( )
正确答案: A
(i+3)×i/2
(i+1)×i/2
(2n-i+1)×i/2
(2n-i-l)×i/2
这道题我审题错了
很明显横纵坐标的位置都是i
题目让你求对角线元素的存放位置
题目让你求对角线元素的存放位置
题目让你求对角线元素的存放位置
快一点的方法,前几个代进去基本就可以得到正确答案了
B[0] = A[0][0],i=0,在第0个位置,ABCD均符合
B[2] = A[1][1] ,i=1,在第2个位置,只有A符合
再试一下B[5] = A[2][2],i=2,在第5个位置,也只有A符合
使用递归去做
只考虑下三角,第 i 行有 i+1 个元素,设B[i]的索引为S[i]
则S[i] = S[i-1] + i + 1
就是上面所有的下三角元素行加上当前下三角元素行
初始条件 S[0] = 0,S[1] = 2,S[2] = 5
前3行的总元素个数一眼就能看出来
可以写程序使用递归去做
public static int findPosition(int n) {
if (n == 0) {
return 0;
}else {
return findPosition(n - 1) + n + 1;
}
}
*/
堆和栈的区别
/*
下列关于堆和栈的区别描述错误的有
正确答案: A
申请方式的不同,堆是系统自动分配,栈是自己申请
栈的大小是固定的,堆的大小受限于系统中有效的虚拟内存
栈的空间由系统决定何时释放,堆需要自己决定何时去释放
堆的使用容易产生碎片,但是用起来最方便
先通俗解释一下
堆:自己做菜自己吃,什么时候收盘子自己知道,但是可能会浪费(产生碎片)
因为可能自己一个人吃不完
栈:公司食堂,你吃饭由食堂工作人员帮你打饭和分配位置,吃完了工作人员帮你收盘子
你浪费粮食(碎片)那是不可能的,因为食堂会把碎片拿去喂猪
再官方说一下
栈内存操作系统来分配,堆内存由程序员人为开辟
栈有系统自动分配,只要栈剩余空间大于所申请空间
系统将为程序提供内存,否则将报异常提示栈溢出
*/
计算存储地址
/*
已知 10*12 (10行12列) 的二维数组 A ,以行序为主序进行存储,每个元素占 1 个存储单元
已知 A[1][1] 的存储地址为 420 ,则 A[5][5] 的存储地址为 ( )
正确答案: C
470
471
472
473
对于本题,不用纠结于起始位置到底在哪里
题目已经告诉我们了A[1][1]的地址
只需要求题目要求的元素与A[1][1]之间有多少个元素就阔以了
A[5][5]与A[1][1]之间有4*12+4=52个元素
而A[1][1]地址为420,那么A[5][5]的地址就为420+52=472
注意两者之间相差52
最后要减去自身
*/
第2趟快速排序的结果
/*
下列选项中,不可能是快速排序第2趟排序结果的是
正确答案: C
2,3,5,4,6,7,9
2,7,5,6,4,3,9
3,2,5,4,7,6,9
4,2,3,5,7,6,9
补充快速排序知识点
快速排序的最坏结果是正序和逆序
快排的阶段性排序结果的特点是
第i趟完成时,会有i个以上的数
出现在它最终将要出现的位置
即它左边的数都比它小,它右边的数都比它大
题目问第二趟排序的结果,即要找不存在2个这样的数的选项
A选项中,2、3、6、7、9均符合,所以A排除
B选项中,2、9均符合,所以B排除
D选项中,5、9均符合,所以D选项排除
最后看C选项,只有9一个数符合,所以C不可能是快速排序第二趟的结果
我的通俗理解
第2趟排序,至少有2个数出现在最终结果的最终位置
*/
选择合适的排序方法
/*
设有 1000 个基本有序的元素,希望用最快的速度挑选出其中前 10 个最大的元素,最后选用( )排序法。
冒泡排序
快速排序
直接插入排序
归并排序
条件说基本有序
并且在1000个数中取最大的前10个元素
冒泡排序作为交换排序的方法,在基本有序的集合中元素位移次数最少
其次,冒泡排序每次会将最大的元素放在最后面
想要获取前10个最大元素,只需要截取排序10趟过后的最后10个元素即可
*/
第1轮堆排序结果
/*
将整数数组(7-6-3-5-4-1-2)按照堆排序的方式原地进行升序排列
请问在第一轮排序结束之后,数组的顺序是
正确答案: C
2-6-3-5-4-1-7
6-2-3-5-4-1-7
6-5-3-2-4-1-7
1-5-3-2-4-6-7
原数组已经是一个大顶堆,可直接开始交换调整
交换堆顶和左子树叶子节点
然后调整堆
直到所有的父节点大于两个子树
第1轮排序才算完成
所以是考的第1轮大顶堆了理解
不仅是交换,还要调整堆
7 2 6 6
/ \ / \ / \ / \
6 3 ==> 6 3 ==> 2 3 ==> 5 3
/ \ / \ / \ / \ / \ / \ / \ / \
5 4 1 2 5 4 1 7 5 4 1 7 2 4 1 7
由此得出,第一轮结束后的顺序是:6,5,3,2,4,1,7。
*/
包装类和常量池
abstract class Test20 {
public static void main(String[] args) {
Float s=new Float(0.1f);
Float t=new Float(0.1f);
Double u=new Double(0.1);
System.out.println(s == t); // false
System.out.println(u.equals(t)); // false
System.out.println(s.equals(t)); // true
/*
Float,Double类都重写了equals方法
在两者比较之前会判断
是不是都是同一个类型
如果是就再次比较数值的大小
如果不是,直接返回false
下面是源码
public boolean equals(Object obj) {
return (obj instanceof Float)
&& (floatToIntBits(((Float)obj).value) == floatToIntBits(value));
}
*/
String a="a";
String b="b";
String c=a+b;
String d=new String("ab");
System.out.println((a+b).equals(c)); // true
System.out.println(a+b==c); // false
System.out.println(c==d); // false
System.out.println(c.equals(d)); // true
/*
String对象的两种创建方式:
第一种方式: String str1 = "aaa";
是在常量池中获取对象("aaa" 属于字符串字面量
因此编译时期会在常量池中创建一个字符串对象
如果常量池中已经存在该字符串对象则直接引用)
第二种方式: String str2 = new String("aaa");
一共会创建两个字符串对象一个在堆中
一个在常量池中(前提是常量池中还没有"aaa"对象)
System.out.println(str1==str2); //false
String类型的常量池比较特殊
它的主要使用方法有两种
第1种:直接使用双引号声明出来的String对象会直接存储在常量池中
第2种:如果不是用双引号声明的String对象,可以使用String提供的intern方法
String.intern()是一个Native方法
它的作用是:
如果运行时常量池中已经包含一个等于此 String 对象内容的字符串
则返回常量池中该字符串的引用;
如果没有,则在常量池中创建与此String内容相同的字符串,并返回常量池中创建的字符串的引用
String s1 = new String("AAA");
String s2 = s1.intern();
String s3 = "AAA";
System.out.println(s2); // AAA
System.out.println(s1 == s2); // false,因为一个是堆内存中的String对象一个是常量池中的String对象,
System.out.println(s2 == s3); // true, s1,s2指向常量池中的”AAA“
有关字符串拼接:
String a = "a";
String b = "b";
String str1 = "a" + "b";//常量池中的对象
String str2 = a + b; //在堆上创建的新的对象
String str3 = "ab";//常量池中的对象
System.out.println(str1 == str2);//false
System.out.println(str1 == str3);//true
System.out.println(str2 == str3);//false
*/
}
}