秋招嘎嘎乱杀
# Java 并发:如何保证线程之间的变量同步?
描述:就是变量 A 如果在线程 A 中做了修改,那么在线程 B
中应该获取到的是修改后的变量值。
# Java 并发:在 Java 中守护线程和本地线程区别?
Java 中的线程分为两种:守护线程和用户线程
其实任何线程都可以设置为守护线程和用户线程。
通过方法 Thread.setDaemon (true) 设置
区别:
执行权不同。在 Java 中,本地线程指的是使用线程库创建的线程,由操作系统管理和调度。本地线程可以与其他线程并发执行,因此其执行顺序是不可预测的。相比之下,守护线程是由 Java 虚拟机管理和调度的轻量级线程,其执行次数通常较少,而其执行顺序通常是固定的。
守护线程不管主程序是否运行,只要虚拟机还在就会一直运行。
# Spring 通知
Spring 切面的五种类型的通知
- before,前置通知,就是在一个方法被执行前被调用
- after,顾名思义,在方法执行之后被调用的通知,无论方法执行是否会成功
- after-returning:只有方法成功被执行后才会有的通知
- after-throwing: 在方法出现异常后被执行的通知
- around:在方法执行之前和之后被调用的通知
# 微服务架构
# volatile 关键字
volatile
是 Java 中的一个关键字,用于修饰变量。当一个变量被 volatile
修饰时,它具有以下特性:
- 可见性:
volatile
变量的修改会立即对其他线程可见。当一个线程修改了一个volatile
变量的值时,它会立即刷新到主内存中,而其他线程在读取该变量时会从主内存中获取最新的值。 - 有序性:
volatile
变量具有禁止指令重排序的特性。在多线程环境下,指令重排序可能会影响程序的正确性,但volatile
变量的读写操作会保持有序,不会发生乱序执行。
需要注意的是, volatile
只能保证单个变量的可见性和有序性,无法保证一组操作的原子性。如果需要保证多个操作的原子性,可以考虑使用 synchronized
关键字或 java.util.concurrent.atomic
包提供的原子类。
下面是一个示例代码,展示如何使用 volatile
修饰变量:
1 | public class Example { |
在上述代码中, flag
被声明为 volatile
,保证了在 doSomething
方法中对 flag
的修改对其他线程可见。当 setFlag
方法将 flag
设置为 true
时, doSomething
方法中的循环会退出,从而执行后续操作。
需要注意的是,虽然 volatile
可以满足大部分的可见性和有序性需求,但并不适用于所有的场景。在某些情况下,可能需要使用更强大的同步手段来确保线程安全性,如使用 synchronized
关键字或锁机制。
# synchronized 修饰变量
在 Java 中, synchronized
关键字不能直接修饰变量。 synchronized
关键字可以用于实现线程之间的同步,它可以修饰代码块、方法和静态方法,用于确保在执行同步代码时只有一个线程能够访问共享资源。
如果你想要同步访问或修改某个变量,一种常见的做法是将该变量封装在一个对象中,然后使用 Synchronized
关键字来同步访问该对象的方法或代码块。
下面是一个示例代码:
1 | public class Example { |
在上述代码中,我们将共享变量 count
封装在一个对象 lock
中,并使用 synchronized
关键字来同步访问和修改 count
。通过构造一个同步块(synchronized block)并传入 lock
对象作为锁对象,确保了对 count
的操作是同步的。只有一个线程能够同时执行被 synchronized
修饰的代码块。
需要注意的是,在同步访问和修改共享变量时,必须使用同一个锁对象。否则,不同的锁对象将无法实现线程之间的同步。
另外,Java 5 引入了 java.util.concurrent.atomic
包,它提供了一组原子类(Atomic Classes),可以用来对单个变量进行原子操作,避免了显式的锁和同步块。如果只需要对单个变量进行原子操作,可以考虑使用这些原子类。
# 多线程的上下文切换
在一组计算机的 cpu 中,多线程会共同使用,即共享。当线程数大于给程序分配的 CPU 数量时,就需要轮转使用 CPU,不同的线程切换使用 CPU 时发生的切换数据就是上下文切换。
# Java 中 wait 和 sleep 方法的不同
最大的区别是在等待时 wait 会释放锁,但是 sleep 会一直持有锁。前者用来线程交互,后者用来暂停执行。
线程的六种状态:
首先是初始态:NEW
创建一个 Thread 对象,但还未调用 start()启动线程时,该线程就处于初始态。
运行态:RUNNABLE
Java 中的运行态包括就绪态和运行态
就绪态: 该状态下的线程已经获得了执行所需要的资源,就差 CPU 的执行权获得就能运行。(所有就绪态的线程存放在就绪队列中)
运行态: 就绪态获得了 CPU 的执行权,正在执行的线程,由于一个 CPU 同一时刻只能执行一条线程,所以每个 CPU 每个时刻只能有一条运行态的线程。
阻塞态
当一条正在执行的线程请求某一资源失败时,就会进入阻塞态。而在 java 中,阻塞态专指请求锁失败时进入的状态。由一个阻塞队列存放所有阻塞态的线程。处于阻塞态的线程会不断请求它所需要的资源。一旦请求成功。就会进入就绪队列。然后等待执行。
这里讲一下资源有哪些:锁、IO 、Socket 等
等待态
当前线程中调用 wait、join、park 函数时,当前的线程就会进入等待态。有就绪队列,同时也有等待队列。这些等待态的线程就会存放在一个等待队列中。线程处于等待态表示它有需要等待其他的线程指示或者输出结果才能继续运行。(进入等待态的线程会释放 CPU 的执行权。并且释放资源。)
超时等待态
当运行中的线程调用 sleep(time)、wait、join、parkNanos、parkUntil 时。就会进入该状态。它和等待态一样,并不是因为请求不到资源,而是主动进入,并
且进入后需要其他线程唤醒;进入 该状态后释放 CPU 执行权
和 占有的资源。与等待态的区别:到了超时时间后自动进入阻塞队列,开始竞争锁。
终止态
线程执行结束后的状态。
注意:
wait () 方法会释放 CPU 执行权,还有占有的锁。
sleep (long) 方法仅释放 CPU 使用权,锁仍然占用。线程被放入超时等待队列中,与 yied 相比,它会使线程较长时间得不到运行。
yield () 方法仅释放 CPU 执行权,锁仍然被占用。线程就会被放入就绪队列。会在短时间内再次执行。
wait 和 notify 必须配套使用,即必须使用同意一把锁调用。
wait 和 notify 必须放在一个同步块中调用 wait 和 notify 的对象必须是他们所处同步块的锁对象。
# 线程池是什么框架建立的
从 JDK1.5 开始,JavaAPI 提供了 Executor 框架让你可以创建不同的线程池。
# 如何检测一个线程是否拥有锁?
在 Java.lang.Thread 中有一个方法叫做 holdLock (),它返回 true 如果当且仅当当前线程拥有某个具体的对象锁
# 你对线程优先级的理解是什么?
每个线程都有优先级,一般来说,高优先级的线程在运行时会有优先权。这依赖于线程调度的实现。
线程优先级是一个 int 变量(1-10),1 代表最低优先级,10 代表最高优先级。
注:一般情况下,不需要设置线程优先级。
# 并发编程的三要素
- 原子性
- 可见性
- 有序性
# 四种线程池的创建
- newCachedThreadPool 创建一个可缓存的线程池
- newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数
- newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行
- newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务
# 线程池的优点
- 重用存在的线程,减少对象创建销毁的开销
- 可有效的控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞
- 提供定时执行、定期执行、单线程、并发书控制等功能
# synchronized 的作用
synchronized 关键字是用来控制线程同步的,
# volatile
Java 提供 volatile 关键字来保证可见性
当一个共享变量被 volatile 修饰时,它会保证修改的值会立即被更新到主存中,当有其他线程需要读取时,它会去内存中读取新的值
在实际应用中,volatile 的一个重要作用就是和 CAS 共同作用,保证原子性
# CAS
compare and swap, 就是常说的比较交换
是一种基于锁的操作,且是乐观锁
# 线程 B 怎么知道线程 A 修改了变量
- volatile 修饰变量
- synchronized 修饰变量的方法
- wait/notify
- while 轮询
# SynchronizedMap 和 ConcueeentHashMap 的区别
SynchronizedMap 每次只允许一个线程来访问 Map
ConcurrentHashMap 使用分段锁保证多线程下的性能
所谓分段锁就是一次锁一个桶,ConcurrentHashMap 默认把 hash 表分为 16 个桶,然后使用 get,put,remove 等常用操作只锁住当前的桶,
桶的解释:在 ConcurrentHashMap 中,一个桶(bucket) 实际上就是一个链表。当插入一个元素时,先根据 key 的 hash 值确定插入到哪一个桶中,如果这个桶中已经有了元素存在了(存在的意思是 hash 值相同),那么新的元素就会被加入到这个桶的链表尾部。这样的话,同一个桶中的元素就具有相同的 hash 值了。
分段锁(Segmentation Lock) 是 ConcurrentHashMap 的一种策略。主要思想就是将一个大的数据结构(如 Map) 拆分为多个小的数据结构。每个小的数据结构都有一把自己的锁。这样的话,当线程访问该数据结构时,只需要锁定与自己相关的小数据结构,而不是整个大的数据结构。由此实现了多线程的并发访问。在 ConcurrentHashMap 中,这个小的数据结构就是桶。
# 怎样检测一个线程是否拥有锁
holdsLock () 方法,
# Java 线程数过多会造成什么异常?
1、 线程的生命周期开销非常高
2、 资源消耗高
3、 降低虚拟机的稳定性
# JVM 的启动参数
# Mybatis: #{} 和 ${} 的区别?
说明:#{} 是预编译处理,${} 是字符串替换
Mybatis 在处理 #{} 时,会将 sql 中的 #{} 替换为?号,调用 PreparedStatement 的 set 方法来赋值。
# 说明:PreparedStatement
PreparedStatement 是 Java JDBC API 中的一个接口,用于预编译 SQL 语句并将参数传递给数据库。与 Statement 相比,PreparedStatement 具有以下优点:
- 预编译:PreparedStatement 对象在执行前会将 SQL 语句发送到数据库进行预编译,这样可以提高 SQL 执行效率。
- 安全性:通过占位符(?)来表示参数,可以防止 SQL 注入攻击,同时也减少了手动拼接 SQL 语句的错误风险。
- 可维护性:使用 PreparedStatement 可以使代码结构更清晰,易于维护和修改。
使用 PreparedStatement 需要以下步骤:
- 创建 PreparedStatement 对象:通过 Connection 对象的 prepareStatement 方法创建 PreparedStatement 对象,并传入带有占位符的 SQL 语句。
- 设置参数:使用 PreparedStatement 对象的 setXXX 方法(如 setString、setInt 等)设置 SQL 语句中的占位符对应的参数值。
- 执行 SQL 语句:使用 PreparedStatement 对象的 execute、executeQuery 或 executeUpdate 方法执行 SQL 语句。
下面是一个简单的示例代码:
1 | // 假设已经建立了合适的数据库连接 Connection conn |
使用 PreparedStatement 可以方便地执行带有参数的 SQL 语句,提高了代码的效率和安全性。
Mybatis 在处理 {} 替换成变量的值。
使用替换为?的可以有效防止 SQL 注入。
# 锁的类别
1、 表级锁:开销比较小,不会出现死锁,并发度比较低
2、 行级锁:开销比较大,会出现死锁,并发度比较高
3、 页面锁:开销一般,会出现死锁,并发度一般
# MyISAM 和 InnoDB 的区别
MyISAM:
不支持事务,支持表级锁(就是每次操作都会对整个表上锁)
InnoDB:
支持 ACID 的事务,支持事物的四种隔离级别
支持行级锁以及外键的约束,因此可以支持写并发
原子性
一致性
隔离性
持久性
# char 和 varchar 的区别
1、 char 和 varchar 存储和检索的方式不同
2、 char 的长度固定为声明时的长度,范围是 1 到 255。
# 防止 SQL 注入的方式
产生 SQL 注入的原因:
不注意规范的书写 sql 语句和对特殊字符进行过滤,导致客户端可以通过局部变量 POST 和 GET 提交一些 sql 语句并且正常执行
方式:
1、 开启配置文件中的 magic_quotes_gpc 和 magic_quotes_runtime 设置
2、 执行 sql 语句时使用 addslashes 进行 sql 语句转换
3、 Sql 语句书写尽量不要省略双引号和单引号
4、 过滤掉 sql 语句中的一些关键字,update、insert、delete、select
提高数据库表和字段的命名技巧,对于一些重要的字段根据程序的特点命名,取不易被猜到的名字。
# SQL 优化的方式
1、 在 Where 中,表之间的连接需要写在其它 where 条件之前,
2、 用 exists 替换 in、用 not exists 替换 not in
3、 避免在建立了索引的列上面运算
4、 不要在建立了索引的列上面使用 is null 和 is not null
# Mybatis 的优势
1、 基于 SQL 语句编写,相当灵活,不会对应用程序或者数据库现有的设计造成影响,SQL 写在 XML 中,解除了 SQL 与代码的耦合,可以更加方便的进行服务外部配置,并且支持动态 SQL.
2、 相比于 JDBC, 消除了 JDBC 的大量代码,并且不需要手动连接。
(注:不需要手动连接的原因,在使用 JDBC 进行数据库连接操作时,需要手动地建立和关闭连接,整个操作过程涉及数据库驱动,创建 Connection 对象,设置连接参数,处理 SQLException 等操作。
而 Mybatis 简化了整个过程,它提供了一个 SqlSessionFactory 对象。我们通过使用这个对象来获取 SqlSession。然后就可以执行 SQL 对象了。SqlSessionFactory 和 SqlSession 都会自动管理数据库连接的创建和关闭,我们不需要手动处理这些细节。
具体来说,MyBatis 在内部使用了连接池(Connection Pool)来管理数据库连接。当我们需要执行 SQL 语句时,MyBatis 会从连接池中获取一个可用的连接,执行完毕后再把连接放回连接池。如果连接池中没有可用的连接,MyBatis 会自动创建新的连接。当连接不再使用时,MyBatis 会自动关闭连接,释放资源。
所以,"不需要手动连接" 意味着我们不需要手动创建和关闭数据库连接,MyBatis 会自动处理这些操作,使得代码更加简洁和易于管理。)
3、 与各种数据库兼容
4、 能够与 Spring 很好的集成
5、 提供映射标签,支持对象与数据库地 ORM 字段关系映射;提供对象关系映射标签,支持对象关系组件维护。
注:
"映射标签" 和 "对象关系映射标签" 是 MyBatis 中的重要概念。
- 映射标签:MyBatis 的映射标签是用于定义 SQL 语句与 Java 对象之间的映射关系的标签。例如,
<select>
标签用于映射数据库查询操作,<insert>
标签用于映射数据库插入操作,<update>
标签用于映射数据库更新操作,<delete>
标签用于映射数据库删除操作。通过这些标签,开发者可以将数据库操作与 Java 对象进行关联,从而简化数据库操作的开发过程。 - 对象关系映射标签:对象关系映射(ORM)是一种设计模式,它使用对象来封装关系数据库中的数据,以提供更高级别的抽象。MyBatis 的对象关系映射标签可以帮助开发者实现这种抽象。例如,
<resultMap>
标签可以定义 Java 对象与数据库表之间的映射关系,<association>
标签可以定义 Java 对象与数据库表之间的关联关系,<collection>
标签可以定义 Java 对象集合与数据库表之间的映射关系。通过这些标签,开发者可以轻松地实现对象与数据库的 ORM 字段关系映射,以及对象关系组件的维护。
通过这些标签,MyBatis 提供了强大的映射和抽象功能,使得开发者可以更加专注于业务逻辑的开发,而不用关心底层的数据库操作细节。
# JavaSE,封装,继承,多态
不多说了,百度吧
# 四个访问修饰符,public,private,protected, 不写(default)
# 常见装箱拆箱过程
- 赋值操作(装箱或拆箱)
- 进行加减乘除混合运算(拆箱)
- 进行 >,<,== 比较运算 (拆箱)
- 调用 equals 进行比较(装箱)
- ArrayList、HashMap 等集合类添加基础类型数据时 (装箱)
# 内存中的栈(stack), 堆(heap), 方法区(method area).
在计算机科学领域,“stack”(栈)、“heap”(堆)和 "method area"(方法区)是三个常用的术语,用于描述内存管理的不同方面。
- 栈(Stack):
栈是一种线性数据结构,用于存储函数调用和局部变量等信息。栈采用 "后进先出"(LIFO)的方式进行操作,即最后进入栈的数据最先被取出。栈内存的大小是固定的,在程序执行时会自动分配和释放内存。每当一个函数被调用时,栈都会为该函数分配一段局部变量的内存空间,随着函数执行的结束,这些内存空间也会被释放。栈的操作速度非常快,因此适用于存储较小的数据块和需要快速分配和释放内存的情况。 - 堆(Heap):
堆是用于动态分配内存的一种存储区域,它的大小通常比栈大得多。相对于栈来说,堆是一种更为灵活的内存分配方式,可以按需分配和释放内存。在堆中分配的内存块可以由多个部分组成,称为 "对象" 或 "数据结构"。堆内存的分配和释放需要显式的指令,一般由程序员手动管理或通过垃圾回收机制自动回收不再使用的对象。堆适用于需要在不同函数之间共享数据或存储较大数据结构的情况。 - 方法区(Method Area):
方法区是一种特殊的堆区域,主要用于存储类的相关信息,如类的结构、静态变量、常量池、方法代码等。方法区在程序启动时被加载,其大小也是固定的,并且与堆区相互独立。方法区的数据由多个线程共享,因此需要进行并发访问控制。在 Java 虚拟机中,方法区也被称为 "永久代"(Permanent Generation),但在最新版本的 JVM 中,它已被 "元空间"(Metaspace)所取代。
综上所述,栈用于存储函数调用和局部变量,堆用于动态分配内存和存储较大数据结构,方法区用于存储类的相关信息。这三个内存区域在计算机程序执行过程中起着不同的作用和管理方式。
# 数组和 String 的一些差异
数组没有 length () 方法,但是有 length 的属性,String 有 length () 方法,
# 构造器(constructor)与重写(override)
构造器不能被继承,于是也不能被重写,但是可以被重载
# 两个对象值相同,即(x.equals (y) == true), 但有不同的 hash code。是否正确?
是对的,这是可能发生的情况。根据 Java 对象的规范,如果两个对象通过 equals()
方法比较返回 true
,则它们被认为是相等的。但是,即使两个对象的值相同,它们的哈希码(hash code)不一定相同。
Java 中的哈希码是通过 hashCode()
方法生成的一个整数,用于在哈希表等数据结构中快速查找对象。根据规范,如果两个对象通过 equals()
方法比较返回 true
,那么它们的哈希码通常应该相等。但这并不是强制性的要求,因为两个不同的对象可以具有相同的值但不同的哈希码。
当两个对象的哈希码不相同时,它们可能被放置在哈希表的不同桶中,导致无法正确查找某些操作。因此,在重写 equals()
方法时,也应该相应地重写 hashCode()
方法,以确保相等的对象具有相等的哈希码。
简而言之,两个对象的值相同但哈希码不同是合法的,但在实际使用中可能会导致一些问题,因此建议在重写 equals()
方法时同时重写 hashCode()
方法。
# 类加载器的类别与作用
类加载器(Class Loader)是 Java 虚拟机(JVM)的一部分,负责将字节码文件加载到内存中,并转换为可执行的 Java 类。在 Java 中,类加载器分为以下几个类别:
- 启动类加载器(Bootstrap Class Loader):
启动类加载器是 JVM 的一部分,用于加载核心 Java 类库(如 rt.jar 等)。它是 JVM 自身的一部分,通常由本地代码实现,不是 Java 类。启动类加载器是在 JVM 启动时被创建的,它负责加载 JVM 运行所需的基础类。 - 扩展类加载器(Extension Class Loader):
扩展类加载器用于加载 JRE 扩展目录(jre/lib/ext)中的类库。它是由 Java 类sun.misc.Launcher$ExtClassLoader
实现的,它是启动类加载器的子类。扩展类加载器在 JVM 启动时创建,并在需要时动态加载类库。 - 应用程序类加载器(Application Class Loader):
应用程序类加载器是用于加载应用程序类的加载器。它是由 Java 类sun.misc.Launcher$AppClassLoader
实现的,它是扩展类加载器的子类。应用程序类加载器是在 JVM 启动时创建的,它从系统类路径(classpath)中加载类。大多数应用程序的类都由应用程序类加载器加载。
除了这些内置的类加载器,Java 还提供了一些自定义的类加载器,用于满足特定的需求,例如:
- 自定义类加载器(Custom Class Loader):
开发人员可以通过继承java.lang.ClassLoader
类创建自定义的类加载器。自定义类加载器可以根据自己的需求从非标准位置加载类,实现类的动态加载等功能。
不同的类加载器有不同的作用,它们协同工作来加载和管理 Java 类。类加载器的主要目标是按需加载类,以避免不必要的资源消耗和提高应用程序的性能。它们还提供了类隔离和命名空间的概念,允许在同一个 JVM 中加载不同版本或来源的类。
# 实现对象的克隆
实现思路是:新建一个对象类,并在另一个 main 方法中使用 Cloneable 克隆它
如何创建一个对象类,并在另一个 main
方法中使用 Cloneable
接口进行克隆?
首先,让我们创建一个可克隆的对象类 MyObject
,它需要实现 Cloneable
接口并重写 clone()
方法。这样才能在后续使用 clone()
进行对象克隆。
1 | class MyObject implements Cloneable { |
现在我们已经创建了一个可克隆的对象类 MyObject
,接下来让我们在另一个 main
方法中使用它进行克隆。
1 | public class Main { |
在上述代码中,我们首先创建了一个 MyObject
对象 obj1
,并设置其值为 10。然后通过调用 clone()
方法,将 obj1
克隆成 obj2
。最后,分别打印了 obj1
和 obj2
的值。
当我们运行 main
方法时,将会输出以下结果:
1 | obj1 value: 10 |
这表明对象 obj1
成功克隆为对象 obj2
,并且它们的值相同。
# 查看 JVM 参数并了解其含义
1 | java -XX:+PrintFlagsFinal |
# 关于静态成员变量,构造器,父类与子类同存的代码题
1 | package ObjectTemp; |
执行结果是:1a2b2b.
在创建对象时构造器的调用顺序是:先初始化静态成员,然后再调用父类构造器,再初始化非静态成员变量,最后调用自身构造器。
# CDN 是啥?
Content Delivery Network
内容分发网络
核心技术:
- 负载均衡:有多台不同地理位置,不同访问速度的服务器解析来自不同的位置的用户请求,通过对服务器进行实时监测,将各节点流量保存至数据库,对系统中监测到的系统状态产生故障告警,给用户定向至最优的 CDN 节点
- 内存存储:主要有两个方面,其实站点的源服务器(根服务器)和 Cache 节点中的存储功能,源服务器的客户请求有很大一部分分发给了 CDN 节点,目的如上第一点,同时你需要把大量的数据也给分发出去,但是数量巨大,为了方便存储读取,会根据 CDN 节点的规律去存储内容,现代的物流系统也是一个比较实物现实的服务系统。可以参考物流系统中建立仓库,提前分发包裹给各地仓库,已达到 3 日达的效果甚至次日达的速度。
- 内容发布:借助索引,缓存,流分裂,组播等技术,将内容由内容源分发到 CDN 边缘的整个缓存过程,内容分发技术主要是 PUSH (主动分发技术)和 PULL (被动分发技术)。PUSH 一般是由供应商或是 CDN 的内容管理人员从站点源服务器或媒体的资源库直接向各个 CDN 节点主动分发的一些热点内容,或是客户指定的内容。PULL 则是客户向节点请求缓存中没有存储的内容时,Cache 从源服务器或是其他 CDN 节点请求获取内容。
# String s = new String (“xyz”); 创建了几个字符串对象?
两个,一个是静态区的 “xyz”, 另一个是用 new 创建在堆上的对象。
# 一道类似的题:问法不同
String str = new String (“abc”) 到底 new 了几个对象?
分两种情况:
- new 了一个:如果常量池中已经有了 "abc" 这个字符串,也就是说你在前面已经 new 过一个值为”abc" 的字符串,那么虚拟机就会在堆内存中 new 一个 String 对象,并将常量池中 “abc” 的地址指向你刚刚 new 的 String 对象。
- new 了两个:如果常量池中没有 “abc" 这个字符串,那么虚拟机就会在堆内存中 new 出一个 String 对象,并且在常量池中 new 一个 abc 字符串对象。
# 阐述 ArrayList、Vector、LinkedList 的存储性能和特性。
ArrayList 和 Vector 都是使用数组方式存储数据,此数组元素大于实际存储的数据以便于添加新的元素。两者都允许直接按照序号索引元素。但是添加的元素涉及数组元素的移动操作,所有查找快而添加修改元素慢。Vector 中的方法由于添加了 syncheonized 修饰,所以 Vector 是线程安全的容器。LinkedList 使用双向链表实现存储。按照序号索引数据需要向前或者向后遍历。查找慢,但是添加元素快。
# 启动一个线程是调用 run () 还是 start () 方法?
start ():使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由 JVM 调度并执行,这并不意味着线程就会立即运行。run () 方法是线程启动后要进行回调的方法。
# 线程池(thread pool)
在面向对象编程中,创建和销毁对象是比较耗费资源的,因为创建一个对象需要获取内存资源或者其它更多资源。在 Java 虚拟机中,虚拟机会试图跟踪每一个对象,以便后期对其资源的回收,所以想要提高服务的程序效率,有个办法就是尽可能的减少创建和销毁对象的次数。
所以对于一些很耗资源的对象创建和销毁,使用线程池技术可以较好的管理资源。首先创建诺干个可以执行的线程放在一个池中,需要的时候就从池中获取线程,而不用自行创建。使用完不必销毁而是放回池中,这样就减少了很大一部分创建和销毁的操作。从而减少资源开销。
这里还有多种线程池可以了解一下。
另外还有线程组的概念,但是不推荐使用,都是用线程池。
# Java 如何实现序列化,意义是什么?
序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。
意义: 解决对象流读写操作时可能引发的问题(如果不进行序列化可能会存在数据乱序的问题
要实现序列化,需要让一个类实现 Serializable 接口,该接口是一个标识性接口,标注该类对象可以被序列化,然后使用一个输出流来构造一个对象输出流并通
过 writeObject (Object) 方法就可以将实现对象写出(即保存其状态);如果需要
反序列化则可以用一个输入流建立对象输入流,然后通过 readObject 方法从流中
读取对象。序列化除了能够实现对象的持久化之外,还能够用于对象的深度克隆
# XML 文档定义有几种形式?它们之间有何本质区别?解析 XML 文档有哪几种方式?
XML 文档定义有两种形式,分别是 DTD 和 XSD。
DTD(Document Type Definition)是一种用于定义 XML 文档中元素、属性、实体和文档类型声明的语法规范。DTD 是一种早期的 XML 标准,其语法简单,但不够强大和灵活。
XSD(XML Schema Definition)是一种比 DTD 更为强大和灵活的 XML 文档定义语言,它使用 XML 格式编写,可以定义复杂的数据类型、较为精细的约束条件和元素关系等,支持命名空间和继承等概念。
本质区别在于,DTD 只能定义元素、属性和实体等基本结构,对于元素的数据类型、约束条件和关系等定义比较简单,而 XSD 则可以对数据类型、复杂元素、简单元素和属性等进行更为详细的定义和限制,灵活性更强。
XML 文档解析方式有 DOM 解析和 SAX 解析。
DOM(Document Object Model)解析方式是将整个 XML 文档解析成一个树形结构,程序可以通过访问树的方式来访问和操作 XML 文档。
SAX(Simple API for XML)解析方式是一种基于事件驱动的解析器,在解析过程中逐行读取 XML 文档,只有在需要的情况下才触发相应的事件。SAX 解析方式相比 DOM 更为高效,适用于处理大型 XML 文档。
# XML 的用处
XML 的主要两个作用是:数据交互,信息配置
在数据交互时,XML 将数据用标签组装起来,然后压缩打包加密后通过网络传送给接收者,现在该该功能已经几乎被 JSON 取而代之了。
信息配置:现在很多软件仍然使用 XML 来存储配置信息。
# DAO 模式
Data Access Object 顾名思义是一个为数据库或其它持久化机制提供了抽象接口的对象,在不暴露底层持久化方案实现细节的前提下提供了各种数据访问操作。
在实际开发中,应该将所有对数据源的访问操作进行抽象化封装在一个公共的 API 中。
用程序设计语言思想来说,就是建立一个接口,接口中定义了此应用程序中将会用到的所有事务方法。
在这个程序中。当需要和数据源进行交互的时候就直接使用该接口。并且编写一个单独的类来实现这个接口。逻辑上该类对应一个特定的数据存储。
DAO 模式包括两个模式,
- Data Accessor (数据访问器)
- Data Object (数据对象)
第一种解决数据的访问问题,第二种解决用对象封装数据
。
# 事务的 ACID 是指什么?
- 原子性(Atomic):事务中各项操作,要么全做要么全不做,任何一项操作的失败都会导致整个事物的失败
- 一致性(Consistent):事务结束后系统状态是一致的
- 隔离性(Isolated):并发执行的事务彼此无法看到对方的中间状态
- 持久性(Durable):事务完成后所做的改动都会被持久化,即使发生灾难性的失败。通过日志和同步备份可以在故障发生后重建数据。
关于事务的补充:
首先需要知道的是:只有存在并发数据访问时才需要事务。当多个事务访问同一个数据时,可能会存在 5 类问题,包括 3 类数据读取问题(脏读、不可重复读和幻读)和 2 类数据更新问题
# Java 中的 volatile 数组
在 Java 中,“volatile” 关键字可以用于修饰数组类型的变量,以确保数组的可见性和原子性。
具体来说,如果一个线程修改了一个 volatile 数组中的元素,那么其他线程能够立即看到这个修改。而且,JVM 会保证 volatile 操作的原子性。也就是说,当一个线程正在更新一个 volatile 数组时,其他线程不能同时读取或修改该数组,以避免数据竞争和不一致的状态。
示例代码如下:
1 | public class VolatileArrayExample { |
在上面的示例中,我们声明了一个 volatile 的 int 数组 nums,并提供了两个方法 updateNums 和 getNum,分别用于更新和获取数组中的元素。
需要注意的是,虽然 volatile 数组能够确保可见性和原子性,但是它并不能保证线程安全。如果多个线程同时访问同一个 volatile 数组,并且进行复合操作(例如递增、递减、判断等),仍然需要进行额外的同步机制或者使用线程安全的数据结构来保证正确性。
# volatile 修饰符有什么应用场景
一种应用场景是使用 volatile 修饰 long 和 double 变量,
double 和 long 都是 64 位宽,因此对这两种类型的读是分为两个部分的,第一次读取 32 位,然后再读取剩下的 32 位。这个过程不是原子的。
volatile 修饰符的另一个作用是提供内存屏障(memory barrier), 例如再分布式框架中的应用,简单来说:就是当你写一个 volatile 变量之前,java 内存模型会插入一个写屏障(write barrier), 读取该 volatile 变量之前,会插入一个读屏障(read barrier). 就是保证再修改一个 volatile 域时,要保证任何线程都能看到你修改的值,与此同时,在写之前,也要能保证任何数值的更新对所有线程都是可见的,因为内存屏障会将其它所有写的值更新到缓存。
# Java 中 ++ 操作符是线程安全的吗?
不是线程安全的操作,它涉及到多个指令,如果读取变量值,后增加,然后存储回内存,这个过程可能会出现多个线程交差。
# int 和 Integer 哪个会占用更多的内存?
Integer 对象会占用更多的内存。Integer 是一个对象,需要存储对象的元数据。但是 int 是一个原始类型的数据,所以占用的空间更少。
# grant deny revoke
这三个单词都与权限管理相关,通常在计算机科学和信息安全中使用。
- ‘grant’:授予。在权限管理系统中,这意味着将特定权限或特权给予某个用户或用户组。例如,管理员可能授予某个用户对特定文件或系统的读取、写入或执行权限。
- ‘deny’:拒绝,否定。在权限管理系统中,这意味着禁止某个用户或用户组执行某项操作或访问特定资源。例如,管理员可以拒绝某个用户对特定文件或系统的访问请求。
- ‘revoke’:撤销,废止。在权限管理中,这意味着收回了之前授予的权限或特权。例如,如果一个用户不再需要访问特定文件或执行特定操作,管理员可以撤销 这些权限。
# Spring
# Spring 的组件
- 接口:用来定义功能
- Bean 类:用来赋予属性,提供 setter 和 getter 方法,以及一些函数
- Spring 面向切面编程
- Bean 配置文件
- 用户程序:暴露给使用者的接口
# 区分构造函数注入和 setter 注入
# SpringBoot
# 简单描述一下 yaml 是什么
它是一种可读的数据序列化语言,一般用作配置文件,yaml 更加结构化,使用分层配置数据
# 基础概念
- SpringBoot 是一个快速开发框架,简化了 XML 配置,提供了自动配置和开箱即用的功能,使得开发人员能够快速搭建应用程序。
- SpringBoot 的核心原理是自动配置,通过继承 Spring 父类来实现大量的自动配置,从而简化了 XML 配置。
- SpringBoot 的配置文件支持多种类型,包括.properties、.yml 和.yaml 文件,用于配置应用程序的各个方面。
- SpringBoot 内置了 Tomcat 等服务器,使得无需额外配置即可运行 Web 应用程序。
- SpringBoot 提供了对各种常用开发工具的整合,如 SpringMVC、SpringData、MyBatis 等,以及一些大型项目常用的非功能性特性的支持,如安全性、指标、健康检查等。
- SpringBoot 的启动类上有一个关键的注解 @SpringBootApplication,它是 SpringBootApplication 类的子类,用于启动 SpringBoot 应用程序。
- SpringBoot 的自动配置原理是通过在应用程序的类路径中搜索并加载符合条件的 Bean 定义,然后根据这些定义自动创建 Bean。
- SpringBoot 对外部属性的支持是通过使用 SpringApplication.setDefaultProperties 来设置的,这使得我们可以在运行时传入额外的属性。
- SpringBoot 的命令行参数可以通过使用 SpringApplication.run (Class<?>[] args) 方法来接收。
- SpringBoot 的 Actuator 是用于监控和管理的模块,提供了对健康检查、指标收集等功能的支持。
# Redis
# Redis 优势
性能极高 -----Redis 读写速度高
五种数据类型
1、 String (字符串)
2、 hash (哈希)
3、 list (列表)
4、 set (集合)
5、 Zset/sorted set (有序集合)
# Redis 持久化机制
# 一个 redis 实例最多能存放多少个 keys?
理论上可以有 232 个 keys, 实际使用取决于你的系统可用内存值。
# MySQL 中有 2000w 数据,redis 中只存了 20w 的数据,如何保证 redis 中的数据都是热点数据?
Redis 内存数据集大小越来越多,当达到一定大小的时候,就会施行数据淘汰机制。
有 6 种数据淘汰机制:
volatile-lru:从已经设置过期时间的数据集中挑选出最近最少使用的数据淘汰掉
volatile-ttl:从已经设置过期时间的数据集中挑选将要过期的数据淘汰
allkeys-lru:从数据集中挑选最近最少使用的数据淘汰
allkeys-random:从数据集中任选数据淘汰
no-enviction:禁止驱逐数据
# Redis 适合的应用场景
1、 会话缓存(Session Cache)
Redis 提供持久化
2、 全页缓存(FPC)
除去基本的会话 Token 之外,使用 FPC 可以保证读写的高效性,同时降低磁盘 I/O 的读写压力,同时如果重启 redis, 由于有磁盘的持久化,可以保证快速的恢复内存中的数据
3、 队列
Redis 在内存存储引擎领域二点一大优点是提供 list 和 set 操作,这两大优点可以保证 Redis 能够作为一个很好的消息队列平台来使用.
4、 排行榜 / 计数器
Redis 在内存中对数字进行递增或递减的操作实现的非常好,Redis 提供得两大集合(Set) 和有序集合(Sorted Set) 也使得我们在执行这些操作变得非常简单。
# 如果有大量的 Key 需要设置同一时间过期,一般需要注意什么?
如果有大量的 key 过期时间设置的过于集中,那么就会出现一到那个时间点就出现短暂的卡顿现象。所以一般需要在时间上加上一个随机值,使得过期时间分散一些,避免过于集中。
# SpringCloud
# SpringCloud 的优势
使用 SpringBoot 开发分布式微服务时,会有以下几个问题:
1、 与分布式系统相关的复杂性 - 这种开销包括网络问题,延迟开销,带宽问题,SpringBoot 并没有一个很好的解决方案去处理这样一个分布式的系统问题
2、 服务发现 - 作为一个工具,它管理着集中的流程和服务用来查找与服务之间的相互通信。
3、 性能 - 问题:各种微服务的开销比较大,不便于统一的进行管理
4、 部署的复杂性:使用 SpringCloud 可以更好的使用 Devops 的进行部署
5、 负载平衡:计算机集群,网络链接,中央处理器,磁盘驱动。
# Spring Cloud Config
# RabbitMQ
# 简单描述一下:
RabbitMQ 是一个开放源代码的消息代理软件。
它是一个具有可靠的消息传递、发布 / 订阅功能以及消息持久性和排序等功能。RabbitMQ 的架构包括消息队列、生产者和消费者等组件。通过其可扩展的集群和故障转移功能,RabbitMQ 可以处理大量的消息并确保消息的可靠传输。
# 消息基于什么传输?
由于 TCP 连接 的创 建和 销毁 开销 较大 ,且 并发 数受 系统 资源 限制 ,会 造成 性能 瓶
颈。 RabbitMQ 使用 信道 的方 式来 传输 数据 。 信 道是 建立 在真 实的 TCP 连接 内的
虚拟 连接 ,且 每条 TCP 连接 上的 信道 数量 没有 限制。
# Java 集合
# 讲讲你对集合的了解
首先,总共有这几大类集合:List、Set、Queue、Map。
主要特点:List 存储的数据有序,可重复
Set存储的数据不可重复
Queue有序,可重复
Map中的Key无序、不可重复,value无序、可重复
有个 Collection 接口
上面的 List,Set,Queue 都继承与它
# List
主要有三个实现类:
ArrayList:最常用的实现类,内部通过数组实现,查询效率较高,插入和删除效率较低
Vector:通过数组实现,同时它与 ArrayList 的一个差别就是 Vector 支持线程同步,简单来说就是某一个时刻只有一个线程可以读写 Vector, 这样避免了多线程同时写找出数据的不一致。当然代价就是丢失性能。对于安全性能要求比较高,数据量较大且需要进行多线程访问的场景,它并不适合。
注:Vector 实现线程同步机制的方法是通过 synchronized 关键字。它会在所有的公共方法上加上 synchronized 关键字。
LinkedList:通过链表结构实现。插入和删除操作快,查找效率低
# Set
主要有三个实现类:
HashSet:基于哈希表实现的集合,不允许重复元素。允许使用 null 元素,存储顺序和取出的顺序是一样的。线程不安全的集合。它适合用于存储大量的数据,并且对数据的存储顺序没有严格要求的场景,对数据的插入、删除、查找效率都比较高。
LinkedHashSet:不允许元素重复,允许 null 值,非线程安全,LinkedHashSet 是基于链表和哈希表实现的,当需要存储的数据量比较少,且需要按照插入的顺序遍历时。可以使用 LinkedHashSet
TreeSet:底层是红黑树(平衡二叉树)。不允许使用 null 元素,线程不安全。
注:以上三者都不允许重复元素,如果尝试重复元素,会抛出 IllegalStateException 或者 IIlegalArgumentException 异常
# Queue
一种队列数据结构,
PriorityQueue:是一种基于优先级的队列,可以根据元素优先级来进行排序,优先级最高的元素总是位于队列的头部,而优先级最低的元素则位于队列的尾部。
ArrayQueue:是基于数组实现的,适用于需要频繁入队和出队操作的情况。
# Map
HashMap:数组 + 链表。链表是为了解决哈希冲突而存在的。插入、查询、删除的效率高。
TreeMap:按照特定的顺序进行排序。
LinkedHashMap:能够保持其插入顺序。
Hashtable:不支持 null 的键或值。
ConcurrentHashMap:支持多线程并发访问,很高的写入性能,与 HashTable 相比,它支持 null 的键和值,并且具有更高的查询性能。
如何选用集合?
1、 数据的存储和访问方式,如果需要快速的查询和更新数据,可以选择 HashMap、TreeMap 或 LinkedHashMap,如果需要保证数据的顺序,可以选择 TreeMap 或 LinkedHashMap; 如果需要线程安全的数据操作,可以选择 ConcurrentHashMap.
2、 数据的大小,如果预计集合中的元素数量较小,可以选择 ArrayList 或 LinkedList,如果预计集合中的元素数量比较大,可以选择 HashSet 或 LinkedHashSet。
3、 是否需要排序,如果需要按照某种顺序排列集合中的元素,就选择 TreeSet 或者 LinkedHashSet.
4、 是否需要保存元素的插入顺序,如果需要,可以选择 LinkedHashSet 或者 LinkedHashMap.
5、 内存的占用和性能。需要高效使用内存的话,选择较小的数据结构,比如 ArrayList 和 HashSet.
# ArrayList 和 Array 的区别
ArrayList 内部基于动态数组,而 Array 是静态数组
区别:
-
ArrayList 会根据实际存储的元素动态的扩容或者缩容,而静态数组 Array 一经创建就没办法改变它的长度了。
-
ArrayList 允许你使用泛型保证类型的安全,但 Array 不可以。
-
ArrayList 只能存储对象,对于基本数据类型,需要使用对应的包装类(Integer、Double 等)
而 Array 可以直接存储基本类型,也可以存储对象
-
ArrayList 支持插入、删除、遍历等常见操作,提供丰富的 API 操作,而 Array 只是固定长度的数组。
-
ArrayList 创建时不需要指定大小,而 Array 创建时必须要指定大小
# ArrayList 插入和删除元素的时间复杂度?
三种情况插入和删除:
- 头部插入删除:需要所有的元素依次向后或向前移动一个位置,时间复杂度为 O (n)
- 尾部插入删除:当 ArrayList 容量还没有达到极限时,插入时间复杂度为 O (1), 当达到极限时,就需要扩容操作,执行扩容操作的需要先执行 O(n) 操作将原数组复制到新的更大的数组中,然后在执行 O (1) 操作添加元素
- 指定位置插入删除:平均需要移动 n/2 个元素,时间复杂度为 O (n)
# HashTable 和 HashMap 的使用场景集合:
HashTable 和 HashMap 在 Java 开发过程中都用于存储键值对数据。区别主要在于线程安全与是否运行键值对为 null
首先是 HashTable,它是线程安全的,常用于多线程环境下,由于多了一个线程安全,所以它的性能是要低于 HashMap 的。
另外关于键值对,它不支持或者说不允许使用 null 作为键和值。当你使用 null 时,它会抛出 NullPointerException.
其次,HashTable 提供了一组方法用来支持枚举遍历,这是与 HashMap 不同的一点
最后,在需要线程安全的情况下,你可以使用 HashTable,比如在多线程访问共享数据时
HashMap: 线程不安全,在单线程环境下适用,且性能更高。
允许 null 作为键和值。null 元素会计入 size 大小
# Hashtable 用什么来保证线程安全?
在 Java 中,Hashtable 的实现使用 synchronized 关键字来保证线程安全。当多个线程同时访问 HashTable 时,Java 内置的一套锁机制会用来确保同一时间只有一个线程可以执行 HashTable 的某个个方法。
具体一点来讲,HashTable 中的每个方法(如 put,get,remove 等) 都会被 synchronized 关键字修饰,这意味着在执行这些方法时,都只有一个线程可以进入该方法。
注:随着现代大型 webApp 的发展,出现越来越多的高并发场景。而面对这样的高并发场景,HashTable 已经被视为一种过时的数据结构,现在更多的是使用 ConcurrentHashMap 等并发数据结构来替代 HashTable。
# 解释一下”Hashtable<String, Integer> hashtable = new Hashtable<>();“分别代表什么?
该语句是用来创建并初始化 HashTable 对象的语句
- Hashtable<String,Integer>:这是声明一个 HashTable 类型的变量,其中 String 是键的类型,Integer 是值的类型。意味着你的 HashTable 中的 key 必须是 String 类型,values 必须是 Integer 类型。
- hashtable:这是变量名
- new Hashtable<>():这部分是创建一个新的 Hashtable 实例。new 关键字在 Java 中用于创建对象,Hashtable()是 Hashtable 的构造函数,它创建了一个新的、初始化的 Hashtable.
# CopyOnWriteArrayList 和 CopyOnWriteArraySet 的区别?
一个是 List 接口,一个是 Set 接口,CopyOnWriteArrayList 适用于对数据的随机访问和读取操作频繁,修改比较少的场景。
CopyOnWriteArraySet 适用于对数据读取操作比较少,但需要频繁进行修改的情况。
前者 List 支持 contains、get、set 等操作,
后者 Set 不支持 contains、get、set 等操作
由此可以得出:
当需要一个可以高效支持读操作的线程安全的集合时,应该使用 CopyOnWriteArrayList。
但需要一个不允许重复元素的线程安全的集合时,应该使用 CopyOnWriteArraySet。
# 集合使用注意事项
# 集合判空
# 设计模式
# 软件设计原则
- 开闭原则:指一个软件实体可以通过扩展来开实现新的功能
- 单一职责原则:一个类应该只有一个引起变化的原因,使类的职责更加单一,降低类的复杂度。
- 里氏替换原则:指子类必须能够替换父类的功能。
- 依赖倒置原则:底层不依赖高层
- 接口隔离原则:指一个类不应该强制实现一个接口中的所有方法,应该是只实现它需要的方法
- 合成 / 聚合复用原则:尽量使用对象组合,不用继承关系
- 迪米特法则:一个对象应该尽量少的了解关联其它对象,简而言之就是一个对象应该通过方法调用其它对象的方法和属性,而不是直接访问其它对象的属性。
# 设计模式的分类
- 创建型
- 结构型
- 行为型
# 谈谈简单工厂模式
由一个工厂对象来创建实例,客户端不需要关注创建逻辑,只需要提供传入的工厂参数。
应用场景有:Spring 中的 BeanFactory 使用简单工厂模式,通过一个唯一的标识来获取 Bean 对象。
# 单例模式了解吗
单例模式属于创建型模式,一个单例类在任何情况下都只存在一个实例,方法是私有的。由自己创建一个静态变量用于存储实例,对外提供一个静态公有方法获取实例。
# 写一下单例模式吧
# 饿汉式的单例
这种方式:当类一加载就会创建对象,比较常用的一种方法。
优点:线程安全,不使用锁,执行的效率比较高
缺点:不是懒加载,类加载的时候就会初始化,比较浪费内存空间。且产生垃圾对象
注:懒加载就是使用类的时候再创建对象。
# DNS 的两种查询解析模式
分布式域名解析和集中式域名解析
# Linux 文件系统
在 linux 中,一切都被操作系统管理着,网络接口卡,磁盘驱动器,打印机,输入输出设备,普通文件或目录等,都被视为文件。“一切皆文件”
在 linux 中,所有资源都被抽象为文件,以文件的形式进行管理和访问。
# inode 节点
1、 硬盘的最小存储单位是扇区,块由多个扇区组成,文件系统中数据存储在块中。块的最常见的大小是 4kb, 大概为 8 个连续的扇区组成(一般情况下一个扇区存储 512 字节)。一个文件可能会占用多个 block, 但是一个块只能存放一个文件,虽然文件存放在块中,但是还需要一部分空间用来存放文件的元信息 metadata。内容有:文件被分为几块、每一块的地址、文件拥有者、创建的时间、权限、大小等。
存储文件元信息的区域叫做 inode: 就是索引节点。i (index)+node。每个文件都有一个唯一的 inode, 存储文件的元信息。
# 区分一下 inode 和 block
inode: 记录文件的属性信息,可以通过 stat 命令查看 inode 信息
block: 实际文件的内容,这里需要看文件的大小,如果文件比较大,大于一个块时,就会占用多个 block, 但是每个块只能存放一个文件,不管这个文件有多小,就是但一个文件小于块的大小时。
(注:数据是由 inode 指向的,如果有两个文件的数据都存放在同一个块中,就会出现问题)
# 使用 inode 的一些优点
在 linux 系统中,可以使用 inode 区分不同的文件,好处是即使文件名被修改或者删除,该文件的 inode 号码不会改变,可以避免文件重名的错误,
inode 可以提高文件系统的性能,使用 inode 的访问速度非常快,可以直接通过 inode 号码定位到文件的元数据信息,避免了遍历整个文件系统。
# 关于硬链接和软链接
文件链接属于一种特殊的文件类型,就是可以在文件系统中指向另一个文件。
1、硬链接(hard link)
每个文件和目录都有一个唯一的索引节点(inode), 用来标识文件或目录。
注:只有删除了源文件和所对应的硬链接文件,该文件才会被真正的被删除
硬链接的有些局限,
主要有两点:
1、不能对目录以及不存在的文件创建硬链接,2、不可以跨域文件系统
ln 命令是用来创建硬链接的
2、软链接(symbolic link)
软链接指向一个文件地址路径
注:源文件被删除后,软链接依然会存在,但是是指向一个无效的或者说是空的文件路径
(这里我们可以类比软链接为 windows 的快捷方式)
# 文件权限
使用 ls 查看文件或者目录的权限有四个部分的区别
首先第一个位置是表示文件类型:总共三个
d: 代表目录
-:代表文件
l: 代表软链接(即理解为 window 中的快捷方式)
1 | linux中的权限主要有三种: |
第二个位置,第三个位置,第四个位置都是三个字符表示的
分别表示属主权限,属组权限,其它用户权限
1、属主可以理解为文件的创建者
2、属组理解为文件所在组
3、其它组理解为:除了文件的所有者还有所在组的用户外的其它用户。