分析java源码及大厂真题-基础

分析java源码及大厂真题-基础


一、基础

1、String、Long源码解析和面试题

1.1、String
  • 1.1.1 不变性

在这里插入图片描述

  • 我们可以写一个demo来模仿一下:
    String s = "hello";
    s = "world";
    

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

  • 我们可以写一个demo来模仿一下:

    String str ="hello world !!";
    // 这种写法是替换不掉的,必须接受 replace 方法返回的参数才行,这样才行:str = str.replace("l","dd");
    str.replace("l","dd");
    
1.1.2 字符串乱码

在这里插入图片描述

  • 我们可以写一个demo来模仿一下字符乱码:
    String str ="nihao 你好 喬亂";
    // 字符串转化成 byte 数组
    byte[] bytes = str.getBytes("ISO-8859-1");
    // byte 数组转化成字符串
    String s2 = new String(bytes);
    log.info(s2);
    // 结果打印为:
    nihao ?? ??
    

在这里插入图片描述

1.1.3 首字母大小写

在这里插入图片描述

1.1.4 相等判断

在这里插入图片描述

  • 我们可以写一个demo来看一下:
    public boolean equals(Object anObject) {
    	// 判断内存地址是否相同
    	if (this == anObject) {
    		return true;
    	}
    	// 待比较的对象是否是 String,如果不是 String,直接返回不相等
    	if (anObject instanceof String) {
    		String anotherString = (String)anObject;
    		int n = value.length;
    		// 两个字符串的长度是否相等,不等则直接返回不相等
    		if (n == anotherString.value.length) {
    			char v1[] = value;
    			char v2[] = anotherString.value;
    			int i = 0;
    			// 依次比较每个字符是否相等,若有一个不等,直接返回不相等
    			while (n-- != 0) {
    				if (v1[i] != v2[i])
    					return false;
    				i++;
    			}
    			return true;
    		}
    	}
    	return false;
    }
    

在这里插入图片描述

1.1.5 替换、删除

在这里插入图片描述

  • 写一个演示demo:

    public void testReplace(){
    String str ="hello word !!";
    log.info("替换之前 :{}",str);
    str = str.replace('l','d');
    log.info("替换所有字符 :{}",str);
    str = str.replaceAll("d","l");
    log.info("替换全部 :{}",str);
    str = str.replaceFirst("l","");
    log.info("替换第一个 l :{}",str);
    }
    //输出的结果是:
    替换之前 :hello word !!
    替换所有字符 :heddo word !!
    替换全部 :hello worl !!
    替换第一个 :helo worl !!
    
1.1.6 拆分和合并

在这里插入图片描述

  • 写一个演示demo:
    String s ="boo:and:foo";
    // 我们对 s 进行了各种拆分,演示的代码和结果是:
    s.split(":") 结果:["boo","and","foo"]
    s.split(":",2) 结果:["boo","and:foo"]
    s.split(":",5) 结果:["boo","and","foo"]
    s.split(":",-2) 结果:["boo","and","foo"]
    s.split("o") 结果:["b","",":and:f"]
    s.split("o",2) 结果:["b","o:and:foo"]
    

在这里插入图片描述

  • 写一个演示demo:
    String a =",a,,b,";
    a.split(",") 结果:["","a","","b"]
    

在这里插入图片描述

  • 写一个演示demo:
    String a =",a, , b c ,";
    // Splitter 是 Guava 提供的 API
    List<String> list = Splitter.on(',')
    .trimResults()// 去掉空格
    .omitEmptyStrings()// 去掉空值
    .splitToList(a);
    log.info("Guava 去掉空格的分割方法:{}",JSON.toJSONString(list));
    // 打印出的结果为:
    ["a","b c"]
    

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 写一个演示demo:

    // 依次 join 多个字符串,Joiner 是 Guava 提供的 API
    Joiner joiner = Joiner.on(",").skipNulls();
    String result = joiner.join("hello",null,"china");
    log.info("依次 join 多个字符串:{}",result);
    List<String> list = Lists.newArrayList(new String[]{"hello","china",null});
    log.info("自动删除 list 中空值:{}",joiner.join(list));
    // 输出的结果为;
    依次 join 多个字符串:hello,china
    自动删除 list 中空值:hello,china
    

在这里插入图片描述

1.2、Long
1.2.1 缓存

在这里插入图片描述

private static class LongCache {
	private LongCache(){}
	// 缓存,范围从 -128 到 127,+1 是因为有个 0
	static final Long cache[] = new Long[-(-128) + 127 + 1];
	// 容器初始化时,进行加载
	static {
	// 缓存 Long 值,注意这里是 i - 128 ,所以再拿的时候就需要 + 128
		for(int i = 0; i < cache.length; i++)
			cache[i] = new Long(i - 128);
	}
}
1.3、面试题
1.3.1 为什么使用 Long 时,大家推荐多使用 valueOf 方法,少使用 parseLong 方法
答:因为 Long 本身有缓存机制,缓存了 -128 到 127 范围内的 Long,valueOf 方法会从缓存中去拿值,如果命中缓存,会减少资源的开销,parseLong 方法就没有这个机制。
1.3.2 如何解决 String 乱码的问题
答:乱码的问题的根源主要是两个:字符集不支持复杂汉字、二进制进行转化时字符集不匹配,所以在 String 乱码时我们可以这么做:
1. 所有可以指定字符集的地方强制指定字符集,比如 new String 和getBytes 这两个地方;
2. 我们应该使用 UTF-8 这种能完整支持复杂汉字的字符集。
1.3.3 为什么大家都说 String 是不可变的
答:主要是因为 String 和保存数据的 char 数组,都被 final 关键字所修饰,所以是不可变的,具体细节描述可以参考上文。
1.3.4 String 一些常用操作问题,如问如何分割、合并、替换、删除、截取等等问题
答:这些都属于问 String 的基本操作题目,考察我们平时对 String 的使用熟练程度,可以参考上文。

2、Java 常用关键字理解

2.1、static

在这里插入图片描述

2.1.1 修饰的对象

在这里插入图片描述

所以在使用 static 修饰类变量时,如何保证线程安全是我们常常需要考虑的。 当 static
修饰方法时,代表该方法和当前类是无关的,任意类都可以直接访问(如果权限是public 的话)。

在这里插入图片描述

  • 当 static 修饰方法块时,我们叫做静态块,静态块常常用于在类启动之前,初始化一些值,比如:

    public static List<String> list = new ArrayList();
    // 进行一些初始化的工作
    static {
    	list.add("1");
    }
    

在这里插入图片描述

2.1.2 初始化时机
  • 对于被static修饰的类变量、方法块和静态方法的初始化机会,我们写一个测试demo:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

被 static 修饰的方法,在类初始化的时候并不会初始化,只有当自己被调用时,才会被执行。

2.1.3 final

在这里插入图片描述

2.1.4 try、catch、finally

在这里插入图片描述

  • 演示demo:

    public void testCatchFinally() {
    	try {
    		log.info("try is run");
    		if (true) {
    			throw new RuntimeException("try exception");
    		}
    	} catch (Exception e) {
    		log.info("catch is run");
    		if (true) {
    			throw new RuntimeException("catch exception");
    		}
    	} finally {
    		log.info("finally is run");
    	}
    }
    

在这里插入图片描述

2.1.5 volatile

  volatile 的意思是可见的,常用来修饰某个共享变量,意思是当共享变量的值被修改后,会及时通知到其它线程上,其它线程就能知道当前共享变量的值已经被修改了。
  我们再说原理之前,先说下基础知识。就是在多核 CPU 下,为了提高效率,线程在拿值时,是直接和 CPU 缓存打交道的,而不是内存。主要是因为 CPU 缓存执行速度更快,比如线程要拿
值 C,会直接从 CPU 缓存中拿, CPU 缓存中没有,就会从内存中拿,所以线程读的操作永远都是拿 CPU 缓存的值。
在这里插入图片描述

2.1.6 transient

  transient 关键字我们常用来修饰类变量,意思是当前变量是无需进行序列化的。在序列化时,
就会忽略该变量,这些在序列化工具底层,就已经对 transient 进行了支持。

2.1.7 default
  • default 关键字一般会用在接口的方法上,意思是对于该接口,子类是无需强制实现的,但自己必须有默认实现,我们举个例子如下:
    在这里插入图片描述
2.2、面试题
2.2.1 如何证明 static 静态变量和类无关?
答:从三个方面就可以看出静态变量和类无关。
1. 我们不需要初始化类就可直接使用静态变量;
2. 我们在类中写个 main 方法运行,即便不写初始化类的代码,静态变量都会自动初始化;
3. 静态变量只会初始化一次,初始化完成之后,不管我再 new 多少个类出来,静态变量都不会
再初始化了。
不仅仅是静态变量,静态方法块也和类无关。
2.2.2 常常看见变量和方法被 static 和 final 两个关键字修饰,为什么这么做?
答:这么做有两个目的:
1. 变量和方法于类无关,可以直接使用,使用比较方便;
2. 强调变量内存地址不可变,方法不可继承覆写,强调了方法内部的稳定性。
2.2.3 catch 中发生了未知异常,finally 还会执行么?
答:会的,catch 发生了异常,finally 还会执行的,并且是 finally 执行完成之后,才会抛出
catch 中的异常。
不过 catch 会吃掉 try 中抛出的异常,为了避免这种情况,在一些可以预见 catch 中会发生异
常的地方,先把 try 抛出的异常打印出来,这样从日志中就可以看到完整的异常了。
2.2.4 volatile 关键字的作用和原理
答:这个上文说的比较清楚,可以参考上文。

3、Arrays、Collections、Objects 常用方法源码解析

3.1、工具类通用的特征

  再看细节之前,我们先总结一下好的工具类都有哪些通用的特征写法:

  1. 构造器必须是私有的。这样的话,工具类就无法被 new 出来,因为工具类在使用的时候,无
    需初始化,直接使用即可,所以不会开放出构造器出来。
  2. 工具类的工具方法必须被 static、final 关键字修饰。这样的话就可以保证方法不可变,并且可以直接使用,非常方便。
    我们需要注意的是,尽量不在工具方法中,对共享变量有做修改的操作访问(如果必须要做的话,必须加锁),因为会有线程安全的问题。除此之外,工具类方法本身是没有线程安全问题的,可以放心使用。
3.2、Arrays

  Arrays 主要对数组提供了一些高效的操作,比如说排序、查找、填充、拷贝、相等判断等等。
我们选择其中两三看下,对其余操作感兴趣的同学可以到 GitHub 上查看源码解析。

3.2.1 排序

  Arrays.sort 方法主要用于排序,入参支持 int、long、double 等各种基本类型的数组,也支持
自定义类的数组,下面我们写个 demo 来演示一下自定义类数组的排序:

  • 写个demo:

    @Data
    // 自定义类
    class SortDTO {
    	private String sortTarget;
    	
    	public SortDTO(String sortTarget) {
    		this.sortTarget = sortTarget;
    	}
    }
    
    @Test
    public void testSort(){
    	List<SortDTO> list = ImmutableList.of(
    	new SortDTO("300"),
    	new SortDTO("50"),
    	new SortDTO("200"),
    	new SortDTO("220")
    );
    // 我们先把数组的大小初始化成 list 的大小,保证能够正确执行 toArray
    SortDTO[] array = new SortDTO[list.size()];
    list.toArray(array);
    
    log.info("排序之前:{}", JSON.toJSONString(array));
    Arrays.sort(array, Comparator.comparing(SortDTO::getSortTarget));
    log.info("排序之后:{}", JSON.toJSONString(array));
    }
    
    输出结果为:
    排序之前:[{"sortTarget":"300"},{"sortTarget":"50"},{"sortTarget":"200"},{"sortTarget":"220"}]
    排序之后:[{"sortTarget":"200"},{"sortTarget":"220"},{"sortTarget":"300"},{"sortTarget":"50"}]
    

在这里插入图片描述

3.2.2 二分查找法

  Arrays.binarySearch 方法主要用于快速从数组中查找出对应的值。其支持的入参类型非常多,
如 byte、int、long 各种类型的数组。返回参数是查找到的对应数组下标的值,如果查询不
到,则返回负数。
在这里插入图片描述

  • 我们写一个demo:

    List<SortDTO> list = ImmutableList.of(
    	new SortDTO("300"),
    	new SortDTO("50"),
    	new SortDTO("200"),
    	new SortDTO("220")
    );
    
    SortDTO[] array = new SortDTO[list.size()];
    list.toArray(array);
    
    log.info("搜索之前:{}", JSON.toJSONString(array));
    Arrays.sort(array, Comparator.comparing(SortDTO::getSortTarget));
    log.info("先排序,结果为:{}", JSON.toJSONString(array));
    int index = Arrays.binarySearch(array, new SortDTO("200"),
    Comparator.comparing(SortDTO::getSortTarget));
    if(index<0){
    	throw new RuntimeException("没有找到 200");
    }
    log.info("搜索结果:{}", JSON.toJSONString(array[index]));
    输出的结果为:
    搜索之前:[{"sortTarget":"300"},{"sortTarget":"50"},{"sortTarget":"200"},{"sortTarget":"220"}]
    先排序,结果为:[{"sortTarget":"200"},{"sortTarget":"220"},{"sortTarget":"300"},{"sortTarget":"5
    搜索结果:{"sortTarget":"200"}
    

在这里插入图片描述

  • 这种情况进行了判断,如果是负数,会提前抛出明确的异常。 接下来,我们来看下二分法底层代码的实现:

    // a:我们要搜索的数组,fromIndex:从那里开始搜索,默认是0; toIndex:搜索到何时停止,默认
    // key:我们需要搜索的值 c:外部比较器
    private static <T> int binarySearch0(T[] a, int fromIndex, int toIndex,
    	T key, Comparator<? super T> c) {
    // 如果比较器 c 是空的,直接使用 key 的 Comparable.compareTo 方法进行排序
    // 假设 key 类型是 String 类型,String 默认实现了 Comparable 接口,就可以直接使用 compar
    if (c == null) {
    	// 这是另外一个方法,使用内部排序器进行比较的方法
    	return binarySearch0(a, fromIndex, toIndex, key);
    }
    int low = fromIndex;
    int high = toIndex - 1;
    // 开始位置小于结束位置,就会一直循环搜索
    while (low <= high) {
    	// 假设 low =0,high =10,那么 mid 就是 5,所以说二分的意思主要在这里,每次都是计算索
    	int mid = (low + high) >>> 1;
    	T midVal = a[mid];
    	// 比较数组中间值和给定的值的大小关系
    	int cmp = c.compare(midVal, key);
    	// 如果数组中间值小于给定的值,说明我们要找的值在中间值的右边
    	if (cmp < 0)
    		low = mid + 1;
    	// 我们要找的值在中间值的左边
    	else if (cmp > 0)
    		high = mid - 1;
    	else
    		// 找到了
    		return mid; // key found
    }
    // 返回的值是负数,表示没有找到
    return -(low + 1); // key not found.
    

在这里插入图片描述

3.2.3 拷贝

  数组拷贝我们经常遇到,有时需要拷贝整个数组,有时需要拷贝部分,比如 ArrayList 在
add(扩容) 或 remove(删除元素不是最后一个) 操作时,会进行一些拷贝。拷贝整个数组
我们可以使用 copyOf 方法,拷贝部分我们可以使用 copyOfRange 方法,以 copyOfRange
为例,看下底层源码的实现:

  • 写个demo:

    // original 原始数组数据
    // from 拷贝起点
    // to 拷贝终点
    public static char[] copyOfRange(char[] original, int from, int to) {
    	// 需要拷贝的长度
    	int newLength = to - from;
    	if (newLength < 0)
    		throw new IllegalArgumentException(from + " > " + to);
    	// 初始化新数组
    	char[] copy = new char[newLength];
    	// 调用 native 方法进行拷贝,参数的意思分别是:
    	// 被拷贝的数组、从数组那里开始、目标数组、从目的数组那里开始拷贝、拷贝的长度
    	System.arraycopy(original, from, copy, 0,
    	Math.min(original.length - from, newLength));
    	return copy;
    }
    

在这里插入图片描述

3.3、Collections

在这里插入图片描述

3.3.1 集合和中最大、小值

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.3.2 多种类型的集合

  Collections 对原始集合类进行了封装,提供了更好的集合类给我们,一种是线程安全的集合,一种是不可变的集合,针对 List、Map、Set 都有提供,我们先来看下线程安全的集合:线程安全的集合方法都是 synchronized 打头的,如下:
在这里插入图片描述

  • 从方法命名我们都可以看出来,底层是通过 synchronized 轻量锁来实现的,我们以synchronizedList 为例来说明下底层的实现:
    在这里插入图片描述

可以看到 List 的所有操作方法都被加上了 synchronized 锁,所以多线程对集合同时进行操
作,是线程安全的。

3.3.2.1 不可变集合

  得到不可变集合的方法都是以 unmodifiable 开头的。这类方法的意思是,我们会从原集合中,得到一个不可变的新集合,新集合只能访问,无法修改;一旦修改,就会抛出异常。这主要是因
为只开放了查询方法,其余任何修改操作都会抛出异常,我们以 unmodifiableList 为例来看下底层实现机制:
在这里插入图片描述

3.4、Objects

  对于 Objects,我们经常使用的就是两个场景,相等判断和判空。

3.4.1 相等判断

  Objects 有提供 equals 和 deepEquals 两个方法来进行相等判断,前者是判断基本类型和自定
义类的,后者是用来判断数组的,我们来看下底层的源码实现:

在这里插入图片描述

从源码中,可以看出 Objects 对基本类型和复杂类型的对象,都有着比较细粒度的判断,可以
放心使用。

3.4.2 为空判断

在这里插入图片描述

Objects 提供了各种关于空的一些判断,isNull 和 nonNull 对于对象是否为空返回 Boolean
值,requireNonNull 方法更加严格,如果一旦为空,会直接抛出异常,我们需要根据生活的场
景选择使用。

3.5、面试题
3.5.1 工作中有没有遇到特别好用的工具类,如何写好一个工具类
答:有的,像 Arrays 的排序、二分查找、Collections 的不可变、线程安全集合类、Objects
的判空相等判断等等工具类,好的工具类肯定很好用,比如说使用 static final 关键字对方法进
行修饰,工具类构造器必须是私有等等手段来写好工具类。
3.5.2 写一个二分查找算法的实现
答:可以参考 Arrays 的 binarySearch 方法的源码实现。
3.5.3 如果我希望 ArrayList 初始化之后,不能被修改,该怎么办
答:可以使用 Collections 的 unmodifiableList 的方法,该方法会返回一个不能被修改的内部
类集合,这些集合类只开放查询的方法,对于调用修改集合的方法会直接抛出异常。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值