0%

后缀数组(Suffix Array)是一种用于字符串排序和字符串相关问题的数据结构。它可以在 O(n log n) 的时间内构建出字符串的后缀数组,其中 n 是字符串的长度。后缀数组可以用于解决很多与字符串相关的问题,如最长公共子串、模式匹配等。

后缀数组的定义很简单:对于一个字符串 S,它的后缀数组 SA 是一个整数数组,其中 SA[i] 表示字符串 S 的后缀的起始位置,使得 S.substring(SA[i]) 是排在第 i 位的后缀。

以下是一个简单的 Java 实现示例,用于构建后缀数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import java.util.Arrays;

class Suffix implements Comparable<Suffix> {
int index;
String suffix;

public Suffix(int index, String suffix) {
this.index = index;
this.suffix = suffix;
}

@Override
public int compareTo(Suffix other) {
return this.suffix.compareTo(other.suffix);
}
}

public class SuffixArray {
public static int[] buildSuffixArray(String text) {
int n = text.length();
Suffix[] suffixes = new Suffix[n];

for (int i = 0; i < n; i++) {
suffixes[i] = new Suffix(i, text.substring(i));
}

Arrays.sort(suffixes);

int[] suffixArray = new int[n];
for (int i = 0; i < n; i++) {
suffixArray[i] = suffixes[i].index;
}

return suffixArray;
}

public static void main(String[] args) {
String text = "banana";
int[] suffixArray = buildSuffixArray(text);

System.out.println("Suffix Array:");
for (int index : suffixArray) {
System.out.println(index + ": " + text.substring(index));
}
}
}

在上面的代码中,我们首先定义了一个 Suffix 类,用于表示后缀及其索引。然后,我们创建了一个 SuffixArray 类,其中的 buildSuffixArray 方法用于构建后缀数组。该方法首先创建一个 Suffix 对象数组,然后使用 Arrays.sort 对后缀数组进行排序,最后将排序后的索引数组返回。

注意,这只是一个简化的后缀数组实现示例。实际上,构建后缀数组有更高效的算法,如倍增算法和 DC3 算法。如果需要处理大规模字符串,建议使用这些更高效的算法。

优先队列(Priority Queue)是一种特殊的队列,它能够根据元素的优先级自动排序。在优先队列中,元素的出队顺序不是按照它们进队的顺序,而是根据元素的优先级确定的,优先级高的元素先出队。优先队列常用于实现任务调度、最小(最大)堆等场景。

Java 中的 PriorityQueue 类实现了优先队列的功能,它使用堆数据结构来实现自动排序。默认情况下,PriorityQueue 是最小堆(即优先级最小的元素先出队),但您也可以通过自定义比较器来创建最大堆。

以下是一个简单的 Java 代码示例,演示如何使用 PriorityQueue 创建一个最小堆:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.util.PriorityQueue;

public class PriorityQueueExample {
public static void main(String[] args) {
// 创建一个最小堆
PriorityQueue<Integer> minHeap = new PriorityQueue<>();

// 插入元素
minHeap.add(5);
minHeap.add(2);
minHeap.add(8);
minHeap.add(1);
minHeap.add(10);

// 输出堆中的元素(按照升序排列)
while (!minHeap.isEmpty()) {
System.out.print(minHeap.poll() + " ");
}
}
}

在上面的代码中,我们使用 PriorityQueue 创建了一个最小堆。通过 add 方法向堆中插入元素,然后通过 poll 方法逐个弹出并输出堆中的元素。输出的结果将会是升序排列的。

需要注意的是,PriorityQueue 可以存储任何实现了比较接口(如 Comparable 或自定义的比较器)的元素。在创建优先队列时,您可以通过构造函数传递自定义的比较器来控制元素的排序方式。

虽然这只是一个简单的示例,但 PriorityQueue 在实际应用中具有更广泛的用途,如实现 Dijkstra 算法、优先级任务调度等。在使用时,建议您仔细阅读相关文档并根据具体需求进行定制。

Trie 树,也称为字典树或前缀树,是一种用于存储和检索字符串集合的数据结构。它的主要优势是可以高效地进行字符串的插入、删除和搜索操作,特别适用于大量字符串的快速查找。Trie 树的基本思想是将字符串按照字符逐层存储,每个节点代表一个字符,从根节点到叶子节点的路径表示一个完整的字符串。

以下是一个简单的 Java 实现示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
class TrieNode {
private final int ALPHABET_SIZE = 26;
TrieNode[] children;
boolean isEndOfWord;

public TrieNode() {
children = new TrieNode[ALPHABET_SIZE];
isEndOfWord = false;
}
}

public class Trie {
private TrieNode root;

public Trie() {
root = new TrieNode();
}

public void insert(String word) {
TrieNode current = root;
for (char c : word.toCharArray()) {
int index = c - 'a';
if (current.children[index] == null) {
current.children[index] = new TrieNode();
}
current = current.children[index];
}
current.isEndOfWord = true;
}

public boolean search(String word) {
TrieNode current = root;
for (char c : word.toCharArray()) {
int index = c - 'a';
if (current.children[index] == null) {
return false;
}
current = current.children[index];
}
return current.isEndOfWord;
}

public boolean startsWith(String prefix) {
TrieNode current = root;
for (char c : prefix.toCharArray()) {
int index = c - 'a';
if (current.children[index] == null) {
return false;
}
current = current.children[index];
}
return true;
}

public static void main(String[] args) {
Trie trie = new Trie();
trie.insert("apple");
System.out.println(trie.search("apple")); // 输出 true
System.out.println(trie.search("app")); // 输出 false
System.out.println(trie.startsWith("app")); // 输出 true
trie.insert("app");
System.out.println(trie.search("app")); // 输出 true
}
}

在这个示例中,我们创建了一个 TrieNode 类来表示 Trie 树的节点。每个节点都包含一个字符数组作为子节点,并有一个标志来指示当前节点是否是一个单词的结尾。然后,我们创建了一个 Trie 类来实现 Trie 树的插入、搜索和前缀搜索功能。

请注意,这只是 Trie 树的一个简单实现示例。在实际应用中,您可能需要进行更多的优化和功能扩展,以适应特定的需求。

控制Java多线程环境下的线程同步

对象锁

synchronized修饰方法或者代码块

类锁

synchronized修饰静态方法或者代码块

方法锁(对象锁)

synchronized修饰方法,锁住的也是这个对象

示例

public class SynchronizeTest {
    static class S{
        String name;
        public void setName(String name) throws InterruptedException {
            synchronized (this) {
                System.out.println("..........");
                Thread.sleep(1000*5L);
                this.name = name;
            }
        }
        public void print(){
            synchronized (this){
                System.out.println(name);
            }
        }
        public synchronized void doSomething1(){
            System.out.println("doSomething1");
            try {
                Thread.sleep(1000*5L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        public synchronized void doSomething2(){
            System.out.println(name);
        }
    }
    public static void main(String[] args) throws Exception{
        S s = new S();
//        Thread t1 = new Thread(() -> {
//            try {
//                s.setName("hello");
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//        });
//        t1.start();
//        Thread t2 = new Thread(() -> {
//                s.print();
//        });
//        t2.start();
//        t1.join();
//        t2.join();
        Thread t1 = new Thread(() -> {
             s.doSomething1();
        });
        t1.start();
        Thread t2 = new Thread(() -> {
            s.doSomething2();
        });
        t2.start();
        t1.join();
        t2.join();

    }
}

Java 8 是 Java 编程语言的一个重要版本,引入了许多新特性和语法改进,使得编写代码更加简洁和高效。以下是一些 Java 8 中的重要语法特性的详细解释:

  1. Lambda 表达式:Lambda 表达式是一种更简洁的方法来表示匿名函数。它们可以用于替代需要单一抽象方法的接口实现。Lambda 表达式的语法如下:

    1
    2
    3
    (parameters) -> expression

    (parameters) -> { statements; }
  2. 函数式接口:函数式接口是只包含一个抽象方法的接口。Java 8 中的 Lambda 表达式常常与函数式接口一起使用。例如,RunnableCallable 接口就是函数式接口的例子。

  3. Stream API:Stream 是一种处理集合数据的新方式,它提供了一种流式处理元素的方法。Stream API 可以极大地简化集合操作,例如过滤、映射、聚合等。以下是一个使用 Stream 进行过滤的例子:

    1
    2
    3
    4
    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    List<Integer> evenNumbers = numbers.stream()
    .filter(n -> n % 2 == 0)
    .collect(Collectors.toList());
  4. 方法引用:方法引用是一种更简洁地调用已存在方法的方式,可以使代码更易读。方法引用可以用于静态方法、实例方法以及构造函数。以下是一些方法引用的例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 静态方法引用
    Function<String, Integer> parseInt = Integer::parseInt;

    // 实例方法引用
    List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
    names.forEach(System.out::println);

    // 构造函数引用
    Supplier<List<String>> listSupplier = ArrayList::new;
  5. 默认方法和静态方法:Java 8 允许在接口中定义默认方法和静态方法。默认方法允许在不破坏实现类的情况下向接口添加新方法。静态方法可以直接通过接口名调用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    interface MyInterface {
    void doSomething();

    default void doSomethingElse() {
    // 默认实现
    }

    static void staticMethod() {
    // 静态方法实现
    }
    }
  6. Optional 类:Optional 是一个容器类,可以用来处理可能为空的值,避免空指针异常。它鼓励程序员显式处理可能为空的情况。

    1
    2
    3
    4
    5
    6
    Optional<String> optionalValue = Optional.ofNullable(getValue());
    if (optionalValue.isPresent()) {
    String result = optionalValue.get();
    } else {
    // 值为空的处理
    }
  7. 新的日期和时间 API:Java 8 引入了新的日期和时间 API,解决了旧的 DateCalendar 类的问题。新的 API 提供了更好的可读性和易用性。

    1
    2
    3
    LocalDateTime now = LocalDateTime.now();
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    String formattedDateTime = now.format(formatter);
  8. 默认方法的解决冲突:当一个类实现了多个接口,这些接口有相同的默认方法时,编译器会要求显示指定调用哪个接口的方法,或者在实现类中重写该方法。

这些是 Java 8 中的一些重要语法特性。它们使得代码更加简洁、可读性更高,并且提供了更多的灵活性和功能。在使用这些特性时,要注意充分了解它们的用法和适用场景,以便编写出高质量的代码。

RBAC : 基于角色的权限访问控制(Role-Based Access Control)通过角色绑定权限,然后给用户划分角色。在web应用中,可以将权限理解为url,一个权限对应一个url。

表设计

用户,角色,权限,权限组,菜单

用户和角色,多对多
角色和权限,多对多
权限和权限组,多对一
权限组和菜单,多对一
菜单和菜单,自引用

系统

用户登录后,取出其权限及所属菜单信息,写入session中

自定义中间件,检查用户权限,进行访问控制

参考:https://www.jianshu.com/p/f45b54768aa9

基本内容

JVM内存模型

指令重排(编译器、运行时)

从java源代码到最终实际执行的指令序列,会分别经历下面三种重排序:

编译器优化重排序
指令级并行重排序
内存系统重排序

编译器优化重排序

在执行程序时,为了提高性能,编译器和处理器会对指令做重排序。但是,JMM确保在不同的编译器和不同的处理器平台之上,通过插入特定类型的Memory Barrier来禁止特定类型的编译器重排序和处理器重排序,为上层提供一致的内存可见性保证。

指令级并行重排序

编译器优化重排序:编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
指令级并行的重排序:现代处理器采用了指令级并行技术(Instruction-Level Parallelism, ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。

内存系统重排序

内存系统的重排序:处理器使用缓存和读写缓冲区,这使得加载和存储操作看上去可能是在乱序执行(强调的是内存缓存)。
在虚拟机层面,为了尽可能减少内存操作速度远慢于CPU运行速度所带来的CPU空置的影响,虚拟机会按照自己的一些规则将程序编写顺序打乱——即写在后面的代码在时间顺序上可能会先执行,而写在前面的代码会后执行——以尽可能充分地利用CPU

内存屏障

内存屏障类型

  • LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
  • StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
  • LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
  • StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能

基于保守策略的JMM内存屏障插入策略:

  • 在每个volatile写操作的前面插入一个StoreStore屏障
  • 在每个volatile写操作的后面插入一个StoreLoad屏障
  • 在每个volatile读操作的后面插入一个LoadLoad屏障
  • 在每个volatile读操作的后面插入一个LoadStore屏障

使用JDK动态代理

Spring AOP默认使用JDK动态代理(基于接口的代理)。

如果被代理的目标对象已经实现了接口,则使用JDK动态代理。如果目标对象未实现任何接口,则会创建CGLIB代理。

使用CGLIB代理注意

  • final方法不建议使用,因为它们无法被覆盖。
  • 除非强制使用CGLIB,需要将元素 <aop:config> 的属性proxy-target-class值设置为true。
  • 需要CGLIB库,找不到CGLIB库类时,Spring会发出警告。
  • 代理对象的构造函数将被调用两次。这是因为CGLIB代理会为每个代理对象生成子类。构造函数中只做赋值和初始化,不能实现其他的逻辑。

Lambda表达式

stream().forEach()循环处理

List<String> list = com.google.common.collect.Lists.newArrayList();
list.add("1");
list.add("2");
list.add("3");

list.stream().forEach(string ->{
    System.out.println(string);
});

stream().map()处理集合,collect(Collectors.toList())结果输出为List

List<String> list1 = Lists.newArrayList();
list1.add("1");
list1.add("2");
list1.add("3");

List<String> list2 = list1.stream().map(string -> {
    return "stream().map()处理之后:" + string;
}).collect(Collectors.toList());

System.out.println(list2.toString());

stream().filter()过滤集合

List<String> list1 = Lists.newArrayList();
list1.add("1");
list1.add("1");
list1.add("2");
list1.add("3");

List<String> list2 = list1.stream().filter(s -> !s.equals("1")).collect(Collectors.toList());
System.out.println(list2.toString());

函数接口

一个只包含一个方法的接口,可以使用Lambda表达式作为参数

new Thread(() -> System.out.println("Hello World!"));

public interface Interface1 {
     void test();
}

private void func(Interface1 interface1) {
    interface1.test();
}

func(() -> System.out.println("Hello World"));

//有参数,参数知名类型更规范
func((Integer x) -> System.out.println("Hello World" + x));

//有参数,有返回值
func((Integer x) -> {
    System.out.println("Hello World" + x);
    return true;
});

EL-Spring 表达式语言,支持xml和注解中使用表达式,类似JSP 的EL 表达式,可以实现普通文件、网址、配置文件、系统环境变量的注入

示例

@PropertySource("classpath:application.properties")

@Value("This is common string") // 注入普通字符串
private String normal;

@Value("#{systemProperties['os.name']}") // 注入操作系统属性
private String osName;

@Value("#{T(java.lang.Math).random()*100.0}") // 注入表达式结果
private double randomNumber;

@Value("#{anotherService.property}") // 注入其他Bean属性
private String propfromAnother;

@Value("#{T(com.demo.el.spring_el_demo.DemoService).getCalc()*100}") // 注入类static方法结果,支持运算处理
private double result;

@Value("classpath:test.txt") // 注入文件资源
private Resource testFile;

@Value("http://www.baidu.com") // 注入网址资源
private Resource testUrl;

@Value("${book.name}") // 注入配置文件
private String bookName;

@Autowired
private Environment environment;