Java 学习笔记:牛客找虐记(1)

注意文章内容均总结于牛客网,解析参照大佬的讲解

一、 HashMap 和 HashTable

1.1 源码

// HashMap的源码
public class HashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable

// Hashtable的源码
public class Hashtable<K,V>
    extends Dictionary<K,V>
    implements Map<K,V>, Cloneable, java.io.Serializable
// HashMap的put方法,没有同步
public V put(K key, V value) 
 
// Hashtable的put方法,与同步
// 当然,Hashtable的其他方法,如get,size,remove等方法,都加了synchronized关键词同步操作
public synchronized V put(K key, V value) 
// HashMap的put方法中,有如下语句
// 调用某个方法直接把key为null,值为value的键值对插入进去。
if (key == null)
    return putForNullKey(value);

// Hashtable的put方法有以下语句块,key 不可为 null
// Make sure the value is not null
if (value == null) {
    throw new NullPointerException();
}
//以下是HashMap中的方法,注意,没有contains方法,所以,D错误
public boolean containsKey(Object key)
public boolean containsValue(Object value)

//以下是Hashtable的方法
public synchronized boolean contains(Object value)
public synchronized boolean containsKey(Object key)
public boolean containsValue(Object value)

1.2 总结

1.2.1 HashMap

  1. HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。HashMap的底层结构是一个数组,数组中的每一项是一条链表。

  2. HashMap的实例有俩个参数影响其性能: “初始容量” 和 装填因子。

  3. HashMap实现不同步,线程不安全。HashTable线程安全

  4. HashMap中的key-value都是存储在Entry中的。

  5. HashMap可以存null键和null值,不保证元素的顺序恒久不变,它的底层使用的是数组和链表,通过hashCode()方法和equals方法保证键的唯一性

  6. 解决冲突主要有三种方法:定址法拉链法再散列法HashMap是采用拉链法解决哈希冲突的。

: 链表法是将相同hash值的对象组成一个链表放在hash值对应的槽位;

开放定址法解决冲突的做法是:当冲突发生时,使用某种探查(亦称探测)技术在散列表中形成一个探查(测)序列。 沿此序列逐个单元地查找,直到找到给定 的关键字,或者碰到一个开放的地址(即该地址单元为空)为止(若要插入,在探查到开放的地址,则可将待插入的新结点存人该地址单元)。

拉链法解决冲突的做法是: 将所有关键字为同义词的结点链接在同一个单链表中 。若选定的散列表长度为m,则可将散列表定义为一个由m个头指针组成的指针数 组T[0…m-1]。凡是散列地址为i的结点,均插入到以T[i]为头指针的单链表中。T中各分量的初值均应为空指针。在拉链法中,装填因子α可以大于1,但一般均取α≤1。拉链法适合未规定元素的大小。

1.2.2 Hashtable 和 HashMap 的区别:

  1. 继承不同:

    public class Hashtable extends Dictionary implements Map
    public class HashMap extends  AbstractMap implements Map
    
  2. Hashtable中的方法是同步的,而HashMap中的方法在缺省情况下是非同步的。在多线程并发的环境下,可以直接使用Hashtable,但是要使用HashMap的话就要自己增加同步处理了。

  3. HashTable 中, keyvalue 都不允许出现 null 值。 在 HashMap 中, null 可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为 null 。当 get() 方法返回 null 值时,即可以表示 HashMap 中没有该键,也可以表示该键所对应的值为 null 。因此,在HashMap 中不能由 get()方法来判断 HashMap 中是否存在某个键, 而应该用 containsKey() 方法来判断。

  4. 两个遍历方式的内部实现上不同。HashtableHashMap都使用了Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式 。

  5. 哈希值的使用不同,HashTable直接使用对象的hashCode。而HashMap重新计算hash值。

  6. HashtableHashMap它们两个内部实现方式的数组的初始大小和扩容的方式。HashTablehash数组默认大小是11,增加的方式是old*2+1HashMaphash数组的默认大小是16,而且一定是2的指数

HashSet子类依靠hashCode()equal()方法来区分重复元素。

HashSe``t内部使用Map保存数据,即将HashSet的数据作为Mapkey值保存,这也是HashSet中元素不能重复的原因。而Map中保存key值的,会去判断当前Map中是否含有该Key对象,内部是先通过keyhashCode,确定有相同的hashCode之后,再通过equals方法判断是否相同。

二、构造块 和 静态块

2.1 题目

public class B
{
    public static B t1 = new B();
    public static B t2 = new B();
    {
        System.out.println("构造块");
    }
    static
    {
        System.out.println("静态块");
    }
    public static void main(String[] args)
    {
        B t = new B();
    }

2.2 解析

	输出结果:构造块 构造块 静态块 构造块
  1. 开始时JVM加载B.class,对所有的静态成员进行声明,t1 t2被初始化为默认值,为null,又因为t1 t2需要被显式初始化,所以对t1进行显式初始化,初始化代码块→构造函数(没有就是调用默认的构造函数)

  2. 静态代码块不初始化:因为在开始时已经对static部分进行了初始化,虽然只对static变量进行了初始化,但在初始化t1时也不会再执行static块了。因为JVM认为这是第二次加载B.class了,所以static会在t1初始化时被忽略掉,直接初始化非static部分,也就是构造块部分(输出’‘构造块’’)接着构造函数(无输出)。

  3. 接着对t2进行初始化过程同t1相同(输出’构造块’),此时就对所有的static变量都完成了初始化,接着就执行static块部分(输出’静态块’),接着执行,main方法,同样也,new了对象,调用构造函数输出(‘构造块’)`

:并不是静态块最先初始化,而是静态域。

静态域中包含静态变量、静态块和静态方法,其中需要初始化的是静态变量和静态块.而他们两个的初始化顺序是自上而下,自左向右!

三、鲁棒性

3.1 概念

鲁棒性(Robust,即健壮性)

  1. Java在编译和运行程序时,都要对可能出现的问题进行检查,以消除错误的产生。它提供自动垃圾收集来进行内存管理,防止程序员在管理内存时容易产生 的错误。通过集成的面向对象的例外处理机制,在编译时,Java揭示出可能出现但未被处理的例外,帮助程序员正确地进行选择以防止系统的崩溃。

  2. 另外, Java在编译时还可捕获类型声明中的许多常见错误,防止动态运行时不匹配问题的出现。

3.2 特点

  1. Java在编译和运行程序时都要对可能出现的问题进行检查,以防止错误的产生;

  2. Java编译器可以查出许多其他语言运行时才能发现的错误;

  3. Java不支持指针操作,大大减少了错误发生的可能性;

  4. Java具有异常处理的功能,当程序异常时,它能捕获并响应意外情况,以保证程序能稳妥地结束,计算机系统不会崩溃;

四、 实参 和 形参

4.1 题目

public class Tester{
public static void main(String[] args){
   Integer var1=new Integer(1);
   Integer var2=var1;
   doSomething(var2);
   System.out.print(var1.intValue());
   System.out.print(var1==var2);
}
public static void doSomething(Integer integer){
    integer=new Integer(2);
    }
}
运行结果:1true

4.2 解析

java中引用类型的实参向形参的传递,只是传递的引用,而不是传递的对象本身。

在这里插入图片描述

五、 try - catch - finally

5.1 题目

package algorithms.com.guan.javajicu;
public class TestDemo
{
    public static String output = ””;
    public static void foo(inti)
    {
        try
        {
            if (i == 1)
            {
                throw new Exception();
            }
        }
        catch (Exception e)
        {
            output +=2;
            return ;
        } finally
        {
            output +=3;
        }
        output +=4;
    }
    public static void main(String[] args)
    {
        foo(0);
        foo(1);
        System.out.println(output);
    }
}

运行结果:3423

5.2 解析

5.2.1 步骤推演

  1. 首先是foo(0)在try代码块中未抛出异常,finally是无论是否抛出异常必定执行的语句,所以 output += “3”;然后是output += “4”;

  2. 执行foo(1)的时候,try代码块抛出异常,进入catch代码块,output += “2”;
    前面说过finally是必执行的,即使return也会执行output += “3”

  3. 由于catch代码块中有return语句,最后一个output += “4”不会执行。
    所以结果是3423

5.2.2 误区

try-catch-finally块中,finally块在以下几种情况将不会执行。

  1. finally块中发生了异常。

  2. 程序所在线程死亡。

  3. 在前面的代码中用了System.exit()

  4. 关闭了CPU

六、多态

6.1题目

class Test {
    public static void main(String[] args) {
        System.out.println(new B().getValue());
    }
    static class A {
        protected int value;
        public A (int v) {
            setValue(v);
        }
        public void setValue(int value) {
            this.value= value;
        }
        public int getValue() {
            try {
                value ++;
                return value;
            } finally {
                this.setValue(value);
                System.out.println(value);
            }
        }
    }
    static class B extends A {
        public B () {
            super(5);
            setValue(getValue()- 3);
        }
        public void setValue(int value) {
            super.setValue(2 * value);
        }
    }
}
运行结构:22 34 17

6.2 解析

6.2.1 多态特性

执行对象实例化过程中遵循多态特性

  1. 调用的方法都是实例化的子类中的重写方法
  2. 只有明确调用了super关键词或者是子类中没有该方法时,才会去调用父类相同的同名方法

6.2.2 步骤推演

Step 1: new B()构造一个 B 类的实例

  1. 此时super(5)语句显示调用父类 A 带参的构造函数,该构造函数调setValue(v)。虽然构造函数是 A 类的构造函数,但此刻正在初始化的对象是 B 的一个实例,因此这里调用的实际是 B 类的setValue方法,于是调用B类中的setValue方法 。
  2. 然而,B 类中setValue方法显示调用父类的setValue方法,将 B 实例的value值设置为 2 x 5 = 10
  3. 接着,B类的构造函数还没执行完成,继续执行setValue(getValue()- 3)// 备注1
  4. 先执行getValue方法,B 类中没有重写getValue方法,因此调用父类 A 的getValue方法。:
    1. 调用getValue方法之前,B 的成员变量value值为10
    2. value++ 执行后, B 的成员变量value值为11,此时开始执行到return语句,将11这个值作为getValue方法的返回值返回出去。
    3. 但是由于getValue块被try finally块包围,因此finally中的语句无论如何都将被执行,所以步骤 2 中 11 这个返回值会先暂存起来,到finally语句块执行完毕后再真正返回出去。
    4. 这里有很重要的一点:finally语句块中 this.setValue(value)方法调用的是 B 类的setValue方法。为什么?因为此刻正在初始化的是 B 类的一个对象(运行时多态),就像最开始第一步提到的一样(而且这里用了使用了this关键词显式指明了调用当前对象的方法)。因此,此处会再次调用 B 类的setValue方法,同上,super.关键词显式调用 A 的setValue方法,将 B 的value值设置成为了2 * 11 = 22
    5. 因此第一项打印项为 22
    6. finally语句执行完毕 会把刚刚暂存起来的11 返回出去,也就是说这么经历了这么一长串的处理,getValue方法最终的返回值是11
      回到前面标注了 // 备注1 的代码语句,其最终结果为setValue(11-3) => setValue(8)
      这里执行的setValue方法,将会是 B 的setValue方法。 之后 B 的value值再次变成了2*8 = 16;

Step2:new B().getValue()

B 类中没有独有的getValue方法,此处调用A的getValue方法。同Step 1

  1. 调用getValue方法之前,B 的成员变量value值为16
  2. value++ 执行后, B 的成员变量value值为17,此时执行到return语句,会将17这个值作为getValue方法的返回值返回出去
  3. 但是由于getValue块被try finally块包围而finally中的语句无论如何都一定会被执行,所以步骤2中17这个返回值会先暂存起来,到finally语句块执行完毕后再真正返回出去。
  4. finally语句块中继续和上面说的一样: this.setValue(value)方法调用的是 B 类的setValue()方法将 B 的value值设置成为了2 * 17 = 34
  5. 因此第二个打印项为34
  6. finally语句执行完毕 会把刚刚暂存起来的17返回出去。
  7. 因此new B().getValue()最终的返回值是17.

Step3: main => System.out.println

  1. 将刚刚返回的值打印出来,也就是第三个打印项:17

最终结果为 22 34 17

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值