一个好将军,不打没有准备的仗
文章目录
- 1. 熟悉 Java 语法,具有面向对象编程思想
- 2. 熟悉JVM内存区域划分
- 3. GC原理
- 4. Python基础语法
- 5. 常见的数据结构
- 6. 常见的排序算法
- 7. MySQL增删改查 ,索引 ,事务
- 8. 多线程的基本用法,熟悉线程与进程的区别、线程API的使用、线程的状态转换
- 9. 线程不安全问题的解决办法,Synchronized、Volatile等操作
- 10、了解线程池的实现
- 10. TCP、 UDP、 HTTP 等,Servlet 的使用。
- 11. Linux 常用命令
- 12. 测试用例设计方法,测试流程,bug的生命周期,bug的描述方法。
- 13. Selenium 常用 API 的使用 ,JUnit 的使用。
- 14. Charles及接口测试工具Postman的使用,熟悉ADB命令,用ADB抓取手机日志。
1. 熟悉 Java 语法,具有面向对象编程思想
1.1 Java语言的封装继承多态
封装(Encapsulation):
封装是面向对象编程的基本原则之一,它将数据和操作数据的方法封装在一个单元中,并对外部隐藏了数据的实现细节。在Java中,封装通过使用访问修饰符(如private、public、protected)来限制对类的成员变量和方法的访问。通过封装,可以实现数据的安全性和灵活性,同时隐藏实现细节,提高了代码的可维护性和可扩展性。
继承(Inheritance):
继承是面向对象编程中实现代码重用和建立类之间关系的机制之一。Java中的继承允许一个类(子类)继承另一个类(父类)的属性和方法。通过继承,子类可以直接访问父类的非私有成员,并可以在子类中添加新的成员或重新定义父类的成员。继承提供了层次化的类结构,通过Is-A关系(子类是父类的一种类型)来描述类之间的关系,提高了代码的可读性和可扩展性。
多态(Polymorphism):
多态是指相同的方法调用可以在不同的对象上具有不同的行为。在Java中,多态可以通过方法重载(Overloading)和方法重写(Overriding)来实现。方法重载是指在同一个类中定义多个同名但参数列表不同的方法,根据调用时提供的参数类型和数量来确定调用哪个方法。方法重写是指子类重写父类的方法,使得子类可以根据自身的需要来改变方法的实现逻辑。多态可以提高代码的灵活性和可扩展性,使得代码更具有通用性和复用性。
class Animal{
public void print(){
System.out.println("i am animal");
}
}
class Cat extends Animal{
public void print(){
System.out.println("i am cat");
}
}
class Dog extends Animal{
public void print(){
System.out.println("i am dog");
}
}
public class Different_show {
public static void main(String[] args) {
Animal cat = new Cat();
Animal dog = new Dog();
cat.print();
dog.print();
}
}
在上面的示例中,Animal 是父类,而 Dog 和 Cat 是继承自 Animal 的子类。通过在子类中重写父类的 print() 方法,每个子类都有了自己独特的行为。在运行时,可以通过父类的引用变量来引用子类的对象,并执行相应的方法。这就是多态的体现,即通过父类类型的引用来调用子类的方法,实现了动态绑定。
多态的好处在于可以增加代码的灵活性和扩展性。通过使用父类的引用来引用不同的子类对象,可以在不修改客户端代码的情况下,针对不同的子类实现不同的行为。
1.2 多态与重载的区别
重写是发生在父子类之间,而重载是发生在同一个类中或继承关系中的父子类之间。
重写方法具有相同的方法名和参数列表及返回值类型,而重载方法只要求具有不同的参数列表,方法名要求相同,对方法返回值类型不做要求。
重写是子类重新定义父类方法,覆盖父类的方法实现,而重载是在同一个类中定义多个具有相同名称但参数列表不同的方法。
1.3 Java的几种数据类型
在Java中,有以下基本数据类型和引用数据类型:
基本数据类型:
byte:1字节
short:2字节
int:4字节
long:8字节
float:4字节
double:8字节
boolean:布尔类型,表示 true 或 false
char:2字节
引用数据类型:
类(Class):用于定义对象的模板,包括属性和方法。
数组(Array):用于存储同类型数据的集合。
接口(Interface):用于定义一组方法,可以被类实现。
枚举(Enumeration):用于定义一组常量。
字符串(String):用于表示文本字符串。
基本数据类型直接存储在内存中,而引用数据类型存储的是对实际数据所在内存位置的引用。
此外,在Java中还可以使用包装类(Wrapper Class)来将基本数据类型包装为对象,以便进行面向对象的操作,例如 Integer、Double、Boolean 等。
1.4 字符串常用方法
✨ 初始化一个字符串:
String str = "Hello, World!";
🔢 获取字符串长度:
int length = str.length();
✂️ 截取子字符串:
String substring = str.substring(startIndex, endIndex);
🔁 字符串替换:
String replacedStr = str.replace(oldChar, newChar);
🔎 查找字符或子字符串的位置:
int indexOfChar = str.indexOf(ch);
int indexOfSubstring = str.indexOf(substring);
🔄 字符串转换为大写或小写:
String upperCaseStr = str.toUpperCase();
String lowerCaseStr = str.toLowerCase();
✂️ 去除字符串两端的空白字符:
String trimmedStr = str.trim();
🔁 字符串拼接:
String concatenatedStr = str1 + str2;
✨ 判断字符串是否为空或空白字符:
boolean isEmpty = str.isEmpty();
boolean isBlank = str.isBlank();
✅ 判断字符串是否以指定的前缀或后缀开头/结束:
boolean startsWith = str.startsWith(prefix);
boolean endsWith = str.endsWith(suffix);
➕ 字符串分割:
String[] array = str.split(delimiter);
🔍 字符串是否包含指定的字符或子字符串:
boolean containsChar = str.contains(ch);
boolean containsSubstring = str.contains(substring);
1.5 StringBuilder 和 StringBuffer的区别
StringBuilder 和 StringBuffer 都是用于操作可变字符串的类,它们之间的主要区别在于线程安全性和性能方面。
线程安全性:
StringBuffer 是线程安全的,它的方法都被 synchronized 修饰,可以保证多线程环境下的安全操作。这意味着在多个线程同时访问和修改同一个 StringBuffer 对象时,不会发生数据不一致的问题。
StringBuilder 不是线程安全的,它的方法没有被 synchronized 修饰,因此在多线程环境下使用 StringBuilder 需要额外的同步措施来保证线程安全。
性能:
StringBuffer 的方法是线程安全的,但同步机制会带来一些额外的性能消耗。如果没有多线程的需求,使用 StringBuffer 可能会导致不必要的性能损失。
StringBuilder 的方法没有线程安全的保证,因此在单线程环境下,StringBuilder 的性能比 StringBuffer 更好,因为无需进行同步。
在大多数情况下,如果你的代码在单线程环境中运行,建议使用 StringBuilder,因为它具有更好的性能。只有在多线程环境中需要保证线程安全时,才应该使用 StringBuffer。
1.5.1 StringBuilder常用方法
StringBuilder sb = new StringBuilder(); // 创建一个空的 StringBuilder 对象
// append() 方法用于追加字符串
sb.append("Hello");
sb.append(" ");
sb.append("World");
System.out.println(sb.toString()); // 输出: Hello World
// insert() 方法用于在指定位置插入字符串
sb.insert(5, "Beautiful ");
System.out.println(sb.toString()); // 输出: Hello Beautiful World
// delete() 方法用于删除指定范围内的字符
sb.delete(6, 15);
System.out.println(sb.toString()); // 输出: Hello World
// reverse() 方法用于反转字符串
sb.reverse();
System.out.println(sb.toString()); // 输出: dlroW olleH
// replace() 方法用于替换指定范围内的字符串
sb.replace(6, 11, "Java");
System.out.println(sb.toString()); // 输出: dlroW JavaH
// length() 方法用于获取字符串长度
int length = sb.length();
System.out.println("Length: " + length); // 输出: Length: 11
// toString() 方法用于将 StringBuilder 转换为 String
String result = sb.toString();
System.out.println("Result: " + result); // 输出: Result: dlroW JavaH
2. 熟悉JVM内存区域划分
见之前的文章
https://editor.csdn.net/md/?articleId=130408523
Java虚拟机(JVM)内存区域可以分为以下几个部分:
-
程序计数器(Program Counter Register):
程序计数器是一块较小的内存区域,它可以看作是当前线程所执行的字节码的行号指示器。在任意时刻,一个线程都只执行一个方法,如果方法是非本地(Native)方法,则程序计数器的值为空(undefined)。 -
Java虚拟机栈(Java Virtual Machine Stacks):
每个线程在创建时都会被分配一个私有的Java虚拟机栈,用于存储方法的局部变量、部分结果和方法调用的栈帧。每个方法在执行时都会创建一个栈帧,在方法执行结束后,栈帧会从栈中弹出。
Java虚拟机栈可以分为多个栈帧,每个栈帧对应一个方法的运行或执行。栈帧包括局部变量表、操作数栈、动态链接、方法出口等信息。 -
本地方法栈(Native Method Stacks):
本地方法栈与Java虚拟机栈类似,区别在于本地方法栈为执行本地方法(Native Method)服务。 -
Java堆(Java Heap):
Java堆是Java虚拟机中最大的一块内存区域,被所有线程共享。它用于存储对象的实例和数组。Java堆是垃圾收集器管理的主要区域,当对象不再被引用时,垃圾收集器会回收这些对象的内存。
Java堆可以分为新生代(Young Generation)和老年代(Old Generation)。新生代用于存放新创建的对象,而老年代用于存放长时间存活的对象。 -
方法区(Method Area): 方法区也称为永久代(Permanent Generation),在Java虚拟机规范的早期版本中,方法区是存在的。在Java 8 及之后的版本,永久代被彻底移除,取而代之的是元空间(Metaspace)。
方法区用于存储已被虚拟机加载的类信息、常量、静态变量、编译器编译后的代码等数据。
类信息的主要组成部分包括:
类的名称:类的全限定名,例如"com.example.MyClass"。
类的修饰符:描述类的可见性和特性,如public、abstract、final等。
父类信息:描述类的直接父类,Java中只允许单继承,一个类只能有一个直接父类。
接口信息:描述类实现的接口,一个类可以实现多个接口。
字段信息:描述类的成员变量,包括字段的名称、类型、修饰符等。
方法信息:描述类的成员方法,包括方法的名称、参数列表、返回类型、修饰符等。
构造方法信息:描述类的构造方法,用于创建对象实例。
注解信息:描述类上使用的注解,注解可以为类提供额外的元数据。
内部类信息:描述类中定义的内部类。
其他元数据:如类的泛型信息、常量池等。
元空间(Metaspace)使用本地内存来存储类的元数据,不再受到方法区大小的固定限制。
运行时常量池(Runtime Constant Pool):
运行时常量池是方法区的一部分,它用于存放编译时生成的各种字面量和符号引用。
3. GC原理
见之前的文章
3.1 内存溢出
内存溢出(Memory Overflow)可能由多种原因引起,以下是一些常见的原因:
不合理的内存分配:如果程序频繁地分配内存且未及时释放,或者分配的内存大小远远超过程序实际需要的大小,就容易导致内存溢出。这可能是由于算法或数据结构设计问题、内存分配逻辑错误等引起的。
递归调用:递归函数在每次递归调用时会将参数、局部变量和返回地址等存储在栈内存中,如果递归调用层数过深,栈空间可能被耗尽,导致栈溢出,进而引发内存溢出。
内存泄漏:内存泄漏是指程序中已经不再使用的对象仍然被占用内存,而无法被垃圾回收器回收释放。如果频繁发生内存泄漏,最终会导致可用内存逐渐减少,最终引发内存溢出。
大对象、大数组:如果程序中创建了大量的大对象或大数组,并且无法及时释放它们占用的内存空间,那么内存溢出可能发生。
导致内存泄漏的外部资源:在程序中使用了外部资源,如文件、网络连接、数据库连接等,如果这些资源没有被正确释放,就会导致内存泄漏,并可能引发内存溢出。
虚拟机限制:虚拟机分配给程序的内存有一定的限制。如果程序本身需要的内存超过了虚拟机配置的最大内存限制,就会发生内存溢出。
3.2 内存泄漏
内存泄漏(Memory Leak)通常是由以下一些常见原因引起的:
对象引用未及时释放:当一个对象无法被访问到时(通常是因为对该对象的所有引用都已丢失),但它仍然占据着内存空间,这就会导致内存泄漏。例如,在使用动态内存分配时,如果忘记调用释放内存的函数(如C/C++中的free()或delete),那么分配的内存将永远无法释放。
长生命周期的对象引用:如果某些对象在其本来应该释放的时候仍然持有对其他对象的引用,那么这些对象将无法被垃圾回收器回收。这可能是由于缓存、事件监听器、回调函数等原因造成的。如果不正确地管理这些引用,就可能导致内存泄漏。
静态集合类的使用不当:静态集合类(如List、Map等)是常见的存储对象的容器,如果向这些集合中添加对象但未从中移除,那么这些对象将一直存在于内存中,即使它们已不再需要,也不会被垃圾回收器回收。
线程泄漏:在多线程应用程序中,如果创建的线程未正确终止或释放,那么这些线程及其关联的资源将一直存在于内存中,导致内存泄漏。
资源未释放:除了内存以外,还有其他资源(如文件、数据库连接、网络连接等)也需要正确释放。如果在使用完这些资源后忘记释放,就可能导致内存泄漏。
对象缓存不当:在某些场景下,缓存对象可以提高性能。然而,如果缓存的对象不被正确管理,超出了实际需要的范围,就可能导致内存泄漏。
https://blog.csdn.net/scsery/article/details/130408523?ops_request_misc=&request_id=4ccb42bf7f6647228c0ca1543cd4da73&biz_id=&utm_medium=distribute.pc_search_result.none-task-blog-2blogkoosearch~default-1-130408523-null-null.268v1control&utm_term=GC&spm=1018.2226.3001.4450
4. Python基础语法
5. 常见的数据结构
5.1 顺序表
(Array):具有固定长度的连续内存空间,用于存储相同类型的元素。
初始化一个顺序表:
ArrayList<Type> sequenceList = new ArrayList<>();
插入元素到顺序表:
sequenceList.add(element); // 在顺序表末尾插入元素
sequenceList.add(index, element); // 在指定位置插入元素
删除顺序表中的元素:
sequenceList.remove(index); // 删除指定位置上的元素
sequenceList.remove(element); // 删除第一个匹配的元素
查找元素在顺序表中的位置:
int index = sequenceList.indexOf(element); // 查找元素第一次出现的位置
更新顺序表中的元素:
sequenceList.set(index, newElement); // 将指定位置上的元素替换为新元素
获取顺序表的长度:
int size = sequenceList.size();
🔄 遍历顺序表中的元素:
for (Type element : sequenceList) {
// 处理元素
}
5.2 链表
(Linked List):由节点组成的集合,每个节点包含数据和指向下一个节点的引用。有单向链表、双向链表和循环链表等变体。
:
💡 初始化一个链表:
LinkedList<Type> linkedList = new LinkedList<>();
✨ 在链表头部插入元素:
linkedList.addFirst(element);
✨ 在链表尾部插入元素:
linkedList.addLast(element);
✂️ 删除链表头部的元素:
linkedList.removeFirst();
✂️ 删除链表尾部的元素:
linkedList.removeLast();
🔍 查找元素在链表中的位置:
int index = linkedList.indexOf(element);
🔄 更新链表中的元素:
linkedList.set(index, newElement);
🔢 获取链表的长度:
int size = linkedList.size();
🔄 遍历链表中的元素:
for (Type element : linkedList) {
// 处理元素
}
5.3 栈
栈(Stack):具有后进先出(LIFO)特性的数据结构,只能在栈顶进行插入和删除操作。
💡 初始化一个栈:
Stack<Type> stack = new Stack<>();
✨ 入栈操作(将元素压入栈顶):
stack.push(element);
✂️ 出栈操作(移除并返回栈顶元素):
Type topElement = stack.pop();
🔍 查看栈顶元素但不移除:
Type topElement = stack.peek();
🔢 获取栈的大小:
int size = stack.size();
🔄 判断栈是否为空:
boolean isEmpty = stack.isEmpty();
🔄 清空栈:
stack.clear();
🔄 遍历栈中的元素:
for (Type element : stack) {
// 处理元素
}
5.4 队列
(Queue):具有先进先出(FIFO)特性的数据结构,支持在队尾插入元素,在队首删除元素。常见的队列有普通队列、双端队列和优先队列等。
💡 初始化一个队列:
Queue<Type> queue = new LinkedList<>();
✨ 入队操作(将元素添加到队尾):
queue.offer(element);
✂️ 出队操作(移除并返回队头元素):
Type headElement = queue.poll();
🔍 查看队头元素但不移除:
Type headElement = queue.peek();
🔢 获取队列的大小:
int size = queue.size();
🔄 判断队列是否为空:
boolean isEmpty = queue.isEmpty();
🔄 清空队列:
queue.clear();
🔄 遍历队列中的元素:
for (Type element : queue) {
// 处理元素
}
哈希表(Hash Table):通过哈希函数将键映射到值的数据结构,提供快速的插入、删除和查找操作。Java中的HashMap和Hashtable就是哈希表的实现。
import java.util.HashMap;
import java.util.Map;
public class HashTableDemo {
public static void main(String[] args) {
// 创建一个哈希表
Map<String, Integer> hashMap = new HashMap<>();
// 插入元素
hashMap.put("Alice", 25);
hashMap.put("Bob", 30);
hashMap.put("Charlie", 35);
// 获取元素
int age = hashMap.get("Alice");
System.out.println("Alice's age: " + age);
// 更新元素
hashMap.put("Alice", 26);
age = hashMap.get("Alice");
System.out.println("Updated Alice's age: " + age);
// 删除元素
hashMap.remove("Bob");
// 检查元素是否存在
boolean isCharlieExists = hashMap.containsKey("Charlie");
System.out.println("Charlie exists in the hash table: " + isCharlieExists);
// 获取哈希表的大小(元素个数)
int size = hashMap.size();
System.out.println("Hash table size: " + size);
// 遍历哈希表的键值对
for (Map.Entry<String, Integer> entry : hashMap.entrySet()) {
String name = entry.getKey();
int personAge = entry.getValue();
System.out.println(name + "'s age: " + personAge);
}
// 清空哈希表
hashMap.clear();
// 检查哈希表是否为空
boolean isEmpty = hashMap.isEmpty();
System.out.println("Hash table is empty: " + isEmpty);
}
}
集合(Set):无序且不包含重复元素的数据结构。Java中的HashSet和TreghveSet是Set接口的实现。
映射(Map):存储键值对的数据结构,键是唯一的,而值可以重复。Java中的HashMap和TreeMap是Map接口的实现。
树(Tree):具有层次结构的数据结构,包括二叉树、二叉搜索树、红黑树等。Java中的TreeSet和TreeMap就是基于树结构实现的。
5.5 二叉树
class Node {
int data;
Node left;
Node right;
public Node(int data) {
this.data = data;
this.left = null;
this.right = null;
}
}
class BinaryTree {
Node root;
public BinaryTree() {
this.root = null;
}
// 插入节点
public void insert(int data) {
root = insertRec(root, data);
}
private Node insertRec(Node root, int data) {
if (root == null) {
root = new Node(data);
return root;
}
if (data < root.data) {
root.left = insertRec(root.left, data);
} else {
root.right = insertRec(root.right, data);
}
return root;
}
// 遍历二叉树
public void traverseInOrder() {
traverseInOrderRec(root);
}
private void traverseInOrderRec(Node root) {
if (root != null) {
traverseInOrderRec(root.left);
System.out.print(root.data + " ");
traverseInOrderRec(root.right);
}
}
// 查找节点
public boolean search(int data) {
return searchRec(root, data);
}
private boolean searchRec(Node root, int data) {
if (root == null) {
return false;
}
if (root.data == data) {
return true;
} else if (data < root.data) {
return searchRec(root.left, data);
} else {
return searchRec(root.right, data);
}
}
}
public class Main {
public static void main(String[] args) {
BinaryTree tree = new BinaryTree();
// 插入节点
tree.insert(5);
tree.insert(3);
tree.insert(7);
tree.insert(2);
tree.insert(4);
// 遍历二叉树
System.out.print("In-order Traversal: ");
tree.traverseInOrder();
System.out.println();
// 查找节点
int searchData = 4;
boolean found = tree.search(searchData);
if (found) {
System.out.println(searchData + " found in the binary tree.");
} else {
System.out.println(searchData + " not found in the binary tree.");
}
}
}
6. 常见的排序算法
见之前的文章
7. MySQL增删改查 ,索引 ,事务
7.1 聚合查询
之前的文章
数据库的聚合查询是指通过使用聚合函数对数据进行计算和统计的查询操作。聚合函数是用于对一组数据进行计算的函数,常见的聚合函数包括:
COUNT:统计某个列或表的行数。
SUM:计算某个列或表的数值总和。
AVG:计算某个列或表的平均值。
MAX:找出某个列或表的最大值。
MIN:找出某个列或表的最小值。
聚合查询常与 GROUP BY 子句结合使用,用于对数据进行分组并对每个分组进行聚合计算。GROUP BY 子句根据指定的列将数据分成多个组,然后对每个组应用聚合函数进行计算。
例如,以下是一个使用聚合查询的示例:
SELECT department, COUNT(*) AS total_employees, AVG(salary) AS average_salary
FROM employees
GROUP BY department;
上述查询将根据部门对员工表进行分组,并计算每个部门的员工总数(COUNT)和平均工资(AVG)。
在进行聚合查询时,需要注意以下几点:
聚合函数通常与 SELECT 语句一起使用,可以在查询结果中显示计算结果的别名。
如果想要筛选出特定的分组结果,可以使用 HAVING 子句来指定条件。
在 GROUP BY 子句中可以指定多个列,实现多级分组。
聚合函数也可以用于全局计算,而无需使用 GROUP BY 子句。
7.2 having与where的区别
在 MySQL 中,HAVING 子句和 WHERE 子句都用于查询过滤,但它们有几个区别:
适用范围:
WHERE 子句用于对原始数据行进行过滤,即在数据被分组和聚合之前进行操作。
HAVING 子句用于对分组后的数据进行过滤,即在数据被分组和聚合之后进行操作。
使用位置:
WHERE 子句出现在 SELECT 语句中的 FROM 子句之后。
HAVING 子句出现在 SELECT 语句中的 GROUP BY 子句之后。
对象:
WHERE 子句用于筛选原始数据行。它可以基于列的值进行条件判断。
HAVING 子句用于筛选分组数据。它可以基于聚合函数的结果进行条件判断。
聚合函数:
WHERE 子句不能使用聚合函数,因为它操作的是原始数据行。
HAVING 子句可以使用聚合函数,因为它操作的是分组后的数据。
7.3 事务的特性
原子性(Atomicity):事务是一个不可分割的最小执行单位,要么全部执行成功,要么全部失败回滚。如果事务中的任何操作失败,所有已经执行的操作都会被回滚到事务开始前的状态,保持数据库的一致性。
一致性(Consistency):事务的一致性要求事务在执行过程中对数据进行的修改和操作必须遵循数据库所定义的各种约束条件、数据类型、关系模式等。这些约束条件可以包括主键约束、外键约束、唯一性约束、检查约束等。
隔离性(Isolation):
常见的事务隔离级别包括:
读未提交(Read Uncommitted):最低级别的隔离性,允许一个事务读取另一个事务尚未提交的数据。这种隔离级别可能导致脏读问题,即读取到未经提交的数据。
读已提交(Read Committed):要求一个事务只能读取已经提交的数据。这种隔离级别可以避免脏读问题,但可能出现不可重复读和幻读问题。
可重复读(Repeatable Read):要求在同一个事务中多次读取同一数据时,结果保持一致。其他事务的插入、更新和删除操作不会影响当前事务的读取结果。这种隔离级别可以避免脏读和不可重复读问题,但可能出现幻读问题。
串行化(Serializable):最高级别的隔离性,要求事务串行执行,彼此完全隔离。事务之间不能并发执行,可以避免脏读、不可重复读和幻读问题,但会牺牲并发性能。
持久性(Durability):一旦事务提交成功,其所做的修改将会永久保存在数据库中,即使发生系统故障或重启,修改的数据也不会丢失。数据库系统通过持久化技术,如日志记录和数据备份,来保持事务的永久性。
ACID 特性保证了数据库事务的可靠性和一致性,确保了数据的完整性,同时提供了并发控制和持久性保证。这使得事务能够在复杂的并发和故障环境下可靠地执行和恢复。
7.4 增删改查
INSERT INTO users (name, age, email)
VALUES ('John Doe', 25, 'john.doe@example.com');
DELETE FROM users WHERE id = 1;
UPDATE users
SET age = 30, email = 'updated@example.com'
WHERE id = 1;
SELECT name, age, email
FROM users
WHERE age > 18;
8. 多线程的基本用法,熟悉线程与进程的区别、线程API的使用、线程的状态转换
8.1 线程的API
start():启动线程,使其进入可运行状态,当得到 CPU 时间片时,开始执行线程的 run() 方法。
run():线程的主要执行逻辑,实现了 Runnable 接口中的 run() 方法。
sleep(long millis):暂停当前线程的执行,让出 CPU 时间片给其他线程执行。参数表示暂停的时间,以毫秒为单位。
yield():暂停当前正在执行的线程,使系统可以执行其他线程。调用 yield() 方法后,当前线程会从运行状态转为就绪状态。
join():等待当前线程执行完毕,然后再继续执行其他线程。
interrupt():中断线程,给线程设置一个标志位,表示线程应该退出。具体如何处理中断,由线程的逻辑自行决定。
isInterrupted():检查线程是否被中断。
getName() 和 setName(String name):获取和设置线程的名称。
getId():获取线程的唯一标识符。
isAlive():判断线程是否存活,即线程是否启动并且尚未终止。
8.2 线程与进程的区别
定义:进程是程序在执行过程中进行资源分配和调度的基本单位,而线程是进程的一个执行流程,是CPU调度和分派的基本单位。
资源使用:每个进程都拥有独立的内存空间,包括代码、数据和堆栈等。而线程共享进程的内存空间,包括全局变量、静态变量和堆等。这意味着线程可以访问相同的变量和数据,但也需要注意并发访问带来的同步问题。
创建与销毁开销:进程创建和销毁进程的开销较大,涉及到资源分配、初始化等步骤。而线程创建和销毁线程的开销较小,相对较快。
切换速度:由于线程共享进程的资源,线程切换的速度较快,开销较小。而进程切换需要切换整个内存空间,速度较慢。
并发性:由于线程共享进程的资源,线程间通信和数据共享比较容易,适合处理并发任务。而进程之间通信需要通过额外的机制,如管道、消息队列等。
8.3 线程状态转换
线程在执行过程中会经历不同的状态转换。下面是常见的线程状态及其转换:
新建(New):创建线程对象后的初始状态。
可运行(Runnable):线程可以在多个线程中进行调度并执行,包括操作系统中的就绪状态和正在运行状态。
运行(Running):线程获得处理器资源,正在执行任务。
阻塞(Blocked):线程暂时停止执行,等待特定条件的发生(如等待I/O操作完成、等待锁释放等)。
等待(Waiting):线程暂停执行,直到接收到特定条件的通知(如调用了wait()方法)。
计时等待(Timed Waiting):线程暂停执行,直到接收到特定条件的通知,或者达到了指定的时间限制(如调用了sleep()方法或等待超时)。
终止(Terminated):线程执行完任务后终止或者出现异常终止。
线程状态之间的转换如下:
线程新建后会从"New"状态转换为"Runnable"状态。
"Runnable"状态的线程可以进入"Running"状态,开始执行任务。
"Running"状态的线程可能会被操作系统调度器切换到"Blocked"状态或者"Timed Waiting"状态。
"Blocked"和"Timed Waiting"状态的线程在条件满足后可以重新进入"Runnable"状态。
"Runnable"状态的线程可能会被操作系统调度器切换到"Waiting"状态。
"Waiting"状态的线程在接收到条件通知后可以重新进入"Runnable"状态。
"Runnable"状态的线程最终会执行完任务并进入"Terminated"状态。
9. 线程不安全问题的解决办法,Synchronized、Volatile等操作
加锁:使用锁机制可以保证在同一时间内只有一个线程可以访问共享数据,这样可以避免多个线程同时修改数据导致不一致。常见的锁包括互斥锁(Mutex)和读写锁(ReadWriteLock)等。但要注意锁的粒度不能太细或太大,太细容易导致性能问题,太大则无法发挥并发性能。
使用原子操作:原子操作是不可中断的操作,可以保证在多线程环境下的数据操作是原子的,不会发生竞态条件。原子操作可以保证对共享数据的读写操作的完整性。
使用线程安全的数据结构:Java中提供了一些线程安全的数据结构,如ConcurrentHashMap和ConcurrentLinkedQueue等。这些数据结构在实现上采用了并发控制方法,可以安全地在多线程环境下使用。
同步控制:通过使用信号量、条件变量等同步工具,可以控制线程的执行顺序,确保线程之间协同工作,避免竞态条件和数据不一致的问题。
使用线程安全的编程模式:编写线程安全的代码是解决线程不安全问题的根本方法。可以使用诸如不可变对象、线程局部变量等编程模式来避免对共享数据的直接修改。
10、了解线程池的实现
10. TCP、 UDP、 HTTP 等,Servlet 的使用。
QSI七层模型
1.物理层
物理层是网络通信的第一层,它负责将比特流(0和1)转换为电信号、光信号或无线电信号等物理信号,以便在网络中传输。物理层的主要任务是定义数据传输的物理介质、数据传输的速率、数据传输的编码方式、数据传输的时序和数据传输的距离等。
物理层的主要功能包括:
传输比特流:将比特流转换为物理信号,以便在网络中传输。
定义物理介质:定义数据传输的物理介质,如双绞线、光纤、无线电波等。
定义数据传输的速率:定义数据传输的速率,即比特率,如1Mbps、10Mbps、100Mbps等。
定义数据传输的编码方式:定义数据传输的编码方式,如曼彻斯特编码、差分曼彻斯特编码等。
定义数据传输的时序:定义数据传输的时序,如同步传输、异步传输等。
定义数据传输的距离:定义数据传输的距离,如LAN、MAN、WAN等。
2.数据链路层
数据链路层是网络通信的第二层,它负责将物理层传输的比特流组织成帧(Frame),并在帧之间建立逻辑连接,以便在网络中传输。数据链路层的主要任务是提供可靠的数据传输,保证数据的完整性、可靠性和有序性。
数据链路层的主要功能包括:
帧的封装和解封:将网络层传来的数据封装成帧,以便在物理层进行传输;同时,将接收到的帧解封,以便在网络层进行处理。
帧的传输和接收:将封装好的帧传输到目标节点,同时接收来自源节点的帧。
帧的错误检测和纠正:在帧传输过程中,对帧进行差错检测和纠正,以保证数据的完整性和可靠性。
帧的流量控制和错误控制:对帧的传输进行流量控制和错误控制,以保证数据的有序性和可靠性。
帧的地址识别和访问控制:对帧进行地址识别和访问控制,以保证数据的安全性和隔离性。
数据链路层的主要作用是将物理层传输的比特流组织成帧,并在帧之间建立逻辑连接,以保证数据的可靠传输。它是网络通信中非常重要的一层,常用的数据链路层协议包括以太网、令牌环网、PPP等
以太网常用于局域网中,例如家庭、办公室、学校等场景,它支持多种传输介质,如双绞线、光纤等,速率可以达到10Mbps、100Mbps、1Gbps等。以太网的优点是成本低、易于安装和维护,因此被广泛应用于各种场景。
令牌环网常用于局域网中,例如工厂、医院、航空管制等场景,它采用环形拓扑结构,数据传输是基于令牌的,每个节点必须等待令牌才能发送数据。令牌环网的优点是具有较高的可靠性和稳定性,因为每个节点都有机会发送数据,同时也能避免冲突和数据丢失的问题。
PPP常用于建立两个网络节点之间的连接,例如拨号上网、VPN等场景,它的优点是支持多种物理介质,如串口、ISDN、DSL等,同时也支持数据压缩、加密等功能,可以提高数据传输的效率和安全性。
3.网络层
网络层是计算机网络体系结构中的一层,主要负责将数据包从源主机传输到目的主机,它的主要功能包括:
路由选择:网络层根据路由算法选择最佳的路径将数据包从源主机传输到目的主机。
分组转发:网络层将数据包分成较小的数据包进行传输,并在传输过程中进行转发。
网络寻址:网络层使用IP地址来标识网络中的主机和设备。
流量控制:网络层可以控制数据包的流量,防止网络拥塞。
差错控制:网络层可以检测和纠正数据包传输过程中的错误。
常见的网络层协议包括:
IP协议:Internet协议是网络层中最重要的协议之一,它使用IP地址来标识网络中的主机和设备,并且可以实现不同网络之间的通信。
ICMP协议:Internet控制消息协议是IP协议的一个补充协议,它用于在网络中传输错误消息和控制消息。
ARP协议:地址解析协议用于将IP地址转换为物理地址,以便在局域网中进行通信。
RARP协议:反向地址解析协议用于将物理地址转换为IP地址。
OSPF协议:开放最短路径优先协议是一种路由协议,用于在网络中选择最佳的路径进行数据包传输。
4.传输层
OSI传输层的主要功能包括:
分段和重组数据:传输层将应用层的数据分成更小的数据段,以便在网络中传输,并在接收端重新组装这些数据段。
端到端的可靠性:传输层通过使用确认和重传机制来确保数据的可靠传输,以便在数据在传输过程中丢失或损坏时进行恢复。
流量控制:传输层通过使用滑动窗口协议来控制数据的发送速率,以避免网络拥塞和数据丢失。
多路复用和分解:传输层可以同时处理多个应用程序的数据,并将它们分解成单独的数据流,以便在网络中传输。
连接管理:传输层可以建立、维护和终止端到端的连接,以确保数据的正确传输。
常见的OSI传输层协议包括:
TCP(传输控制协议):TCP是一种面向连接的协议,提供可靠的数据传输和流量控制功能。TCP使用三次握手建立连接,并使用确认和重传机制来确保数据的可靠传输。
UDP(用户数据报协议):UDP是一种无连接的协议,不提供可靠的数据传输和流量控制功能。UDP适用于需要快速传输数据的应用程序,如视频和音频流。
SCTP(流控制传输协议):SCTP是一种面向连接的协议,提供可靠的数据传输和流量控制功能。SCTP支持多条数据流和多个IP地址,适用于需要高可靠性和高带宽的应用程序,如VoIP和视频会议。
DCCP(数据报传输控制协议):DCCP是一种无连接的协议,提供可靠的数据传输和流量控制功能。DCCP适用于需要可靠传输和流量控制的应用程序,如实时多媒体流。
5.会话层
在OSI模型中,会话层(Session Layer)的功能是为应用程序之间的会话提供管理和控制,确保它们之间的通信能够顺利进行。会话层的主要功能包括:
建立和终止会话:会话层负责建立和终止两个节点之间的会话,包括协商会话参数、创建和维护会话标识符等。
会话管理:会话层负责管理会话的状态、控制和同步,确保数据传输的顺序和完整性。
对话控制:会话层负责管理和控制两个节点之间的对话流程,包括请求和响应的交互、会话的控制命令等。
同步和检查点:会话层提供同步和检查点功能,以确保会话的正确进行,包括对数据传输的同步控制和错误恢复机制。
常用的协议有:
NetBIOS(Network Basic Input/Output System):用于在局域网中建立会话和传输数据的协议,常用于Windows操作系统。
RPC(Remote Procedure Call):远程过程调用协议,用于在网络上调用远程计算机上的程序或服务。
SIP(Session Initiation Protocol):用于建立、修改和终止多媒体会话的协议,常用于VoIP(Voice over IP)通信。
SSH(Secure Shell):用于远程登录和安全传输数据的协议,提供加密和认证机制。
TLS(Transport Layer Security):用于在网络上提供安全通信的协议,常用于保护HTTP、SMTP等应用层协议的数据传输。
6.表示层
OSI表示层(Presentation Layer)的主要功能是处理数据的表示和转换,以确保不同系统之间的数据能够正确解释和理解。表示层的功能包括:
数据格式化:表示层负责将应用层传输的数据进行格式化,以便于在网络中传输和解析。
数据加密和解密:表示层可以对数据进行加密和解密,以确保数据的安全性和保密性。
数据压缩和解压缩:表示层可以对数据进行压缩和解压缩,以减少数据的传输量,提高传输效率。
数据转换:表示层可以对数据进行编码和解码,以确保不同系统之间的数据能够正确解释和理解。
数据描述:表示层可以定义和描述数据的结构、格式和语法,以便接收方能够正确解析和处理数据。
在表示层中,常用的协议有:
ASCII(American Standard Code for Information Interchange):用于表示英文字符的编码标准。
EBCDIC(Extended Binary Coded Decimal Interchange Code):用于表示英文和其他语言字符的编码标准。
JPEG(Joint Photographic Experts Group):用于压缩和解压缩图像数据的标准。
MPEG(Moving Picture Experts Group):用于压缩和解压缩音视频数据的标准。
SSL/TLS(Secure Sockets Layer/Transport Layer Security):用于在网络上提供安全通信的协议,包括数据加密和解密。
7.应用层
OSI(Open Systems Interconnection)模型中的应用层是第七层,它是最靠近用户的一层,主要负责为用户提供网络服务和应用程序接口。应用层的主要功能包括:
提供网络服务:应用层负责为用户提供各种网络服务,例如电子邮件、文件传输、远程登录、Web浏览等。
应用程序接口:应用层为应用程序提供接口,使得应用程序可以通过网络进行通信和数据交换。
数据格式转换:应用层负责将应用程序的数据格式转换为网络传输所需的格式,以便在网络上进行传输。
数据加密和解密:应用层负责对数据进行加密和解密,以保证数据的安全性。
常见的应用层协议包括:
HTTP(Hypertext Transfer Protocol):用于Web浏览器和Web服务器之间的通信。
FTP(File Transfer Protocol):用于文件传输。
SMTP(Simple Mail Transfer Protocol):用于电子邮件的发送。
POP3(Post Office Protocol version 3):用于电子邮件的接收。
IMAP(Internet Message Access Protocol):也用于电子邮件的接收,但比POP3更强大。
DNS(Domain Name System):用于将域名转换为IP地址。
Telnet:用于远程登录。
SSH(Secure Shell):用于安全远程登录。
SNMP(Simple Network Management Protocol):用于网络管理。
DHCP(Dynamic Host Configuration Protocol):用于动态分配IP地址。
11. Linux 常用命令
见之前的面试题文章总结cmd
12. 测试用例设计方法,测试流程,bug的生命周期,bug的描述方法。
12.1 bug优先级
严重(Critical):该Bug会导致系统崩溃、数据损坏或无法正常运行,严重影响用户的核心功能和使用体验。需要立即修复。
高(High):该Bug会导致功能无法正常使用、数据显示错误或引发潜在的安全隐患,且无法使用临时解决方案来规避。会严重影响用户的正常操作和体验。需要在短期内修复。
中(Medium):该Bug对功能和用户体验有一定的影响,但可以通过临时的解决方案或绕过方式来规避。修复的优先级较高,但可以在较长的时间范围内解决。
低(Low):该Bug对功能和用户体验的影响较小,有些情况下可能只是一些小问题或界面上的细节,不会严重影响系统的正常使用。修复的优先级较低,可以在未来的版本中解决。
13. Selenium 常用 API 的使用 ,JUnit 的使用。
WebDriver API:用于控制和操作浏览器的API,包括启动浏览器、导航网页、定位元素等。常用方法包括:
get(url):加载指定URL的页面。
find_element(locator):通过指定的定位器定位单个元素。
find_elements(locator):通过指定的定位器定位一组元素。
send_keys(keys):输入指定的文本内容。
click():点击元素。
get_attribute(name):获取元素的属性值。
WebElement API:用于操作和获取元素的属性、内容等信息的API。常用方法包括:
send_keys(keys):向元素发送指定的文本内容。
click():点击元素。
get_attribute(name):获取元素的指定属性的值。
By API:用于指定元素的定位器,常用的定位方式包括:
By.id(id):通过元素的id属性进行定位。
By.name(name):通过元素的name属性进行定位。
By.className(className):通过元素的class属性进行定位。
By.tagName(tagName):通过元素的标签名进行定位。
By.linkText(linkText):通过链接文本进行定位。
By.partialLinkText(partialLinkText):通过部分链接文本进行定位。
By.xpath(xpathExpression):通过XPath表达式进行定位。
By.cssSelector(cssSelector):通过CSS选择器进行定位。
静态等待(Static Waits):
静态等待是在代码中固定等待一段时间,不论页面加载完毕与否。以下是一些Selenium中用于实现静态等待的API:
time.sleep(seconds):这是Python的内置方法,使代码停止执行指定的秒数。你可以导入time模块并使用该方法来实现静态等待。
动态等待(Dynamic Waits):
动态等待是在页面加载完成之前,根据特定条件等待一段时间。Selenium提供了WebDriverWait类和expected_conditions模块来实现动态等待。以下是一些用于实现动态等待的API:
WebDriverWait(driver, timeout).until(EC.condition):创建一个WebDriverWait实例,等待指定的超时时间,直到特定条件为真。driver参数是webdriver实例,timeout参数是等待的最长时间(以秒为单位),EC.condition是预期条件。
from selenium.webdriver.support import expected_conditions as EC:导入expected_conditions模块,该模块包含了许多预定义的条件用于等待。
一些预定义的条件包括:
EC.presence_of_element_located(locator):等待指定的元素出现在页面中。
EC.visibility_of_element_located(locator):等待指定的元素在页面中可见。
EC.element_to_be_clickable(locator):等待指定元素可被点击。
EC.title_is(title):等待指定的页面标题。
EC.url_contains(url):等待指定URL出现在当前页面的URL中等等。
14. Charles及接口测试工具Postman的使用,熟悉ADB命令,用ADB抓取手机日志。
- ADB常用命令
adb devices:列出连接到计算机的所有Android设备。
adb shell:以交互方式进入设备的命令行Shell。
adb install <path_to_apk>:安装一个APK文件到设备上。
adb uninstall <package_name>:卸载指定包名的应用程序。
adb push <local_path> <device_path>:将文件从计算机复制到设备。
adb pull <device_path> <local_path>:将文件从设备复制到计算机。
adb logcat:显示设备上的日志信息。
adb reboot:重启设备。
adb shell am start -n <package_name>/<activity_name>:启动指定应用程序的特定活动。
adb shell screencap <file_path>:在设备上进行屏幕截图。
adb shell input text “”:模拟输入指定的文本。
adb logcat -d:显示设备上的日志信息,并将其输出到命令行窗口中。这个命令显示完整的日志内容后即停止。
adb logcat -f <file_path>:将设备上的日志信息保存到指定的文件中。
adb logcat -c:清除设备上的日志缓存。
adb logcat -s :只显示具有指定标签的日志条目。
adb logcat -e :只显示包含指定关键字的日志条目。
adb logcat -v :指定日志输出格式。常用的格式包括:brief(简洁)、process(进程)、tag(标签)、thread(线程)、raw(原始)等。
adb logcat -b :指定日志缓冲区。常用的缓冲区包括:main(主缓冲区)、radio(无线电缓冲区)、events(事件缓冲区)等。