每日一题总结 2020.01.26-2020.02.04

  • “>>”(有符号)右移,如果为正,高位补”0“;
    ----------------------------- 如果为负,高位补”1“。
    ”>>>“无符号右移/逻辑右移,无论正负都补"0"。(不管原数的符号位,就是把32位数右移,最后高位补"0")
           没有无符号左移。Java中int类型是4字节,32位,所以左移时要注意。(不是看当前数字有几位)。负数是以补码形式存储的,所以负数的左移和右移要先看补码左右移的结果,
           然后根据”补码的补码是原码“求得最终结果。 (要注意求反码时:符号位不变,其他位取反。)
           比如:-10<<2:(加粗的"1"是符号位) -10的原码是000…0(27个0)11010 ,
    ------------------------------------------------------ 反码是111…1(27个1)10101,
    ----------------------------- ------------------------ 补码是111…1(27个1)10110,
    将补码左移2位的结果是:
    --------------------------------------------------------------- 111…1(25个1)1011000 接下来需要对补码求补码得到最终结果:
    ----------------------------- 反码是000…0(25个0)1100111,
    ----------------------------- 补码是000…0(25个0)1101000,即-4
    接下来求-10>>2,有符号右移,补码是111…1(27个1)10110,
    将补码有符号移2位(本身是负数,高位补"1")的结果是:
    ----------------------------- 111…1(27个1)11101 接下来根据补码的补码为源码,先除符号位外按位取反得反码:
    ----------------------------- 000…0(27个0)10010
    -------------加1得补码:000…0(27个0)10011,即-3
    最后求-10>>>2,无符号右移,补码是111…1(27个1)10110,
    由于无符号位右移的规定结果必须是正数,所以原符号位的“1”被自动转换为对应的数值,不再代表符号。且,正数的补码是本身,所以不变。 将补码无符号右移2位(高位补"0")的结果是:
    ----------------------------------------------- 00111…1(28个1)01
    即‭1073741821。

    计算机里,是这么运算补码的:对于负数,先取绝对值,然后求反,加一
    -128 -> 128 -> 1000 0000 -> 0111 1111 -> 1000 0000
    所以,1字节也就是 8位有符号的整数取值范围的补码表示:
    1000 0000 到 0000 0000, 再到 0111 1111
    即 -128 到 0, 再到 127 ,也就是 -128 ~ +127 。

(可以这么理解: 计算机为数据类型分配了 n 位,超过 n 位的数值会被自动舍弃,现在计算机系统中采用的补码,克服了“原码中存在+0和-0”的情况,仅表现为一个0,1000 0000感觉是 -0,其实是 -128 的补码。)

  • Java语言的下面几种数组复制方法中,哪个效率最高:
    A…for循环逐一复制
    其他3个都是浅拷贝,只复制引用;for循环用的是深拷贝,复制值,效率最低。

B.System.arraycopy

 public static native void arraycopy
 (Object src,  int  srcPos,Object dest, int destPos,int length);

是本地方法。
这篇https://blog.csdn.net/qq_34834846/article/details/97174521说:
arraycopy方法上有@HotSpotIntrinsicCandidate(然鹅我没找到不知道为什么 qaq)为了提升性能,在JVM里对@HotSpotIntrinsicCandidate注解的方法进行了手写,这里要提一个叫*JNI(Java Native Interface)*的东西,普通的native方法( 比如C选项Array.copyOf方法 )编译后还要通过JNI再次编译成.cpp文件才能执行。
而有 @HotSpotIntrinsicCandidate这个注解的方法在JVM里就是用.cpp文件写好的,所以就跳过了JNI阶段,所以速度就能提升,这也是System.arraycopy()速度冠绝群雄的原因。

C,Arrays.copyOf

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)? (T[]) new Object[newLength]:(T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
        return copy;
    }

该方法还是调用的System.arraycopy,效率不如B高 。

D.使用clone方法

 protected native Object clone() throws 
 CloneNotSupportedException;
  • 关于泛型的说法错误的是:
    A.虚拟机中没有泛型,只有普通类和普通方法 。 √
    B.所有泛型类的类型参数在编译时都会被擦除。 √
    C.创建泛型对象时指明类型,让编译器尽早地做参数检查。 √
    D.泛型的类型擦除机制意味着不能在运行时动态获取ListT的实际类型。 × 可以通过反射机制获取 。

下列说法正确的是:
A.ConcurrentHashMap使用synchronized关键字保证线程安全 ×
ConcurrentHashMap 类中包含两个静态内部类 HashEntry 和 Segment。HashEntry 用来封装映射表的键/值对;Segment(继承自 ReentrantLock )用来充当锁的角色,每个 Segment 对象守护整个散列映射表的若干个桶。每个桶是由若干个 HashEntry 对象链接起来的链表。即一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素

使用synchronized关键字来保证同步的是HashTable。

B.HashMap实现了Collection接口 ×
HashMap implements Map而非Collection。

C.Array.asList方法返回java.util.ArrayList对象 ×

 public static <T> List<T> asList(T... a) {
        return new ArrayList<>(a);
    }

返回的是ArrayList对象,但是不是java.util包中的ArrayList,而是Arrays的一个内部类:

 private static class ArrayList<E> extends AbstractList<E>implements RandomAccess, java.io.Serializable
    {
        private static final long serialVersionUID = -2764017481108945198L;
        private final E[] a;

        ArrayList(E[] array) {
a = Objects.requireNonNull(array);
        }

D.SimpleDateFormat是线程不安全的 √

 private StringBuffer format(Date date, StringBuffer toAppendTo,
                                FieldDelegate delegate) {
// Convert input date to time field list 
calendar.setTime(date);                

calendar.setTime(date); 可知:多个线程共享SimpleDateFormat实例时,对Calendar对象的更改会相互影响,因此产生了线程安全问题。

  • Java程序初始化的顺序:
    (1)静态优先于非静态,
    (2)父类优先于子类。
    且(1)优先级大于(2)。

  • 能正确实现GBK编码字节流到UTF-8编码字节流:

byte[] src,dst;
dst=new String(src,"GBK").getbytes("UTF-8");

String类的构造方法:
(1) String() :初始化新创建的 String对象,使其表示空字符序列。
(2) String(byte[] bytes) :通过使用平台的默认字符集解码指定的字节数组来构造新的 String 。
(3) String(byte[] bytes, Charset charset)
构造一个新的String,由指定的字节数组解码charset 。
传入(src,:“GBK”)构造的String对象就是由字节数组src解码GBK。
(4) String(byte[] bytes, int offset, int length) :通过使用平台的默认字符集解码指定的字节子阵列来构造新的 String 。
(5) String(byte[] bytes, int offset, int length, Charset charset) :构造一个新的String通过使用指定的指定字节子阵列解码charset 。
(6) String(byte[] bytes, int offset, int length, String charsetName) :构造一个新的 String通过使用指定的字符集解码指定的字节子阵列。
(7) String(byte[] bytes, String charsetName) :构造一个新的String由指定用指定的字节的数组解码charset 。
(8) String(char[] value) :分配一个新的 String ,以便它表示当前包含在字符数组参数中的字符序列。
(9)String(char[] value, int offset, int count) :分配一个新的 String ,其中包含字符数组参数的子阵列中的字符。
(10) String(int[] codePoints, int offset, int count) :分配一个新的 String ,其中包含 Unicode code point数组参数的子阵列中的 字符 。
(11) String(String original) :初始化新创建的String对象,使其表示与参数相同的字符序列; 换句话说,新创建的字符串是参数字符串的副本。
(12) String(StringBuffer buffer) :分配一个新的字符串,其中包含当前包含在字符串缓冲区参数中的字符序列。
(13)String(StringBuilder builder) :分配一个新的字符串,其中包含当前包含在字符串构建器参数中的字符序列。

接下来调用String类的getBytes方法,传入Charset类型的参数,返回值是byte[ ]。
byte[] getBytes(Charset charset)
使用给定的charset将该String编码为字节序列,将结果存储到新的字节数组中。
注意:
String类没有frombytes方法,
也没有encode方法。

  • 就近原则
public class E20200129 {
    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;
    }
    catch (Exception e)
    {System.out.println(e.toString());
    }

    finally {
        this.setValue(value);
    System.out.println(value);
    }
    return 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
    分析该代码执行过程:
    不管什么程序都是先看psvm主方法,先new B(),调用B类的构造方法,而B类是继承A类的,要先会执行A类的构造方法,B类构造中是 super(5), 调用A类构造方法,传入的参数是5 ,A类构造方法中是调用了setValue方法,注意!!! 这里**就近原则**,调用的是B类的setValue方法,而B类的serValue方法中,又是调用的父类A的setValue方法,传入的参数是 2*5=10,那么 B类的变量value=10,接下来继续执行 B类构造,调用setValue(getValue()-3),先要知道getValue()的值,该方法只有A类有,先执行try块,value++ : value=value+1=10+1=11 ,在return 11之前,会执行finally块的内容(但是11已经被拷贝了,value变量值改变不会影响返回值11 )在finally块中,调用B类的setValue(11)方法,调用A类的setValu(2*11=22),这时B类的value值变为22,finally块会输出22 ,接下来,getValue()返回值是11 ,然后调用B类的setValue(11-3=8),会执行A类的setValue(2*8=16),这时value变成16 。
    到这时B类的构造方法就执行完毕,回到主方法中,新产生的B类对象调用getValue方法,只有父类A有,先执行try块,value++: value=value+1=16+1=17 ,在return 17 前会执行finally块 ,调用B类的setValue(17)方法,而B类的setValue(17)方法的实现是调用A类的setValue( 2*17=34 ),这时value=34 ,在finally 块中需要输出value,所以第二个输出的数是 34 ,fially块执行完毕17就可以return 了,所以在主方法中会输出17 。因此运行结果就是 “22 34 17”

  • 运算符优先级顺序
    口诀:单目乘除为关系。
    逻辑三目后赋值。

    () {} []最优先。
    单目:+ , - , ++ , – , !, ~
    乘除:* , /
    为(位):<< , >>> , >>
    关系:< , ≤ , > , ≥ , instance of
    逻辑:& , ^ , | ,&& , ||
    三目:? :
    赋值:= , += , -= , *= , /=

子类的构造方法中,如果没有显式地调用父类的构造方法,就会默认调用父类的无参构造。(所以父类得有无参构造,如果父类只提供了有参构造,编译就会报错。)比如:

class Person
{
    String name="No name";
    public Person(String nm)
    {name=nm;}
}

class Employee extends Person
{String empID="0000";
public Employee(String id)
{   empID=id;}}

public class F20200204 {
    public static void main(String[] args) {
    Employee e=new Employee("123");
    System.out.println(e.empID);
    }}

改正方法:
(1)父类添加一个无参构造,子类在调用父类无参构造后就会正常执行它自己的构造方法。

class Person
{
    String name="No name";
    public Person(String nm)
    {name=nm;}
    
    public Person()
    {}
}

(2)在子类中显式调用父类的有参构造。

class Employee extends Person
{String empID="0000";
public Employee(String id)
{
super(id);
    empID=id;}
    }
  • JDK1.5以后拆装箱机制:
    int i=0;
    Integer j=new Integer(0);
    System.out.println(i==j);
    System.out.println(j.equals(i));

运行结果:
true
true
包装类和 String 类相像,也有常量池。
比如 Integer i=40 在编译时会执行将代码封装成:

 Integer i=Integer.valueOf( 40 );

    f在Integer类的 valueOf 方法中有个内部类 IntegerCache ,维护了一个 Integer 数组 Cache .长度为 256 ( -128~ 127 ) 。
    f这道题,基本型和包装类进行 “ == ” 运算符的比较,基本型封装型将会自动拆箱变为基本型后再进行比较,(可以这么想:基本型进行“ == ”比较时,比较的是值。基本类型变量在栈中直接存储值;
引用类型变量在栈中存储的是地址,来指向堆内存的实例
。)。
     而包装类变量调用 equals 方法时,看源码:

public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
    }
   

    f所以包装类变量.equals(基本型) ,会先把基本型变量装箱为包装类,然后先比较包装类类型,都是Integer类,OK,再比较值。

    对于Integer i = value; 就相当于 Integer i= Integer.valueOf(xxx); valueOf方法先判断传入参数的值,如果是在 -128 到 127 之间,不会去堆中创建对象,而是直接返回 IntegerCache 中的 cache 值;这个 IntegerCache 是 Integer 的静态内部类 :private static class IntegerCache ,该类中有一个 cache 数组,放 Integer 对象,该类的静态块中完成了数组的初始化。 否则,new 一个 Integer 返回 。
因此:

Integer i1=Integer.valueOf(59);
Integer i2=new Integer(59);
System.out.println(i1==i2);

    结果是 false,因为 i1 不会 new 对象,是从 Cache 数组里取的。

Integer i=Integer.valueOf(22);
Integer j=Integer.valueOf(22);
System.out.println(i==j);

    f结果是 true,取的是同一对象。

Integer i=new Integer(22);
Integer j=new Integer(22);
System.out.println(i==j);

    f结果是 false ,因为在堆中开辟了不同空间。

你可能感兴趣的:(总结,牛客网每日一题)