牛客网刷题笔记

练练不忘,必有回响。

垃圾收集

以前我是堆,你是栈

你总是能精准的找到我,给我指明出路

后来有一天我明白了

我变成了栈,你却隐身堆海

我却找不到你了,空指针了

我不愿意如此,在下一轮 full gc 前

我找到了 object 家的 finalize

又找到了你,这次我不会放手

在世界重启前,一边躲着 full gc 一边老去

虾滑

  1. 一个对象成为垃圾,是因为没有引用指向它

  2. 一个对象成为垃圾之后,等待垃圾回收器收集

  3. finalize方法也可以主动使用

  4. 在java中,对象的内存在哪个时刻回收,取决于垃圾回收器何时运行

  5. 一旦垃圾回收器准备好释放对象占用的存储空间,将首先调用其finalize()方法, 并且在下一次垃圾回收动作发生时,才会真正的回收对象占用的内存(《java 编程思想》)

  6. 在C++中,对象的内存在哪个时刻被回收,是可以确定的,在C++中,析构函数和资源的释放息息相关,能不能正确处理析构函数,关乎能否正确回收对象内存资源。

    在java中,对象的内存在哪个时刻回收,取决于垃圾回收器何时运行,在java中,所有的对象,包括对象中包含的其他对象,它们所占的内存的回收都依靠垃圾回收器,因此不需要一个函数如C++析构函数那样来做必要的垃圾回收工作。当然存在本地方法时需要finalize()方法来清理本地对象。在《java编程思想》中提及,finalize()方法的一个作用是用来回收“本地方法”中的本地对象

编译

  1. Java在运行时才进行翻译指令
  2. Java程序在运行时字节码才会被JVM翻译成机器码

对象序列化

  1. 使用transient修饰的变量不会被序列化
  2. 对象序列化的所属类需要实现Serializable接口
  3. 能够对对象进行传输的貌似只有ObjectOutputStream和ObjectInputStream这些以Object开头的流对象
  4. transient 修饰的变量在对象串化的时侯并不会将所赋值的值保存到传中,串化的对象从磁盘读取出来仍然是null

面向对象程序设计方法优点

  1. 可重用性 多态
  2. 可扩展性 继承
  3. 易于维护与管理 封装

内部类

class Enclosingone {
	public class InsideOne {}
}
public class inertest {
	public static void main(string[]args) {
		EnclosingOne eo = new EnclosingOne();
		//insert code here
	}
}

可以为

InsideOne ei = eo.new InsideOne();
// Or
EnclosingOne.InsideOne ei = eo.new InsideOne();

内部类其实和类的属性没什么区别,只是在声明的时候必须是Outer.Inner a,就像int a 一样,

静态内部类

Outer.Inner a = new Outer.Inner();
// 要把Outer.Inner看成一部分,就像类变量一样

非静态内部类

Outer.Inner a = new Outer().new Inner();
// 非静态,先有Outer对象才能有属性

Switch

  1. char 和 Character
  2. byte 和 Byte
  3. short 和 Short
  4. int 和 Integer
  5. Enum 枚举
  6. String Java1.7以后

ArrayList vs LinkedList

Object obj=new Object();
List aList=new ArrayList();
List bList=new LinkedList();

long t1=System.currentTimeMillis();
for(int i=0;i<50000;i++){
    aList.add(0,obj);
}
long t2=System.currentTimeMillis()-t1;

t1=System.currentTimeMillis();
for(int i=0;i<50000;i++){
    bList.add(0,obj);
}
long t3=System.currentTimeMillis()-t1; 

ArrayList内部是动态数组实现,在增加空间时会复制全部数据到新的容量大一些的数组中。而LinkedList内部为双向链表,可以按需分配空间,扩展容量简单,因此LinkedList用时少。

所以t2 > t3

String

String str =
"";
System.out.print(str.split(",").length);

split 这个方法默认返回一个数组。如果没有找到分隔符,会把整个字符串当成一个长度为1的字符串数组。

因为是数组,所以是.length

输出结果为:

1

多线程

以下哪种JAVA得变量声明方式可以避免程序在多线程竞争情况下读到不正确的值( )

  1. volatile √ volatile保证读写原子性,不保证更新(读=>改变值=>写)原子性
  2. static volatile √
  3. synchronized × synchronized不是修饰变量的 它修饰方法或代码块或对象

Math

double d1 =- 0.5;
System.out.println("Ceil d1="+Math.ceil(d1));
System.out.println("floor d1="+Math.floor(d1));
  • ceil:大于等于 x,并且与它最接近的整数。

  • floor:小于等于 x,且与 x 最接近的整数。

Ceil d1 = -0.0;
floor d1 = -1.0;

包装类

基本数据类型 包装类 大小
byte Byte 8bit
boolean Boolean -
char Character 16bit
short Short 16bit
int Integer 32bit
long Long 64bit
float Float 32bit
double Double 64bit

HashMap vs Hashtable

  1. Hashtable 已被淘汰,可以使用 ConcurrentHashMap 替代,这一点可以在 Hashtable 源码中的注释说明中找出。
  2. Hashtable 的 key、value 都不可以为 null。HashMap 最多只允许一条记录的键为 null,允许多条记录的值为 null。
  3. HashMap和Hashtable都实现了Map接口。
  4. 相比 HashMap,Hashtable 是线程安全的。因为 Hashtable 的几个重要方法都加上了 synchronized 关键字。
  5. Hashtable、HashMap 都使用了 Iterator。而由于历史原因,Hashtable 还使用了 Enumeration 的方式。
  6. HashMap允许将 null 作为一个 entry 的 key 或者 value,而 Hashtable 不允许。
  7. Hashtable 直接使用对象的 hashCode。而 HashMap 重新计算 hash 值。
  8. Hashtable 中的 hash 数组初始大小是 11,增加的方式是 old*2+1。HashMap 中 hash 数组的默认大小是 16,而且一定是 2 的指数。
  9. HashMap 有 containsvalue 和 containsKey 两个方法,Hashtable 使用的是 contains 方法。
  10. public class Hashtable extends Dictionary implements Map
  11. public class HashMap extends AbstractMap implements Map

包(package)由一组类(class)和接口(interface)组成(有其中一个即可)

一个.java文件中定义多个类

  1. public权限类只能有一个(也可以一个都没有,但最多只有一个)
  2. 这个.java文件名只能是public 权限的类的类名
  3. 倘若这个文件中没有public 类,则它的.java文件的名字是随便的一个类名
  4. 当用javac命令生成编译这个.java 文件的时候,则会针对每一个类生成一个.class文件

访问权限

修饰符 类内部 同一个包 子类 任何地方
private Yes
default/friendly Yes Yes
protected Yes Yes Yes
public Yes Yes Yes Yes

缓存

Integer i01 = 59;
int i02 = 59;
Integer i03 = Integer.valueOf(59);
Integer i04 = new Integer(59);
System.out.println(i01 == i02); // 1
System.out.println(i01 == i03); // 2
System.out.println(i03 == i04); // 3
System.out.println(i02 == i04); // 4
  1. 缓存,true
  2. 缓存,true
  3. false
  4. 数值相同,true
  5. 无论如何,Integer与new Integer不会相等。不会经历拆箱过程
  6. 两个都是非new出来的Integer,如果数在-128到127之间,则是true,否则为false
  7. Java在编译Integer i2 = 128的时候,被翻译成-> Integer i2 = Integer.valueOf(128);而valueOf()函数会对-128到127之间的数进行缓存
  8. 两个都是new出来的,都为false
  9. int和integer(无论new否)比,都为true,因为会把Integer自动拆箱为int再去比

依赖注入

  1. 依赖注入能够独立开发各组件,然后根据组件间关系进行组装
  2. 依赖注入提供使用接口编程
  3. 依赖注入指对象在使用时动态注入
  4. 依赖注入的动机就是减少组件之间的耦合度,使开发更为简洁

默认类型

  • 整数不指定类型,默认为int类型
  • 小数不指定类型,默认为double类型
  • 如果指定长整形,则类似于1000L
  • 如果指定单精度,则类似与3.14F

初始值

类中声明的变量有默认初始值;方法中声明的变量没有默认初始值,必须在定义时初始化,否则在访问该变量时会出错。

数据类型 初始值
byte 0
short 0
int 0
long 0L
char ‘u0000’
float 0.0f
double 0
boolean false
所有引用类型 null

算法

算法包括0个或多个输入,1个或多个输出,中间有穷个处理过程。存储结构不属于算法结构。

数组

  1. 数据的创建方式

    float f[][] = new float[6][6];
    float []f[] = new float[6][6];
    float [][]f = new float[6][6];
    float [][]f = new float[6][];
    // 数组命名时名称与[]可以随意排列
    // 但声明的二维数组中第一个中括号中必须要有值
  2. 数组是一个对象,不同类型的数组具有不同的类。因为可以调用方法,例如下方array与array_1类不同。

    public static void main(String[] args) {
        int[] i = new int[]{1,2,3};
        int[] j = new int[]{1,2,3};
        int[] k = i;
        int[] array = new int[100];
        double[] array_1 = new double[100];
        System.out.println(array);
        System.out.println(array_1);
        System.out.println(i.equals(j));
        System.out.println(i.equals(k));
    }
    [I@6d6f6e28
    [D@135fbaa4
    false
    true
  3. 数组是一个连续的存储结构,Java中的数组中的数据是连续存储在一块内存中的,所以可以通过下标(即偏移量)的方式访问。

  4. 查看源码可以知道数组的equals方法是object的equals,比较的是内存地址。

  5. 数组长度是不能动态调整的。

  6. 可以二维数组,且可以有多维数组,都是在Java中合法的。

  7. Arrays.equal()可以比较数组元素。

  8. int[] array=new int [100]; int array[]=new int [100];

final

  1. final修饰变量,则等同于常量
  2. final修饰方法中的参数,称为最终参数
  3. final修饰类,则类不能被继承
  4. final修饰方法,则方法不能被重写
  5. final 不能修饰抽象类
  6. final不能修饰接口
  7. final修饰的方法可以被重载 但不能被重写

重载 vs 重写

  • 重载 指可以有同样修饰符、返回值、方法名,但参数个数或顺序不同的方法。
  • 重写 指子类继承父类后,重写父类的方法。

表达式转型规则

Java表达式转型规则由低到高转换

  1. 所有的byte,short,char型的值将被提升为int型;
  2. 如果有一个操作数是long型,计算结果是long型;
  3. 如果有一个操作数是float型,计算结果是float型;
  4. 如果有一个操作数是double型,计算结果是double型;
  5. 被fianl修饰的变量不会自动改变类型,当2个final修饰相操作时,结果会根据左边变量的类型而转化。

题目

  1. 表达式 (short)10/10.2*2 运算后结果是什么类型?
  • 首先,要注意是 (short)10/10.2*2 ,而不是 (short) (10/10.2*2) ,前者只是把10强转为short,又由于式子中存在浮点数,所以会对结果值进行一个自动类型的提升,浮点数默认为double,所以答案是double;后者是把计算完之后值强转short。
  • 强制类型转换的优先级高于 + - * /
  • 小转大随便转,不同类型运算结果类型向右看齐: char<short< int< float <double
  1. 下列代码片段中,存在编译错误的语句是()
byte b1=1,b2=2,b3,b6,b8;
final byte b4=4,b5=6,b7;
b3=(b1+b2);  /*语句1*/
b6=b4+b5;    /*语句2*/
b8=(b1+b4);  /*语句3*/
b7=(b2+b5);  /*语句4*/
System.out.println(b3+b6);
  • 语句1错误:b3=(b1+b2);自动转为int,所以正确写法为b3=(byte)(b1+b2);或者将b3定义为int;
  • 语句2正确:b6=b4+b5;b4、b5为final类型,不会自动提升,所以和的类型视左边变量类型而定,即b6可以是任意数值类型;
  • 语句3错误:b8=(b1+b4);虽然b4不会自动提升,但b1仍会自动提升,所以结果需要强转,b8=(byte)(b1+b4);
  • 语句4错误:b7=(b2+b5); 同上。同时注意b7是final修饰,即只可赋值一次,便不可再改变
  1. byte a = 2; a = a - 10; 由于10是整型int,因此表达式 a - 10 将自动转换成int类型,int类型在赋值给byte,会编译错误
  2. byte a = 2; a -= 10; 不会发生编译错误,因为复合运算符是一个带有隐式转换的运算符,将右侧表达式类型自动转换成左侧表达式类型