笔试收获

参加了一次笔试,收获颇多。 不同的输入方法 和力扣不一样,一些大厂的笔试包括面试时的算法题都是 ACM 模式,也就是需要自己处理输入,然后对输入处理并输出,而力扣是核心代码模式,不用你管输入,只需要实现指定方法,平台自会调用你的方法。在参加之前,我去卡码网上了解了一下,发现还是 Scanner 那一套,也就没在意,但当我真正开始做的时候感到有点不对,Scanner 恐怕不太够用。这篇博客主要讲一下 Java 里的输入流。 Java 的输入体系建立在 流 的概念之上,主要有两个抽象基类:InputStream 和 Reader ,前者为字节流,处理原始字节数据,如图片、音频等;后者为字符流,处理字符数据,如文本文件、控制台输入等。两者之间通过 InputStreamReader 作为桥梁,将字节流转换为字符流。 InputStream 字节流 InputStream 类定义了所有字节输入流必须实现的核心办法。包括 int read() , int read(byte[] b) , void close()等方法。 Java 的 IO 体系采用了装饰器模式,意味着可以通过组合不同的流来实现复杂的功能。这里将 InputStream 的子类大致分为两类。 第一种,这种类直接与特定的数据源进行交互,比如 FileInputStream ByteArrayInputStream 。 第二种,这种类不直接连接数据源,而是通过继承装饰器基类 FilterInputStream 来包装一个已存在的 InputStream ,给它提供一些额外的功能,比如缓冲、数据类型转换等。这种类的典型代表有 BufferedInputStream DataInputStream 等。 Reader 字符流 Reader 类是字符输入流的抽象基类,定义的方法有 int read() , int read(char[] cbuf) , void close() 等。 它的子类和 InputStream 一样,也分两种,这里不再多说。但它有一个特别的子类: InputStreamReader 从它的类名就能看出来,它是字节流到字符流的单向桥梁,它可以读取字节,然后根据指定的字符编码将其解码为字符。 System.in 它是 System 类里的一个静态变量,类型是 InputStream ,所以它是一个字节流。如果需要按字符或行读取,通常需要对其包装,比如刚才提到的 InputStreamReader 和常见的 Scanner。通常情况下,System.in 默认连接到控制台(键盘),同时它读取数据也是阻塞式的,程序会一直等待,直到有数据可读或者流被关闭。 ...

March 29, 2026 · 2 min · 242 words · Me

LeetCode8.字符串转换整数

一切源于一道题目:8. 字符串转换整数 (atoi) - 力扣(LeetCode) 考虑这样一个问题:给你一个数字字符串,如何在32位环境下安全的处理可能超过[-2^31, 2^31 - 1]范围的数字,不能使用64位变量临时存储。 因为我熟悉Java, 所以下面提到的数据类型都为 Java 中的数据类型。 Integer.MAX_VALUE = 2^31 - 1 = 2147483647; Integer.MIN_VALUE = -2^31 = -2147483648; 首先题目说不能使用64位变量,所以 Long 类型不能使用,那就一步一步累加,具体思路就是设置两个变量 digit, res,从头到尾遍历字符串每一位,同时计算 res = res * 10 + digit。但这样的话,累加过程中 res 可能会无征兆直接溢出,程序直接抛异常。那怎么办,介绍一下从 K神题解 里学来的方法。 整体思路也是一步一步累加,但提前预判溢出。 这里有个核心变量 bndry = 2^31 / 10 = 214748364,,它是32位有符号整数最大值去掉最后一位的结果。 在执行 res = res * 10 + digit 之前,先检查 res 与 bndry 的关系,有下面两种溢出的可能。 情况一:res > bndry ,意味着 res 乘10后,即使加上最小的数字0,结果也会超过 2147483647,溢出。 情况二:res == bndry,此时是否溢出取决于要累加的数字 digit。 如果 digit <= 7,此时正数负数都不会溢出,但等于七对正数来说已经达到最大值。 如果 digit = 8,拼接后值为 2147483648,比 Integer.MAX_VALUE 大 1,而负数即 -2147483648 还未溢出。 如果 digit > 8,正数负数都溢出。 思路还是很清晰的,在下一位拼接前进行判断可以很好的避开溢出问题。下面看一下题解代码 ...

February 15, 2026 · 2 min · 217 words · Me

Java集合源码阅读

先附上二哥网站上关于集合框架的结构图 版本为JDK21 ArrayList 扩容机制 先介绍一下 ArrayList 中的关键变量: transient Object[] elementData 底层用来存储元素的数组 private int size; 表示集合中元素的实际数量 private static final int DEFAULT_CAPACITY = 10; 默认初始容量 private static final Object[] EMPTY_ELEMENTDATA = {}; 当用户调用 new ArrayList(0) 时,elementData 会引用该数组。 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; 当用户调用默认构造参数时会引用该数组。 private Object[] grow(int minCapacity) { int oldCapacity = elementData.length; if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { int newCapacity = ArraysSupport.newLength(oldCapacity, minCapacity - oldCapacity, /* minimum growth */ oldCapacity >> 1 /* preferred growth */); return elementData = Arrays.copyOf(elementData, newCapacity); } else { return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)]; } } private Object[] grow() { return grow(size + 1); } 上面是有关扩容的源代码。 ...

January 26, 2026 · 6 min · 1242 words · Me

Java泛型

泛型定义 先看这样一个例子: public static void main(String[] args) { List list = new ArrayList(); list.add("aaa"); list.add("bbb"); list.add("ccc"); for (int i = 0; i < list.size(); i++) { System.out.println((String)list.get(i)); } } ArrayList集合中可以加入任何类型的对象,我本意是用这个集合来存储字符串(因为ArrayList默认存储的是Object类型的对象,所以输出时需要强转 ),但我粗心的写错了list.add(123),在我运行后发现报错java.lang.ClassCastException,显然报错原因就是输出时Integer类型并不能强转为String类型。那么如何避免这种情况呢,答案就是泛型。 借用一下维基百科的定义: 泛型程序设计是程序设计语言的一种风格或范型,泛型允许程序员在强类型程序设计语言中 编写代码时 使用一些 以后才指定的类型,在实例化时作为参数指明这些类型。 说一下我的理解,泛字让我联想到了一个词语:泛泛而谈,指那些浮浅平淡,不深入的谈话。这里也可以这样理解,定义时我模糊的说明一下参数,不指明参数具体是哪种类型,等我使用时再说明。 有这样一种说法:泛型的本质就是“参数化类型”。想象一下,定义一个方法需要形参,待你调用时,又需要传递实参。泛型亦是如此,定义时形式上意思意思,真正使用时再说明。其实都一个意思。 下面我将从泛型类、泛型方法、泛型接口、通配符、类型擦除五个方面来对Java泛型进行详细说明。 泛型类 来看一下ArrayList这个类的定义 public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {...} 相比其他普通类,这个类的类名称后面多了个<E>,这就是泛型类的核心标识,其中 E 为类型参数,理论上可以为任何字母,但有一些约定俗成的习惯 T:代表一般的任何类 E:element 元素的意思 K:代表 key 的意思 V:代表 value 的意思,经常和 K 搭配作为键值对 关于类型参数怎么用,来看个例子。 ...

October 14, 2025 · 4 min · 741 words · Me

正则表达式

介绍 正则表达式(regular expression),常简写为regex,用简单字符串来描述、匹配文中全部匹配指定格式的字符串。人话讲就是根据一些规则制定一个字符串,然后你可以用这个字符串来筛选满足规则的字符串。许多程序设计语言都支持用正则表达式操作字符串,这里主要介绍正则表达式在Java中的运用。 不同编程语言的正则表达式引擎有所不同,这里提供一个链接,里面详细介绍了不同语言对各种特性的支持程度。 快速使用 先说明一下\的使用。 在Java普通字符串中,反斜杠\本身就是转义字符,比如\n被转义为"换行符",又比如\\被转义为\。而正则表达式也有自己的语法,它也使用反斜杠作为转义字符,比如\d表示“匹配一个数字”。 那么二者结合起来呢🧐。以"\\d"为例。编译器看到字符串"\\d"会根据字符串规则将其转换为两个字符,一个\,一个d。接下来正则表达式引擎会对其进行解析,最终生效的正则模式就是\d。可以这样理解:正则表达式需要 \d 来匹配数字。但在Java字符串里,一个 \ 需要写成 \\。所以,要把正则的 \d 放到Java字符串里,就变成了 \\d。 到底需不需要两个\\,idea会给你答案。 java.util.regex包是Java标准库中用于支持正则表达式操作的包,主要涉及到Pattern和Matcher这两个类的操作。这里有个简单的例子: String pattern = "java\\d"; String text1 = "java1"; String text2 = "javaBad"; Pattern p = Pattern.compile(pattern); Matcher m = p.matcher(text1); System.out.println(m.matches());//true m = p.matcher(text2); System.out.println(m.matches());//false 先调用Pattern类的静态方法compile(参数为正则表达式)生成一个实例对象,通过调用该对象的matcher方法(参数为待匹配文本)生成一个Matcher实例。接下来就有很多方法供你选择,这里我调用的是matches方法来输出布尔值,在例子中体现为字符串Java后面能否匹配上数字。Matcher类里还有个find方法也很常见,下文会提到。 匹配规则详解 简单匹配 为方便演示,接下来的示例代码使用String类的matches方法,该方法底层原理仍然是Pattern和Matcher这两个类的使用,后面有详细说明。下面示例参考廖雪峰和菜鸟教程。 匹配任意字符:.可以匹配除\r\n之外的任何单个字符。如a.c可以匹配abc但不能匹配abbc和ac 匹配数字:\d匹配 0~9 的数字,同样只匹配一个字符。匹配非数字:\D匹配非数字。 匹配常用字符:\w可以匹配一个字母、数字或下划线 匹配空格字符:\s可以匹配任何空白字符,包括空格、制表符、换页符等。与[\f\n\r\t\v]等效。\W和\S和\D同样是反着来的。 重复匹配: *可以匹配任意个字符,包括0个字符。 +可以匹配至少一个字符。比如A\d+可以匹配A11111和A0。但不能匹配A,因为至少一个字符。 ?可以匹配0个或一个字符。 如果想精确指定n个字符,使用{n},比如A\d{3}可以匹配到A123。指定匹配n~m个字符,用{n,m}, 例如A\d{3,5}可以精确匹配A123 A1234 A12345。{n,}表示可以匹配至少n个字符。m和n为非负整数,其中n <= m。再举一个例子:o{2}和Bob中的一个o不匹配,而匹配food中的两个o。不同表达式可能是等效的,比如o{0,1}和o? 来个综合点的例子:假如电话号码规则如下:34位数字表示区位,78位数字表示电话,中间用-连接。答案:\\d{3,4}-\\d{7,8}。对于连字符-,一般情况下只是一个普通字符,不需要进行转义,当然写上两个反斜杠也是对的,idea会给出提示移除多余的反斜杠。 String pattern = "\\d{3,4}-\\d{7,8}";//不知道需不需要写\?idea会给你的答案 String text1 = "0123-123456"; String text2 = "010-1234567"; System.out.println(text1.matches(pattern));//false System.out.println(text2.matches(pattern));//true 复杂匹配: 匹配开头和结尾:^匹配输入字符串开始的位置,$匹配输入字符串结束的位置。他们俩的作用是将匹配过程限制在整个字符串上,避免了在子串中成功匹配的情况。其实matches()方法的行为已经隐含了^...$锚点的效果,而find()方法则没有。matches方法尝试将整个输入序列与模式匹配,而find方法会在输入序列中查找下一个与模式匹配的子序列。仔细品味这两个方法的名字,你也许会理解。 ...

September 22, 2025 · 2 min · 229 words · Me

Java反射

什么是反射 在spring项目中,只需要写个@Service或者@Component,然后在别的地方用@Autowired声明一个接口变量,Spring就能返回给我们一个实现了该接口的具体对象。这是如何实现的呢?它不可能在编译时就知道加了注解的类与类之间的关系,所以只能是在程序启动运行时,Spring动态地发现了这些类,读取了他们的结构,然后创建对象。这背后的技术支撑又是什么?答案就是反射。 反射是Java提供的一种在程序运行时 检查/获取类、接口、字段、方法、构造器等结构信息的能力。 操作/调用对象、字段、方法的能力。 它就像一面镜子,让程序在运行时“照见”自己的结构。 反射的基石:Class对象 编译器在编译 Java 源代码时会生成 .class 文件(字节码文件)。当 JVM 需要用到某个类时,它的类加载器会读取并解析对应的 .class 文件,在方法区(或元空间)构建该类的运行时数据结构,同时在堆内存中创建一个代表该类的 java.lang.Class 对象。每个被加载的类在 JVM 中都有且只有一个对应的 Class 对象(在同一个类加载器命名空间内)。 这里的Class是一个类的名字,不要和class关键字搞混。 有三种方法获取Class对象 Person person = new Person("me", 20); //法一:通过对象实例 Class< ? extends Person> clazz1 = person.getClass(); //法二:自带属性(基本数据类型也有) Class<Person> clazz2 = Person.class; //法三:Class类的静态方法 forName try { Class<?> clazz3 = Class.forName("org.myblog.reflection.Person"); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } 解释一下这三种方法泛型的使用:法一使用Class<? extends Person> 因为Class对象是在运行时从Person实例获取的,而Person实例的具体类型只能在运行时创建和确定,编译阶段无法判断,所以使用通配符 ,又因为person可能是Person实例,也可能是Person的子类实例,所以最终写成Class< ? extends Person>;法二使用Class<Person>因为编译时已知具体类型;法三使用Class<?>因为通过字符串动态加载类,编译时无法确定具体类型,所以使用通配符。这三个 Class 对象都是同一个(上面也提到了,一个类唯一对应一个 Class 对象)。 ...

August 10, 2025 · 3 min · 518 words · Me

Java序列化与反序列化

什么是序列化和反序列化 序列化,人话讲就是将对象转换为字节序列(也可以是JSON、XML等文本格式),反序列化就是把这个过程倒置。 下面是维基百科关于序列化的介绍 序列化(serialization)在计算机科学的资料处理中,是指将数据结构或对象状态转换成可取用格式(例如存成文件,存于缓冲,或经由网络中发送),以留待后续在相同或另一台计算机环境中,能恢复原先状态的过程。依照序列化格式重新获取字节的结果时,可以利用它来产生与原始对象相同语义的副本。对于许多对象,像是使用大量引用的复杂对象,这种序列化重建的过程并不容易。面向对象中的对象序列化,并不概括之前原始对象所关系的函数。这种过程也称为对象编组(marshalling)。从一系列字节提取数据结构的反向操作,是反序列化(也称为解编组、deserialization、unmarshalling)。 对于Java这种面向对象的编程语言来说,是对实例化后的对象进行序列化,而对于C++这种半面向对象的编程语言来说,序列化的目标不仅有对象(class)还有数据结构(struct) 序列化的使用场景 数据存储:比如序列化可以将存储在 JVM 堆区中的对象转换成字节序列,从而实现持久化。 网络通信:将对象转换为字节序列方便其在网络中进行传递和接收。 使用Java实现序列化 以 JDK 自带序列化方法为例,实现java.io.Serializable接口 @Data public class Cat implements Serializable { private int age; private String name; private Date birth; } 序列化演示: public class serializeTest { public static void main(String[] args) { Cat cat = new Cat(); cat.setName("tom"); cat.setAge(2); cat.setBirth(new Date()); //使用ObjectOutputStream将cat对象序列化并存入test1.txt文件中 try (FileOutputStream fileOutputStream = new FileOutputStream("test1.txt"); ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream)) { objectOutputStream.writeObject(cat); } catch (IOException e) { e.printStackTrace(); } } } 反序列化演示: ...

July 7, 2025 · 2 min · 340 words · Me