转;http://blog.csdn.net/jackfrued/article/details/17740651
Java程序员面试题集(136-150)
摘要:这一部分主要是数据结构和算法相关的面试题目,虽然只有15道题目,但是包含的信息量还是很大的,很多题目背后的解题思路和算法是非常值得玩味的。
136、给出下面的二叉树先序、中序、后序遍历的序列?
答:先序序列:ABDEGHCF;中序序列:DBGEHACF;后序序列:DGHEBFCA。
补充:二叉树也称为二分树,它是树形结构的一种,其特点是每个结点至多有二棵子树,并且二叉树的子树有左右之分,其次序不能任意颠倒。二叉树的遍历序列按照访问根节点的顺序分为先序(先访问根节点,接下来先序访问左子树,再先序访问右子树)、中序(先中序访问左子树,然后访问根节点,最后中序访问右子树)和后序(先后序访问左子树,再后序访问右子树,最后访问根节点)。如果知道一棵二叉树的先序和中序序列或者中序和后序序列,那么也可以还原出该二叉树。
例如,已知二叉树的先序序列为:xefdzmhqsk,中序序列为:fezdmxqhks,那么还原出该二叉树应该如下图所示:
137、你知道的排序算法都哪些?用Java写一个排序系统。
答:稳定的排序算法有:插入排序、选择排序、冒泡排序、鸡尾酒排序、归并排序、二叉树排序、基数排序等;不稳定排序算法包括:希尔排序、堆排序、快速排序等。
下面是关于排序算法的一个列表:
下面按照策略模式给出一个排序系统,实现了冒泡、归并和快速排序。
Sorter.java
- package com.jackfrued.util;
- import java.util.Comparator;
- /**
- * 排序器接口(策略模式: 将算法封装到具有共同接口的独立的类中使得它们可以相互替换)
- * @author骆昊
- *
- */
- public interfaceSorter {
- /**
- * 排序
- * @param list 待排序的数组
- */
- public <T extends Comparable<T>>void sort(T[] list);
- /**
- * 排序
- * @param list 待排序的数组
- * @param comp 比较两个对象的比较器
- */
- public <T> void sort(T[] list,Comparator<T> comp);
- }
BubbleSorter.java
- package com.jackfrued.util;
- import java.util.Comparator;
- /**
- * 冒泡排序
- * @author骆昊
- *
- */
- public classBubbleSorter implements Sorter {
- @Override
- public <T extends Comparable<T>>voidsort(T[] list) {
- boolean swapped = true;
- for(int i = 1; i < list.length && swapped;i++) {
- swapped= false;
- for(int j = 0; j < list.length - i; j++) {
- if(list[j].compareTo(list[j+ 1]) > 0 ) {
- Ttemp = list[j];
- list[j]= list[j + 1];
- list[j+ 1] = temp;
- swapped= true;
- }
- }
- }
- }
- @Override
- public <T> void sort(T[] list,Comparator<T> comp) {
- boolean swapped = true;
- for(int i = 1; i < list.length && swapped;i++) {
- swapped= false;
- for(int j = 0; j < list.length - i; j++) {
- if(comp.compare(list[j],list[j + 1]) > 0 ) {
- Ttemp = list[j];
- list[j]= list[j + 1];
- list[j+ 1] = temp;
- swapped= true;
- }
- }
- }
- }
- }
MergeSorter.java
- package com.jackfrued.util;
- import java.util.Comparator;
- /**
- * 归并排序
- * 归并排序是建立在归并操作上的一种有效的排序算法。
- * 该算法是采用分治法(divide-and-conquer)的一个非常典型的应用,
- * 先将待排序的序列划分成一个一个的元素,再进行两两归并,
- * 在归并的过程中保持归并之后的序列仍然有序。
- * @author骆昊
- *
- */
- public classMergeSorter implements Sorter {
- @Override
- public <T extends Comparable<T>>voidsort(T[] list) {
- T[]temp = (T[]) newComparable[list.length];
- mSort(list,temp, 0, list.length- 1);
- }
- private <T extends Comparable<T>>voidmSort(T[] list, T[] temp, int low, inthigh) {
- if(low == high) {
- return ;
- }
- else {
- int mid = low + ((high -low) >> 1);
- mSort(list,temp, low, mid);
- mSort(list,temp, mid + 1, high);
- merge(list,temp, low, mid + 1, high);
- }
- }
- private <T extends Comparable<T>>voidmerge(T[] list, T[] temp, int left, intright, intlast) {
- int j = 0;
- int lowIndex = left;
- int mid = right - 1;
- int n = last - lowIndex + 1;
- while (left <= mid && right <= last){
- if (list[left].compareTo(list[right]) < 0){
- temp[j++] = list[left++];
- } else {
- temp[j++] = list[right++];
- }
- }
- while (left <= mid) {
- temp[j++] = list[left++];
- }
- while (right <= last) {
- temp[j++] = list[right++];
- }
- for (j = 0; j < n; j++) {
- list[lowIndex + j] = temp[j];
- }
- }
- @Override
- public <T> void sort(T[] list,Comparator<T> comp) {
- T[]temp = (T[]) newComparable[list.length];
- mSort(list,temp, 0, list.length- 1, comp);
- }
- private <T> void mSort(T[] list, T[]temp, intlow, inthigh, Comparator<T> comp) {
- if(low == high) {
- return ;
- }
- else {
- int mid = low + ((high -low) >> 1);
- mSort(list,temp, low, mid, comp);
- mSort(list,temp, mid + 1, high, comp);
- merge(list,temp, low, mid + 1, high, comp);
- }
- }
- private <T> void merge(T[] list, T[]temp, intleft, intright, intlast, Comparator<T> comp) {
- int j = 0;
- int lowIndex = left;
- int mid = right - 1;
- int n = last - lowIndex + 1;
- while (left <= mid && right <= last){
- if (comp.compare(list[left], list[right]) <0) {
- temp[j++] = list[left++];
- } else {
- temp[j++] = list[right++];
- }
- }
- while (left <= mid) {
- temp[j++] = list[left++];
- }
- while (right <= last) {
- temp[j++] = list[right++];
- }
- for (j = 0; j < n; j++) {
- list[lowIndex + j] = temp[j];
- }
- }
- }
QuickSorter.java
- package com.jackfrued.util;
- import java.util.Comparator;
- /**
- * 快速排序
- * 快速排序是使用分治法(divide-and-conquer)依选定的枢轴
- * 将待排序序列划分成两个子序列,其中一个子序列的元素都小于枢轴,
- * 另一个子序列的元素都大于或等于枢轴,然后对子序列重复上面的方法,
- * 直到子序列中只有一个元素为止
- * @author Hao
- *
- */
- public classQuickSorter implements Sorter {
- @Override
- public <T extends Comparable<T>>voidsort(T[] list) {
- quickSort(list,0, list.length- 1);
- }
- @Override
- public <T> void sort(T[] list,Comparator<T> comp) {
- quickSort(list,0, list.length- 1, comp);
- }
- private <T extends Comparable<T>>voidquickSort(T[] list, int first, intlast) {
- if (last > first) {
- int pivotIndex =partition(list, first, last);
- quickSort(list,first, pivotIndex - 1);
- quickSort(list,pivotIndex, last);
- }
- }
- private <T> void quickSort(T[] list, int first, int last,Comparator<T> comp) {
- if (last > first) {
- int pivotIndex =partition(list, first, last, comp);
- quickSort(list,first, pivotIndex - 1, comp);
- quickSort(list,pivotIndex, last, comp);
- }
- }
- private <T extends Comparable<T>>intpartition(T[] list, int first, intlast) {
- Tpivot = list[first];
- int low = first + 1;
- int high = last;
- while (high > low) {
- while (low <= high&& list[low].compareTo(pivot) <= 0) {
- low++;
- }
- while (low <= high&& list[high].compareTo(pivot) >= 0) {
- high--;
- }
- if (high > low) {
- Ttemp = list[high];
- list[high]= list[low];
- list[low]= temp;
- }
- }
- while (high > first&& list[high].compareTo(pivot) >= 0) {
- high--;
- }
- if (pivot.compareTo(list[high])> 0) {
- list[first]= list[high];
- list[high]= pivot;
- return high;
- }
- else {
- return low;
- }
- }
- private <T> int partition(T[] list, int first, int last,Comparator<T> comp) {
- Tpivot = list[first];
- int low = first + 1;
- int high = last;
- while (high > low) {
- while (low <= high&& comp.compare(list[low], pivot) <= 0) {
- low++;
- }
- while (low <= high&& comp.compare(list[high], pivot) >= 0) {
- high--;
- }
- if (high > low) {
- Ttemp = list[high];
- list[high] = list[low];
- list[low]= temp;
- }
- }
- while (high > first&& comp.compare(list[high], pivot) >= 0) {
- high--;
- }
- if (comp.compare(pivot,list[high]) > 0) {
- list[first]= list[high];
- list[high]= pivot;
- return high;
- }
- else {
- return low;
- }
- }
- }
138、写一个二分查找(折半搜索)的算法。
答:折半搜索,也称二分查找算法、二分搜索,是一种在有序数组中查找某一特定元素的搜索算法。搜素过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜素过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一步骤数组为空,则代表找不到。这种搜索算法每一次比较都使搜索范围缩小一半。
- package com.jackfrued.util;
- import java.util.Comparator;
- public classMyUtil {
- public static <T extends Comparable<T>>int binarySearch(T[] x, T key) {
- return binarySearch(x,0, x.length- 1, key);
- }
- public static <T> int binarySearch(T[] x, T key, Comparator<T> comp) {
- int low = 0;
- int high = x.length - 1;
- while (low <= high) {
- int mid = (low + high) >>> 1;
- int cmp = comp.compare(x[mid], key);
- if (cmp < 0) {
- low= mid + 1;
- }
- else if (cmp > 0) {
- high= mid - 1;
- }
- else {
- return mid;
- }
- }
- return -1;
- }
- private static<T extends Comparable<T>>intbinarySearch(T[] x, int low, inthigh, T key) {
- if(low <= high) {
- int mid = low + ((high -low) >> 1);
- if(key.compareTo(x[mid])== 0) {
- return mid;
- }
- else if(key.compareTo(x[mid])< 0) {
- return binarySearch(x,low, mid - 1, key);
- }
- else {
- return binarySearch(x,mid + 1, high, key);
- }
- }
- return -1;
- }
- }
说明:两个版本一个用递归实现,一个用循环实现。需要注意的是计算中间位置时不应该使用(high+ low) / 2的方式,因为加法运算可能导致整数越界,这里应该使用一下三种方式之一:low+ (high – low) / 2或low + (high – low) >> 1或(low + high) >>> 1(注:>>>是逻辑右移,不带符号位的右移)
139、统计一篇英文文章中单词个数。
答:
- import java.io.FileReader;
- public class WordCounting {
- publicstatic void main(String[] args) {
- try(FileReader fr = new FileReader("a.txt")) {
- intcounter = 0;
- booleanstate = false;
- intcurrentChar;
- while((currentChar= fr.read()) != -1) {
- if(currentChar== ' ' || currentChar == '\n'
- ||currentChar == '\t' || currentChar == '\r') {
- state= false;
- }
- elseif(!state) {
- state= true;
- counter++;
- }
- }
- System.out.println(counter);
- }
- catch(Exceptione) {
- e.printStackTrace();
- }
- }
- }
补充:这个程序可能有很多种写法,这里选择的是Dennis M. Ritchie和Brian W. Kernighan老师在他们不朽的著作《The C Programming Language》中给出的代码,向两位老师致敬。下面的代码也是如此。
140、输入年月日,计算该日期是这一年的第几天。
答:
- import java.util.Scanner;
- public classDayCounting {
- public static void main(String[] args) {
- int[][] data = {
- {31,28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
- {31,29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
- };
- Scannersc = newScanner(System.in);
- System.out.print("请输入年月日(1980 11 28): ");
- int year = sc.nextInt();
- int month = sc.nextInt();
- int date = sc.nextInt();
- int[] daysOfMonth =
- data[(year % 4 == 0 && year % 100 != 0 || year % 400 == 0)?1 : 0];
- int sum = 0;
- for(int i = 0; i < month -1; i++) {
- sum+= daysOfMonth[i];
- }
- sum+= date;
- System.out.println(sum);
- sc.close();
- }
- }
141、约瑟夫环:15个基督教徒和15个非教徒在海上遇险,必须将其中一半的人投入海中,其余的人才能幸免于难,于是30个人围成一圈,从某一个人开始从1报数,报到9的人就扔进大海,他后面的人继续从1开始报数,重复上面的规则,直到剩下15个人为止。结果由于上帝的保佑,15个基督教徒最后都幸免于难,问原来这些人是怎么排列的,哪些位置是基督教徒,哪些位置是非教徒。
答:
- public classJosephu {
- private static final int DEAD_NUM = 9;
- public static void main(String[] args) {
- boolean[] persons = new boolean[30];
- for(int i = 0; i < persons.length; i++) {
- persons[i]= true;
- }
- int counter = 0;
- int claimNumber = 0;
- int index = 0;
- while(counter < 15) {
- if(persons[index]) {
- claimNumber++;
- if(claimNumber == DEAD_NUM) {
- counter++;
- claimNumber= 0;
- persons[index]= false;
- }
- }
- index++;
- if(index >= persons.length) {
- index= 0;
- }
- }
- for(boolean p : persons) {
- if(p) {
- System.out.print("基");
- }
- else {
- System.out.print("非");
- }
- }
- }
- }
142、回文素数:所谓回文数就是顺着读和倒着读一样的数(例如:11,121,1991…),回文素数就是既是回文数又是素数(只能被1和自身整除的数)的数。编程找出11~9999之间的回文素数。
答:
- public classPalindromicPrimeNumber {
- public static void main(String[] args) {
- for(int i = 11; i <= 9999;i++) {
- if(isPrime(i)&& isPalindromic(i)) {
- System.out.println(i);
- }
- }
- }
- public static boolean isPrime(int n) {
- for(int i = 2; i <= Math.sqrt(n);i++) {
- if(n % i == 0) {
- return false;
- }
- }
- return true;
- }
- public static boolean isPalindromic(int n) {
- int temp = n;
- int sum = 0;
- while(temp > 0) {
- sum= sum * 10 + temp % 10;
- temp/= 10;
- }
- return sum == n;
- }
- }
143、全排列:给出五个数字12345的所有排列。
答:
- public classFullPermutation {
- public static void perm(int[] list) {
- perm(list,0);
- }
- private static void perm(int[] list, int k) {
- if (k == list.length) {
- for (int i = 0; i < list.length; i++) {
- System.out.print(list[i]);
- }
- System.out.println();
- }else{
- for (int i = k; i < list.length; i++) {
- swap(list,k, i);
- perm(list,k + 1);
- swap(list,k, i);
- }
- }
- }
- private static void swap(int[] list, int pos1, int pos2) {
- int temp = list[pos1];
- list[pos1]= list[pos2];
- list[pos2]= temp;
- }
- public static void main(String[] args) {
- int[] x = {1, 2, 3, 4, 5};
- perm(x);
- }
- }
144、对于一个有N个整数元素的一维数组,找出它的子数组(数组中下标连续的元素组成的数组)之和的最大值。
答:下面给出几个例子(最大子数组用粗体表示):
1) 数组:{ 1, -2, 3,5, -3, 2 },结果是:8
2) 数组:{ 0, -2, 3, 5, -1, 2 },结果是:9
3) 数组:{ -9, -2,-3, -5, -3 },结果是:-2
可以使用动态规划的思想求解:
- public classMaxSum {
- private static int max(int x, int y) {
- return x > y? x: y;
- }
- public static int maxSum(int[] array) {
- int n = array.length;
- int[] start = new int[n];
- int[] all = new int[n];
- all[n- 1] = start[n - 1] = array[n - 1];
- for(int i = n - 2; i >= 0;i--) {
- start[i]= max(array[i], array[i] + start[i + 1]);
- all[i]= max(start[i], all[i + 1]);
- }
- return all[0];
- }
- public static void main(String[] args) {
- int[] x1 = { 1, -2, 3, 5,-3, 2 };
- int[] x2 = { 0, -2, 3, 5,-1, 2 };
- int[] x3 = { -9, -2, -3,-5, -3 };
- System.out.println(maxSum(x1)); // 8
- System.out.println(maxSum(x2)); // 9
- System.out.println(maxSum(x3)); //-2
- }
- }
145、用递归实现字符串倒转
答:
- public classStringReverse {
- public static String reverse(StringoriginStr) {
- if(originStr == null || originStr.length()== 1) {
- return originStr;
- }
- return reverse(originStr.substring(1))+ originStr.charAt(0);
- }
- public static void main(String[] args) {
- System.out.println(reverse("hello"));
- }
- }
146、输入一个正整数,将其分解为素数的乘积。
答:
- public classDecomposeInteger {
- private static List<Integer> list = newArrayList<Integer>();
- public static void main(String[] args) {
- System.out.print("请输入一个数: ");
- Scannersc = newScanner(System.in);
- int n = sc.nextInt();
- decomposeNumber(n);
- System.out.print(n + " = ");
- for(int i = 0; i < list.size() - 1; i++) {
- System.out.print(list.get(i) + " * ");
- }
- System.out.println(list.get(list.size() - 1));
- }
- public static void decomposeNumber(int n) {
- if(isPrime(n)) {
- list.add(n);
- list.add(1);
- }
- else {
- doIt(n,(int)Math.sqrt(n));
- }
- }
- public static void doIt(int n, int div) {
- if(isPrime(div)&& n % div == 0) {
- list.add(div);
- decomposeNumber(n/ div);
- }
- else {
- doIt(n,div - 1);
- }
- }
- public static boolean isPrime(int n) {
- for(int i = 2; i <= Math.sqrt(n);i++) {
- if(n % i == 0) {
- return false;
- }
- }
- return true;
- }
- }
147、一个有n级的台阶,一次可以走1级、2级或3级,问走完n级台阶有多少种走法。
答:可以通过递归求解。
- public classGoSteps {
- public static int countWays(int n) {
- if(n < 0) {
- return 0;
- }
- else if(n == 0) {
- return 1;
- }
- else {
- return countWays(n - 1) + countWays(n - 2) + countWays(n -3);
- }
- }
- publicstaticvoidmain(String[] args) {
- System.out.println(countWays(5)); // 13
- }
- }
148、写一个算法判断一个英文单词的所有字母是否全都不同(不区分大小写)。
答:
- public classAllNotTheSame {
- public static boolean judge(String str) {
- Stringtemp = str.toLowerCase();
- int[] letterCounter = new int[26];
- for(int i = 0; i <temp.length(); i++) {
- int index = temp.charAt(i)- 'a';
- letterCounter[index]++;
- if(letterCounter[index]> 1) {
- return false;
- }
- }
- return true;
- }
- public static void main(String[] args) {
- System.out.println(judge("hello"));
- System.out.print(judge("smile"));
- }
- }
149、有一个已经排好序的整数数组,其中存在重复元素,请将重复元素删除掉,例如,A= [1, 1, 2, 2, 3],处理之后的数组应当为A= [1, 2, 3]。
答:
- import java.util.Arrays;
- public classRemoveDuplication {
- public static int[] removeDuplicates(int a[]) {
- if(a.length <= 1) {
- return a;
- }
- int index = 0;
- for(int i = 1; i < a.length; i++) {
- if(a[index] != a[i]) {
- a[++index] = a[i];
- }
- }
- int[] b = new int[index + 1];
- System.arraycopy(a, 0, b, 0, b.length);
- return b;
- }
- publicstaticvoidmain(String[] args) {
- int[] a = {1, 1, 2, 2, 3};
- a = removeDuplicates(a);
- System.out.println(Arrays.toString(a));
- }
- }
150、给一个数组,其中有一个重复元素占半数或半数以上,找出这个元素。
答:
- public classFindMost {
- public static <T> T find(T[] x){
- Ttemp = null;
- for(int i = 0, nTimes = 0; i< x.length;i++) {
- if(nTimes == 0) {
- temp= x[i];
- nTimes= 1;
- }
- else {
- if(x[i].equals(temp)) {
- nTimes++;
- }
- else {
- nTimes--;
- }
- }
- }
- return temp;
- }
- public static void main(String[] args) {
- String[]strs = {"hello","kiss","hello","hello","maybe"};
- System.out.println(find(strs));
- }
- }
Java程序员面试题集(136-150)(转)的更多相关文章
-
Java程序员面试题集(1-50)(转)
转:http://blog.csdn.net/jackfrued/article/details/17339393 下面的内容是对网上原有的Java面试题集及答案进行了全面修订之后给出的负责任的题目和 ...
-
Java程序员面试题集(1-50
下面的内容是对网上原有的Java面试题集及答案进行了全面修订之后给出的负责任的题目和答案,原来的题目中有很多重复题目和无价值的题目,还有不少的参考答案也是错误的,修改后的Java面试题集参照了JDK最 ...
-
Java程序员面试题集(71-85)(转)
转:http://blog.csdn.net/jackfrued/article/details/17566627 Java程序员面试题集(71-85) 摘要:这一部分主要包括了UML(统一建模语言) ...
-
Java程序员面试题集(51-70)(转)
转:http://blog.csdn.net/jackfrued/article/details/17403101 Java程序员面试题集(51-70) 摘要:这一部分主要讲解了异常.多线程.容器和I ...
-
Java程序员面试题集(86-115)
摘 要:下面的内容包括Struts 2和Hibernate的常见面试题,虽然Struts 2在2013年6月曝出高危漏洞后已经显得江河日下,而Spring MVC的异军突起更加加速了Struts 2的 ...
-
Java程序员面试题集2
51.类ExampleA 继承Exception,类ExampleB 继承ExampleA. 有如下代码片断: try{ throw new ExampleB("b") }catc ...
-
Java程序员面试题集(151-180)
Java面试题集(151-180) 摘要:这部分包含了Spring.Spring MVC以及Spring和其他框架整合以及测试相关的内容,除此之外还包含了大型网站技术架构相关面试内容. 151. Sp ...
-
Java程序员面试题集(116-135)
摘要:这一部分讲解基于Java的Web开发相关面试题,即便在Java走向没落的当下,基于Java的Web开发因为拥有非常成熟的解决方案,仍然被广泛应用.不管你的Web开发中是否使用框架,JSP和Ser ...
-
Java程序员面试题收集(6)
<!————————————————————————————基础题122道,代码题19道————————————————————————————> JAVA相关基础知识1.面向对象的特征有 ...
随机推荐
-
十一个行为模式之中介者模式(Mediator Pattern)
定义: 用一个中介对象(中介者)来封装一系列的对象交互,使各个对象之间不需要显式地相互引用,从而降低耦合度,而且可以独立地改变他们之间的交互关系. 解耦后: 结构图: Mediator:抽象中介者,定 ...
-
Android真机调试 Android.Util.AndroidRuntimeException: You cannot combine custom titles with other title features
参考连接:http://blog.csdn.net/scyatcs/article/details/9003285 Android.Util.AndroidRuntimeException: You ...
- maven+svn忽略提交到svn的文件
-
前端学PHP之文件操作(认真读读)
前面的话 在程序运行时,程序本身和数据一般都存在内存中,当程序运行结束后,存放在内存中的数据被释放.如果需要长期保存程序运行所需的原始数据,或程序运行产生的结果,就需要把数据存储在文件或数据库.一般地 ...
-
linux tricks 之VA系列函数.
VA函数(variable argument function),参数个数可变函数,又称可变参数函数.C/C++编程中,系统提供给编程人员的va函数很少.*printf()/*scanf()系列函数, ...
-
鸟哥的linux私房菜学习记录之bash
当你对计算机输入一个指令时,bash会将指令传送给核心kernel,核心再去调用相关的程序,启动硬件. 如果直接让用户操作操作系统,可能会造成系统的崩溃,所以操作系统通过应用程序来让用户操作系统即壳程 ...
-
生成ssl证书
http://blog.csdn.net/liuchunming033/article/details/48470575 https://segmentfault.com/a/119000000256 ...
-
iOS开发RunTime之函数调用
文章来自小笨狼的iOS博客,一直认为csdn的博客UI不太好看,看博客不太爽.所以自己搭建了一个博客. 欢迎各位去链接中看我的博客.也欢迎大家加QQ群讨论iOS技术问题 经过两个多月的面试,工作最终尘 ...
-
创建简单WEB高可用集群
1. 环境介绍 我这里弄了2个虚拟机,信息如下: node1:192.168.168.201 node2:192.168.168.202 2.配置主机名 [root@node1 ~]# vim /et ...
-
Shiro与CAS整合实现单点登录
1.简介 CAS:Yale 大学发起的一个开源项目,旨在为 Web 应用系统提供一种可靠的单点登录方法. Shiro:Apache Shiro是一个Java安全框架,可以帮助我们完成认证.授权.会话管 ...