后端面试大纲

面试大纲

1、集合框架

简介

集合框架:用于存储数据的容器。

集合框架是为表示和操作集合而规定的一种统一的标准的体系结构。
任何集合框架都包含三大块内容:对外的接口、接口的实现和对集合运算的算法。

接口:表示集合的抽象数据类型。接口允许我们操作集合时不必关注具体实现,从而达到“多态”。在面向对象编程语言中,接口通常用来形成规范。

实现:集合接口的具体实现,是重用性很高的数据结构。

算法:在一个实现了某个集合框架中的接口的对象身上完成某种有用的计算的方法,例如查找、排序等。这些算法通常是多态的,因为相同的方法可以在同一个接口被多个类实现时有不同的表现。事实上,算法是可复用的函数。
它减少了程序设计的辛劳。

集合框架通过提供有用的数据结构和算法使你能集中注意力于你的程序的重要部分上,而不是为了让程序能正常运转而将注意力于低层设计上。
通过这些在无关API之间的简易的互用性,使你免除了为改编对象或转换代码以便联合这些API而去写大量的代码。 它提高了程序速度和质量。

特点

  • 对象封装数据,对象多了也需要存储。集合用于存储对象。

  • 对象的个数确定可以使用数组,对象的个数不确定的可以用集合。因为集合是可变长度的。

集合和数组的区别

  • 数组是固定长度的;集合可变长度的。

  • 数组可以存储基本数据类型,也可以存储引用数据类型;集合只能存储引用数据类型。

  • 数组存储的元素必须是同一个数据类型;集合存储的对象可以是不同数据类型。

数据结构:就是容器中存储数据的方式。

对于集合容器,有很多种。因为每一个容器的自身特点不同,其实原理在于每个容器的内部数据结构不同。

集合容器在不断向上抽取过程中,出现了集合体系。在使用一个体系的原则:参阅顶层内容。建立底层对象。

使用集合框架的好处

1、容量自增长;
2、提供了高性能的数据结构和算法,使编码更轻松,提高了程序速度和质量;
3、允许不同 API 之间的互操作,API之间可以来回传递集合;
4、可以方便地扩展或改写集合,提高代码复用性和可操作性。
5、通过使用JDK自带的集合类,可以降低代码维护和学习新API成本。

Iterator接口

Iterator接口,用于遍历集合元素的接口。

在Iterator接口中定义了三个方法:

修饰与类型 方法与描述
boolean hasNext() 如果仍有元素可以迭代,则返回true。
E next() 返回迭代的下一个元素
void remove() 从迭代器指向的 collection 中移除迭代器返回的最后一个元素(可选操作)。

每一个集合都有自己的数据结构(就是容器中存储数据的方式),都有特定的取出自己内部元素的方式。为了便于操作所有的容器,取出元素。将容器内部的取出方式按照一个统一的规则向外提供,这个规则就是Iterator接口,使得对容器的遍历操作与其具体的底层实现相隔离,达到解耦的效果。

也就说,只要通过该接口就可以取出Collection集合中的元素,至于每一个具体的容器依据自己的数据结构,如何实现的具体取出细节,这个不用关心,这样就降低了取出元素和具体集合的耦合性。

使用迭代器遍历集合元素

public static void main(String[] args) {
    List<String> list1 = new ArrayList<>();
    list1.add("abc0");
    list1.add("abc1");
    list1.add("abc2");

    // while循环方式遍历
    Iterator it1 = list1.iterator();
    while (it1.hasNext()) {
        System.out.println(it1.next());
    }

    // for循环方式遍历
    for (Iterator it2 = list1.iterator(); it2.hasNext(); ) {
        System.out.println(it2.next());
    }

}

使用Iterator迭代器进行删除集合元素,则不会出现并发修改异常。

因为:在执行remove操作时,同样先执行checkForComodification(),然后会执行ArrayList的remove()方法,该方法会将modCount值加1,这里我们将expectedModCount=modCount,使之保持统一。

ListIterator接口

ListIterator是一个功能更加强大的迭代器, 它继承于Iterator接口,只能用于各种List类型的访问。可以通过调用listIterator()方法产生一个指向List开始处的ListIterator, 还可以调用listIterator(n)方法创建一个一开始就指向列表索引为n的元素处的ListIterator。

特点

1、允许我们向前、向后两个方向遍历 List;
2、在遍历时修改 List 的元素;
3、遍历时获取迭代器当前游标所在位置。

常用API

修饰与类型 方法与描述
void add(E e) 将指定的元素插入到列表 (可选操作)。
boolean hasNext() 如果此列表迭代器在前进方向还有更多的元素时,返回 true
boolean hasPrevious() 如果此列表迭代器在相反方向还有更多的元素时,返回 true
int nextIndex() 返回调用 next()后返回的元素索引。
E next() 返回列表中的下一个元素和光标的位置向后推进。
int nextIndex() 返回调用 next()后返回的元素索引。
E previous() 返回列表中的上一个元素和光标的位置向前移动。
int previousIndex() 返回调用previous() 后返回的元素索引 。
void remove() 删除列表中调用next()previous()的返回最后一个元素。
void set(E e) 用指定元素替换列表中调用next()previous()的返回最后一个元素。

Collection接口

所有集合类都位于java.util包下。Java的集合类主要由两个接口派生而出:CollectionMap,Collection和Map是Java集合框架的根接口,这两个接口又包含了一些子接口或实现类。

  • Collection一次存一个元素,是单列集合;

  • Map一次存一对元素,是双列集合。Map存储的一对元素:键–值,键(key)与值(value)间有对应(映射)关系。

Collection集合主要有List和Set两大接口

  • List:有序(元素存入集合的顺序和取出的顺序一致),元素都有索引。元素可以重复。
  • Set:无序(存入和取出顺序有可能不一致),不可以存储重复元素。必须保证元素唯一性。

List常用方法

ArrayList、LinkedList、Vector 的区别
ArrayList LinkedList Vector
底层实现 数组 双向链表 数组
同步性及效率 不同步,非线程安全,效率高,支持随机访问 不同步,非线程安全,效率高 同步,线程安全,效率低
特点 查询快,增删慢 查询慢,增删快 查询快,增删慢
默认容量 10 / 10
扩容机制 int newCapacity = oldCapacity + (oldCapacity >> 1);//1.5 倍 / 2 倍

总结

  • ArrayList 和 Vector 基于数组实现,对于随机访问get和set,ArrayList优于LinkedList,因为LinkedList要移动指针。
  • LinkedList 不会出现扩容的问题,所以比较适合随机位置增、删。但是其基于链表实现,所以在定位时需要线性扫描,效率比较低。
  • 当操作是在一列数据的后面添加数据而不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList会提供比较好的性能;
  • 当你的操作是在一列数据的前面或中间添加或删除数据,并且按照顺序访问其中的元素时,就应该使用LinkedList了。
遍历时操作元素

遍历集合时,同时操作集合中的元素(增删等)

/**
  * Description: for循环遍历
  * 输出结果:
  * [a, b, c, d, e]
  * 由结果可知,第二个元素b并未删除,原因是当第一个元素b被删除后,它后面所有的元素都向前移动了一个单位,循环时导致第二个元素b漏掉了
  */
public static void remove(List<String> list) {
    for (int i = 0; i < list.size(); i++) {
        String s = list.get(i);
        if (s.equals("b")) {
            list.remove(s);
        }
    }
}

/**
  * Description: foreach循环遍历
  *
  * 会报错:java.util.ConcurrentModificationException。这是因为在这里,foreach循环遍历容器本质上是使用迭代器进行遍历的,会对修改次数modCount进行检查,不允许集合进行更改操作
     */
public static void remove2(List<String> list) {
    for (String s : list) {
        if (s.equals("b")) {
            list.remove(s);
        }
        System.out.println(s);
    }
}

/**
  * Description: 使用迭代器遍历
  */
public static void remove3(List<String> list) {
    Iterator<String> it = list.iterator();
    while (it.hasNext()) {
        String s = it.next();
        if (s.equals("b")) {
            it.remove();
        }
    }
}

使用迭代器遍历删除时,能够避免方法二中出现的问题。这是因为:在ArrayList中,modCount是指集合的修改次数,当进行add或者delete时,modCount会+1;expectedModCount是指集合的迭代器的版本号,初始值是modCount,但是当集合进行add或者delete操作时,modCount会+1,而expectedModCount不会改变,所以方法二中会抛出异常。但是it.remove操作时,会同步expectedModCount的值,把modCount的值赋予expectedModCount。所以不会抛出异常。

总结:如果想正确的循环遍历删除(增加)元素,需要使用方法三,也就是迭代器遍历删除(增加)的方法。

Set集合

Set集合元素无序(存入和取出的顺序不一定一致),并且没有重复对象。
Set的主要实现类:HashSet, TreeSet。

Set常用方法

Set常用方法

HashSet、TreeSet、LinkedHashSet的区别
HashSet TreeSet LinkedHashSet
底层实现 HashMap 红黑树 LinkedHashMap
重复性 不允许重复 不允许重复 不允许重复
有无序 无序 有序,支持两种排序方式,自然排序和定制排序,其中自然排序为默认的排序方式。 有序,以元素插入的顺序来维护集合的链接表
时间复杂度 add(),remove(),contains()方法的时间复杂度是O(1) add(),remove(),contains()方法的时间复杂度是O(logn) LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍微逊色于HashSet,时间复杂度是 O(1)。
同步性 不同步,线程不安全 不同步,线程不安全 不同步,线程不安全
null值 允许null值 不支持null值,会抛出 java.lang.NullPointerException 异常。因为TreeSet应用 compareTo() 方法于各个元素来比较他们,当比较null值时会抛出 NullPointerException异常。 允许null值
比较 equals() compareTo() equals()

HashSet如何检查重复
当你把对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他加入的对象的hashcode值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象,这时会调用equals()方法来检查hashcode相等的对象是否真的相同。如果两者相同,HashSet就不会让加入操作成功。
hashCode()与equals()的相关规定:

  • 如果两个对象相等,则hashcode一定也是相同的

  • 两个对象相等,equals方法返回true

  • 两个对象有相同的hashcode值,它们也不一定是相等的

  • 综上,equals方法被覆盖过,则hashCode方法也必须被覆盖
    hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。

总结:
HashSet是一个通用功能的Set,而LinkedHashSet 提供元素插入顺序保证,TreeSet是一个SortedSet实现,由Comparator 或者 Comparable指定的元素顺序存储元素。

Map接口

Map 是一种把键对象和值对象映射的集合,它的每一个元素都包含一对键对象和值对象。 Map没有继承于Collection接口,从Map集合中检索元素时,只要给出键对象,就会返回对应的值对象。
Map 的常用实现类:HashMap、TreeMap、HashTable、LinkedHashMap、ConcurrentHashMap

Map常用方法

Map常用方法

HashMap、HashTable、TreeMap的区别

  • TreeMap:基于红黑树实现。
  • HashMap:基于哈希表实现。
  • HashTable:和 HashMap 类似,但它是线程安全的,这意味着同一时刻多个线程可以同时写入 HashTable 并且不会导致数据不一致。它是遗留类,不应该去使用它。现在可以使用 ConcurrentHashMap 来支持线程安全,并且 ConcurrentHashMap 的效率会更高,因为 ConcurrentHashMap 引入了分段锁。
  • LinkedHashMap:使用双向链表来维护元素的顺序,顺序为插入顺序或者最近最少使用(LRU)顺序。
HashMap HashTable TreeMap
底层实现 哈希表(数组+链表) 哈希表(数组+链表) 红黑树
同步性 线程不同步 同步 线程不同步
null值 允许 key 和 Vale 是 null,但是只允许一个 key 为 null,且这个元素存放在哈希表 0 角标位置 不允许key、value 是 null value允许为null。
当未实现 Comparator 接口时,key 不可以为null
当实现 Comparator 接口时,若未对 null 情况进行判断,则可能抛 NullPointerException 异常。如果针对null情况实现了,可以存入,但是却不能正常使用get()访问,只能通过遍历去访问。
————————————————
hash 使用hash(Object key)扰动函数对 key 的 hashCode 进行扰动后作为 hash 值 直接使用 key 的 hashCode() 返回值作为 hash 值
容量 容量为 2^4 且容量一定是 2^n 默认容量是11,不一定是 2^n
扩容 两倍,且哈希桶的下标使用 &运算代替了取模 2倍+1,取哈希桶下标是直接用模运算

HashMap在JDK1.7和JDK1.8中有哪些不同

不同 JDK 1.7 JDK 1.8
存储结构 数组 + 链表 数组 + 链表 + 红黑树
初始化方式 单独函数:inflateTable() 直接集成到了扩容函数resize()
hash值计算方式 扰动处理 = 9次扰动 = 4次位运算 + 5次异或运算 扰动处理 = 2次扰动 = 1次位运算 + 1次异或运算
存放数据的规则 无冲突时,存放数组;冲突时,存放链表 无冲突时,存放数组;冲突 & 链表长度 < 8:存放单链表;冲突 & 链表长度 > 8:树化并存放红黑树
插入数据方式 头插法(先讲原位置的数据移到后1位,再插入数据到该位置) 尾插法(直接插入到链表尾部/红黑树)
扩容后存储位置的计算方式 全部按照原来方法进行计算(即hashCode ->> 扰动函数 ->> (h&length-1)) 按照扩容后的规律计算(即扩容后的位置=原位置 or 原位置 + 旧容量)

集合工具类Collections

Collections:集合工具类,方便对集合的操作。这个类不需要创建对象,内部提供的都是静态方法。

静态方法:

Collections.sort(list);//list集合进行元素的自然顺序排序。
Collections.sort(list,new ComparatorByLen());//按指定的比较器方法排序。
class ComparatorByLen implements Comparator<String>{
  public int compare(String s1,String s2){
     int temp = s1.length()-s2.length();
     return temp==0?s1.compareTo(s2):temp;
  }
}
Collections.max(list);//返回list中字典顺序最大的元素。
int index = Collections.binarySearch(list,"zz");//二分查找,返回角标。
Collections.reverseOrder();//逆向反转排序。
Collections.shuffle(list);//随机对list中的元素进行位置的置换。

//将非同步集合转成同步集合的方法:Collections中的  XXX synchronizedXXX(XXX);  
//原理:定义一个类,将集合所有的方法加同一把锁后返回。
List synchronizedList(list);
Map synchronizedMap(map);

Collection 和 Collections的区别

Collections是个java.util下的类,是针对集合类的一个工具类,提供一系列静态方法,实现对集合的查找、排序、替换、线程安全化(将非同步的集合转换成同步的)等操作。

  Collection是个java.util下的接口,它是各种集合结构的父接口,继承于它的接口主要有SetList,提供了关于集合的一些操作,如插入、删除、判断一个元素是否其成员、遍历等。

数组工具类 Arrays

用于操作数组对象的工具类,里面都是静态方法。

数组 -> 集合:asList方法,将数组转换成list集合。

String[] arr ={"abc","kk","qq"};
List<String> list =Arrays.asList(arr);//将arr数组转成list集合。
将数组转换成集合,有什么好处呢?用aslist方法,将数组变成集合;
可以通过list集合中的方法来操作数组中的元素:isEmpty()、contains、indexOf、set;

注意(局限性):数组是固定长度,不可以使用集合对象增加或者删除等,会改变数组长度的功能方法。比如add、remove、clear。(会报不支持操作异常UnsupportedOperationException);

如果数组中存储的引用数据类型,直接作为集合的元素可以直接用集合方法操作。

如果数组中存储的是基本数据类型,asList会将数组实体作为集合元素存在。

集合 -> 数组:用的是Collection接口中的toArray()方法;

如果给toArray传递的指定类型的数据长度小于了集合的size,那么toArray方法,会自定再创建一个该类型的数据,长度为集合的size。

如果传递的指定的类型的数组的长度大于了集合的size,那么toArray方法,就不会创建新数组,直接使用该数组即可,并将集合中的元素存储到数组中,其他为存储元素的位置默认值null。

所以,在传递指定类型数组时,最好的方式就是指定的长度和size相等的数组。

将集合变成数组后有什么好处?限定了对集合中的元素进行增删操作,只要获取这些元素即可。

用基本数据类型的数组转换ArrayList,ArrayList的size有问题

public static void main(String[] args) {
    int[] arr1 = { 1, 2, 3, 4, 5 };
    List<int[]> intList = Arrays.asList(arr1);
    // intList size: 1
    System.out.println(String.format("intList size: %s", intList.size()));
    
    Integer[] arr2 = { 1, 2, 3, 4, 5 };
List<Integer> integerList = Arrays.asList(arr2);
// integerList size: 5
System.out.println(String.format("integerList size:%s", integerList.size()));

asList方法接受的参数是一个泛型的变长参数,我们知道基本数据类型是无法泛型化的,也就是说基本类型是无法作为asList方法的参数的, 要想作为泛型参数就必须使用其所对应的包装类型。但是这个这个实例中为什么没有出错呢?因为该实例是将int 类型的数组当做其参数,而在Java中数组是一个对象,它是可以泛型化的。所以该例子是不会产生错误的。既然例子是将整个int 类型的数组当做泛型参数,那么经过asList转换就只有一个int 的列表了.

结论:

在使用asList()时尽量不要将基本数据类型数组转List.

asList转换得到的ArrayList不是java.util.ArrayList

public static void main(String[] args) {
    String[] arr = {"abc", "kk", "qq"};
    List<String> list = Arrays.asList(arr);
    // 添加一个元素,抛出异常UnsupportedOperationException
    list.add("bb");
}

原因:

此处ArrayList是Arrays的内部类,并没有add方法,add方法是父类AbstractList的,但是没有具体实现,
而是直接抛出UnsupportedOperationException异常.

Arrays内部类ArrayList

正确操作

public static void main(String[] args) {
    String[] arr = {"abc", "kk", "qq"};
	// 使用new ArrayList包裹一层
    List<String> list = new ArrayList<>(Arrays.asList(arr));
    list.add("bb");
}

如何选用集合?

主要根据集合的特点来选用,比如我们需要根据键值获取到元素值时就选用Map接口下的集合,需要排序时选择TreeMap,不需要排序时就选择HashMap,需要保证线程安全就选用ConcurrentHashMap.当我们只需要存放元素值时,就选择实现Collection接口的集合,需要保证元素唯一时选择实现Set接口的集合比如TreeSet或HashSet,不需要就选择实现List接口的比如ArrayList或LinkedList,然后再根据实现这些接口的集合的特点来选用。

说说List,Set,Map三者的区别?

List(对付顺序的好帮手): List接口存储一组不唯一(可以有多个元素引用相同的对象),有序的对象
Set(注重独一无二的性质): 不允许重复的集合。不会有多个元素引用相同的对象。
Map(用Key来搜索的专家): 使用键值对存储。Map会维护与Key有关联的值。两个Key可以引用相同的对象,但Key不能重复,典型的Key是String类型,但也可以是任何对象。

Arraylist 与 LinkedList 区别?

  1. 是否保证线程安全: ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;

  2. 底层数据结构: Arraylist 底层使用的是 Object 数组;LinkedList 底层使用的是 双向链表 数据结构(JDK1.6之前为循环链表,JDK1.7取消了循环。注意双向链表和双向循环链表的区别,下面有介绍到!)

  3. 插入和删除是否受元素位置的影响: ① ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。 比如:执行add(E e)方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是O(1)。但是如果要在指定位置 i 插入和删除元素的话(add(int index, E element))时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。 ② LinkedList 采用链表存储,所以对于add(E e)方法的插入,删除元素时间复杂度不受元素位置的影响,近似 O(1),如果是要在指定位置i插入和删除元素的话((add(int index, E element)) 时间复杂度近似为o(n))因为需要先移动到指定位置再插入。

  4. 是否支持快速随机访问: LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index)方法)。

  5. 内存空间占用: ArrayList的空 间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)。

ArrayList 与 Vector 区别呢?为什么要用Arraylist取代Vector呢?

Vector类的所有方法都是同步的。可以由两个线程安全地访问一个Vector对象、但是一个线程访问Vector的话代码要在同步操作上耗费大量的时间。

Arraylist不是同步的,所以在不需要保证线程安全时建议使用Arraylist。

HashMap 和 Hashtable 的区别

线程是否安全: HashMap 是非线程安全的,HashTable 是线程安全的;HashTable 内部的方法基本都经过synchronized 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap 吧!);

效率: 因为线程安全的问题,HashMap 要比 HashTable 效率高一点。另外,HashTable 基本被淘汰,不要在代码中使用它;
对Null key 和Null value的支持: HashMap 中,null 可以作为键,这样的键只有一个,可以有一个或多个键所对应的值为 null。。但是在 HashTable 中 put 进的键值只要有一个 null,直接抛出 NullPointerException。

初始容量大小和每次扩充容量大小的不同 : ①创建时如果不指定容量初始值,Hashtable 默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap 默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。②创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为2的幂次方大小(HashMap 中的tableSizeFor()方法保证,下面给出了源代码)。也就是说 HashMap 总是使用2的幂作为哈希表的大小,后面会介绍到为什么是2的幂次方。

底层数据结构: JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制。

1.什么是Java异常

答:异常是发生在程序执行过程中阻碍程序正常执行的错误事件。比如:用户输入错误数据、硬件故障、网络阻塞等都会导致出现异常。只要在Java语句执行中产生了异常,一个异常对象就会被创建,JRE就会试图寻找异常处理程序来处理异常。如果有合适的异常处理程序,异常对象就会被异常处理程序接管,否则,将引发运行环境异常,JRE终止程序执行。 Java异常处理框架只能处理运行时错误,编译错误不在其考虑范围之内。

2.Java异常处理中有哪些关键字?

答:

· throw:有时我们需要显式地创建并抛出异常对象来终止程序的正常执行。throw关键字用来抛出并处理运行时异常。

· throws:当我们抛出任何“被检查的异常(checked exception)”并不处理时,需要在方法签名中使用关键字throws来告知调用程序此方法可能会抛出的异常。调用方法可能会处理这些异常,或者同样用throws来将异常传给上一级调用方法。throws关键字后可接多个潜在异常,甚至是在*main()*中也可以使用throws。

· try-catch:我们在代码中用try-catch块处理异常。当然,一个try块之后可以有多个catch子句,try-catch块也能嵌套。每个catch块必须接受一个(且仅有一个)代表异常类型的参数。

· finally:finally块是可选的,并且只能配合try-catch一起使用。虽然异常终止了程序的执行,但是还有一些打开的资源没有被关闭,因此,我们能使用finally进行关闭。不管异常有没有出现,finally块总会被执行。

**3.描述一下异常的层级。

答:Java异常是层级的,并通过继承来区分不同种类的异常。

· Throwable是所有异常的父类,它有两个直接子对象Error,Exception,其中Exception又被继续划分为“被检查的异常(checked exception)”和”运行时的异常(runtime exception,即不受检查的异常)”。 Error表示编译时和系统错误,通常不能预期和恢复,比如硬件故障、JVM崩溃、内存不足等。

· 被检查的异常(Checked exception)在程序中能预期,并要尝试修复,如FileNotFoundException。我们必须捕获此类异常,并为用户提供有用信息和合适日志来进行调试。Exception是所有被检查的异常的父类。

· 运行时异常(Runtime Exception)又称为不受检查异常,源于糟糕的编程。比如我们检索数组元素之前必须确认数组的长度,否则就可能会抛出ArrayIndexOutOfBoundException运行时异常。RuntimeException是所有运行时异常的父类。

后端面试大纲_第1张图片

4.Java异常类有哪些的重要方法?

答:Exception和它的所有子类没有提供任何特殊方法供使用,它们的所有方法都是来自其基类Throwable。

· String getMessage():方法返回Throwable的String型信息,当异常通过构造器创建后可用。

· String getLocalizedMessage():此方法通过被重写来得到用本地语言表示的异常信息返回给调用程序。Throwable类通常只是用getMessage()方法来实现返回异常信息。

· synchronized Throwable getCause():此方法返回异常产生的原因,如果不知道原因的话返回null。(原文有拼写错误 应该是if 不是id)

· String toString():方法返回String格式的Throwable信息,此信息包括Throwable的名字和本地化信息。

· void printStackTrace():该方法打印栈轨迹信息到标准错误流。该方法能接受PrintStream 和PrintWriter作为参数实现重载,这样就能实现打印栈轨迹到文件或流中。

**5.**描述Java 7 ARM(Automatic Resource Management,自动资源管理)特征和多个catch块的使用

答:如果一个try块中有多个异常要被捕获,catch块中的代码会变丑陋的同时还要用多余的代码来记录异常。有鉴于此,Java 7的一个新特征是:一个catch子句中可以捕获多个异常。示例代码如下:

catch(IOException | SQLException | Exception ex){

logger.error(ex);

throw new MyException(ex.getMessage());

}

大多数情况下,当忘记关闭资源或因资源耗尽出现运行时异常时,我们只是用finally子句来关闭资源。这些异常很难调试,我们需要深入到资源使用的每一步来确定是否已关闭。因此,Java 7用try-with-resources进行了改进:在try子句中能创建一个资源对象,当程序的执行完try-catch之后,运行环境自动关闭资源。下面是这方面改进的示例代码:

try (MyResource mr = new MyResource()) {

System.out.println(“MyResource created in try-with-resources”);

} catch (Exception e) {

e.printStackTrace();

}

反射

1、java反射机制的作用

1)在运行时判断任意一个对象所属的类
2)在运行时构造任意一个类的对象
3)在运行时判断任意一个类所具有的成员变量和方法
4)在运行时调用任意一个对象的方法
反射就是动态加载对象,并对对象进行剖析。在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法,这种动态获取信息以及动态调用对象方法的功能成为Java反射机制。

优点:可以动态的创建对象和编译,最大限度发挥了java的灵活性。

缺点:对性能有影响。使用反射基本上一种解释操作,告诉JVM我们要做什么并且满足我们的要求,这类操作总是慢于直接执行java代码。

  1. 如何使用java的反射?
    a. 通过一个全限类名创建一个对象
    1)、Class.forName(“全限类名”); 例如:com.mysql.jdbc.Driver Driver类已经被加载到 jvm中,并且完成了类的初始化工作就行了
    2)、类名.class; 获取Class<?> clz 对象
    3)、对象.getClass();
    b. 获取构造器对象,通过构造器new出一个对象
    1). Clazz.getConstructor([String.class]);
    2). Con.newInstance([参数]);
    c. 通过class对象创建一个实例对象(就相当与new类名()无参构造器)
    1). Clazz.newInstance();

d. 通过class对象获得一个属性对象

1)、Field c =clz. getFields():获得某个类的所有的公共(public)的字段,包括父类中的字段。
2)、Field c=clz.getDeclaredFields():获得某个类的所有声明的字段,即包括public、private和proteced,但是不包括父类的申明字段 e. e、通过class对象获得一个方法对象
1). Clazz.getMethod(“方法名”,class……parameaType);(只能获取公共的)
2). Clazz.getDeclareMethod(“方法名”);(获取任意修饰的方法,不能执行私有)
3) M.setAccessible(true);(让私有的方法可以执行)
f. 让方法执行
1). Method.invoke(obj实例对象,obj可变参数);-----(是有返回值的)

多线程

  1. 什么是线程?

线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。程序员可以通过它进行多处理器编程,可以使用多线程对运算密集型任务提速。比如,如果一个线程完成一个任务要100毫秒,那么用十个线程完成该任务只需10毫秒。

  1. 线程和进程有什么区别?

线程是进程的子集,一个进程可以有很多线程,每条线程并发执行不同的任务。不同的进程使用不同的内存空间,而所有的线程共享一片相同的内存空间。

3)线程的实现方式?使用哪个更好?

有两种方式,一是继承Thread类,二是实现Runnable接口。

java不支持类的多继承,但是允许实现多个接口,所以继承了Thread类就不能继承其他类,而且Thread类实际上也是实现了Runnable接口,所以最好使用Runnable接口实现线程。

4)Thread类种的start()和run()方法有什么区别?

start()方法用来启动新创建的线程,而且start()方法内部调用了run()方法;当调用run()方法的时候,只会在原来的线程中调用,并没有启动新的线程。

5)什么是线程安全性?如何编写线程安全的代码?

定义:当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么这个类就可以称为是线程安全的。

编写线程安全的代码,其核心在于要对状态访问操作进行管理,特别是对共享的和可变的状态的访问。

无状态的变量一定是线程安全的,即没有共享变量

6)什么是竞态条件?举例说明

竞态条件就是,由于不正确的执行时序,而出现不正确的结果。

最常见的竞态条件类型就是“先检查后执行”操作,即通过一个可能失效的观测结果来决定下一步的动作。

比如,++count操作,看上去是一个操作,但其实这个操作并非是原子的,实际上它包含了3个独立的操作,读取count,将其+1,将计算结果写入count,这是一个“读取-修改-写入”的操作序列,并且其结果状态依赖于之前的状态。

7)什么是内置锁?

每个java对象都可以用作一个实现同步的锁,这些锁称为内置锁。

其使用方式就是使用synchronized关键字、synchronized 方法或者 synchronized 代码块。线程在进入同步代码块之前自动获得锁,在退出同步代码块时自动释放锁,获得内置锁的唯一途径就是进入由这个锁保护的同步代码块或方法。

8)什么是可见性问题?如何解决?

一个共享变量,如果线程A做了修改,线程B随后读取到的还是旧值,导致线程读取到脏数据,这就是可见性问题。

解决:

  1. 加锁:内置锁可以保证可见性,确保某个线程以一种可预测的方式来查看另一个线程的执行结果。

  2. 使用volatile变量:可以确保将变量的更新操作 通知到其他线程。

9)什么是volatile变量?什么时候使用volatile?

volatile是一种轻量级的锁,它能够保证可见性,但不能保证原子性。

orm

ORM概念
什么是“持久化”
即把数据(如内存中的对象)保存的磁盘的某一文件中。

什么是持久层
持久层(Persistence Layer),即实现数据持久化应用领域的一个逻辑层面,将数据使用者和数据实体相关联。

什么是ORM
ORM,即Object Relational Mapping,它是对象关系模型的简称。它的作用是在关系型数据库和对象之间作一个映射。使程序能够通过操纵描述对象方式来操纵数据库。

ORM作用
ORM解决的主要问题是对象关系的映射,一般情况下,一个持久化类和一个表对应,类的每个实例对应表中的一条记录,类的每个属性对应表的每个字段。 ## ORM技术特点:
1.提高了开发效率。由于ORM可以自动对Entity对象与数据库中的Table进行字段与属性的映射,所以我们实际可能已经不需要一个专用的、庞大的数据访问层。
2.ORM提供了对数据库的映射,不用sql直接编码,能够像操作对象一样从数据库获取数据。

ORM的优劣处
优点
提高开发效率

通常的系统设计中,使用 JDBC 操作数据库,业务处理逻辑和数据存取逻辑是混杂在一起的。
一般基本上都有如下步骤:
	1、注册驱动,获取链接,即Connection对象。
	2、根据输入组装sql语句
	3、创建执行sql语句对象(静态SQL语句statement、动态SQL语句PrepareStatement、存储过程CallableStatement4、执行SLQ语句(查询用executeQuery、修改用executeUpdate、判断返回值是否为结果集用execute),获取结果集。
	5、按特定的业务逻辑处理结果集,组装更新SQL语句。
	6、执行更新SQL语句,以更新数据库中的数据
	6、关闭链接。
其中的业务处理逻辑和数据存取逻辑完全混杂在一块,还不包括执行失败的处理逻辑。而一个完整的系统包含成千上万个这样重复的而又混杂的处理过程。假如对其中某条业务逻辑进行修改,要改动的代码量不可想象。其次,用户的运行环境和要求千差万别,公司不可能为每一个用户设计一套一样的系统。

所以就要将业务逻辑和数据存逻辑分开。另一方面,关系型数据库基本以行为单位进行存取,而程序运行却是以对象方式进行处理。为解决这一困难,就出现 ORM 这一个对象和数据之间映射技术

解耦合

客户的需求可能随时变更,有些时候,我们不得不通过增删字段的方式来满足客户的需求。由于ORM可以自动对Entity对象与数据库中的Table进行字段与属性的映射,不用sql直接编码,能够像操作对象一样从数据库获取数据。

缺点
减低程序性能

1、从系统结构上来看,采用ORM的系统一般都是多层系统,系统的层次多了,效率就会降低。ORM是一种完全的面向对象的做法,而面向对象的做法也会对性能产生一定的影响。
2、ORM所生成的代码一般不太可能写出很高效的算法。主要体现在对持久对象的提取和和数据的加工处理上。如果用上了ORM,很可能将全部的数据提取到内存对象中,然后再进行过滤和加工处理,这样就容易产生性能问题。
3、在对对象做持久化时,ORM一般会持久化所有的属性,有时,这是不希望的。

从输入URL到浏览器显示页面发生了什么

1、输入网址

当你开始输入网址比如www.cnblogs.com时游览器就可以在书签或者历史记录里面去搜索相关的网址推荐给你。

2、浏览器查找域名的IP地址

① 请求发起后,游览器首先会解析这个域名,首先它会查看本地硬盘的 hosts 文件,看看其中有没有和这个域名对应的规则,如果有的话就直接使用 hosts 文件里面的 ip 地址。

② 如果在本地的 hosts 文件没有能够找到对应的 ip 地址,浏览器会发出一个 DNS请求到本地DNS(域名分布系统)服务器 。本地DNS服务器一般都是你的网络接入服务器商提供,比如中国电信,中国移动。

③查询你输入的网址的DNS请求到达本地DNS服务器之后,本地DNS服务器会首先查询它的缓存记录,如果缓存中有此条记录,就可以直接返回结果,此过程是递归的方式进行查询。如果没有,本地DNS服务器还要向DNS根服务器进行查询

④根DNS服务器没有记录具体的域名和IP地址的对应关系,而是告诉本地DNS服务器,你可以到域服务器上去继续查询,并给出域服务器的地址。这种过程是迭代的过程

⑤本地DNS服务器继续向域服务器发出请求,在这个例子中,请求的对象是.com域服务器。.com域服务器收到请求之后,也不会直接返回域名和IP地址的对应关系,而是告诉本地DNS服务器,你的域名的解析服务器的地址

⑥最后,本地DNS服务器向域名的解析服务器发出请求,这时就能收到一个域名和IP地址对应关系,本地DNS服务器不仅要把IP地址返回给用户电脑,还要把这个对应关系保存在缓存中,以备下次别的用户查询时,可以直接返回结果,加快网络访问。

后端面试大纲_第2张图片

3、建立TCP链接

在拿到域名对应的IP地址后,会以随机端口(1024~~65535)向WEB服务器程序80端口发起TCP的连接请求,这个连接请求进入到内核的TCP/IP协议栈(用于识别该连接请求,解封包,一层一层的剥开),还有可能要经过Netfilter防火墙(属于内核的模块)的过滤,最终到达WEB程序,最终建立了TCP/IP的连接,对于客户端与服务器的TCP链接,必然要说的就是『三次握手』。

后端面试大纲_第3张图片

三次握手

客户端发送一个带有SYN标志的数据包给服务端,服务端收到后,回传一个带有SYN/ACK标志的数据包以示传达确认信息,最后客户端再回传一个带ACK标志的数据包,代表握手结束,连接成功。

通俗化之后就是:

客户端:老弟我要跟你链接

服务端:好的,同意了

客户端:好嘞

4、游览器向WEB服务器发起Http请求

建立TCP连接之后,发起HTTP请求,请求一般分为三部分

请求方法URI协议/版本

请求头(Request Header)

请求正文

下面是一个完整的请求

img

详细的就不描述了,网上很多说明的。

5、服务器端处理

服务器端收到请求后的由web服务器(准确说应该是http服务器)处理请求,诸如Apache、Ngnix、IIS等。web服务器解析用户请求,知道了需要调度哪些资源文件,再通过相应的这些资源文件处理用户请求和参数,并调用数据库信息,最后将结果通过web服务器返回给浏览器客户端。

后端面试大纲_第4张图片

6、关闭TCP链接

为了避免服务器与客户端双方的资源占用和损耗,当双方没有请求或响应传递时,任意一方都可以发起关闭请求。与创建TCP连接的3次握手类似,关闭TCP连接,需要4次握手。

后端面试大纲_第5张图片

上图可以通俗化:

客户端:老弟,我这边没数据要传了,咱们关闭链接吧

服务端:好的,接收到了,我看看我这边还有没有要传的

服务端:我这边也没有了,关闭吧

客户端:好嘞

7、游览器解析资源

对于获取到的HTML、CSS、JS、图片等等资源。

浏览器通过解析HTML,生成DOM树,解析CSS,生成CSS规则树,然后通过DOM树和CSS规则树生成渲染树。渲染树与DOM树不同,渲染树中并没有head、display为none等不必显示的节点。

在解析CSS的同时,可以继续加载解析HTML,但在解析执行JS脚本时,会停止解析后续HTML,这就会出现阻塞问题,关于JS阻塞相关问题,这里不过多阐述,后面会单独开篇讲解。

后端面试大纲_第6张图片

webkit渲染流程

8、游览器布局渲染

根据渲染树布局,计算CSS样式,即每个节点在页面中的大小和位置等几何信息。HTML默认是流式布局的,CSS和js会打破这种布局,改变DOM的外观样式以及大小和位置。这时就要提到两个重要概念:repaint和reflow。

repaint:屏幕的一部分重画,不影响整体布局,比如某个CSS的背景色变了,但元素的几何尺寸和位置不变。
reflow: 意味着元件的几何尺寸变了,我们需要重新验证并计算渲染树。是渲染树的一部分或全部发生了变化。这就是Reflow,或是Layout。

有些情况下,比如修改了元素的样式,浏览器并不会立刻 reflow 或 repaint 一次,而是会把这样的操作积攒一批,然后做一次 reflow,这又叫异步 reflow 或增量异步 reflow。
有些情况下,比如 resize 窗口,改变了页面默认的字体等。对于这些操作,浏览器会马上进行 reflow。

好的,完毕!

索引

索引的基本原理

索引用来快速寻找那些具有特定值的记录,如果没有索引,一般遍历整张表

索引的原理:就是把无序的数据变成有序的查询

1、把创建了索引的列的内容进行排序

2、对排序结果生成倒排表

3、在倒排表内容上拼接上数据地址链

4、在查询的时候,先拿到倒排表内容,再取出数据地址链,从而拿到具体数据

聚簇索引和非聚簇索引的区别

都是B+树的数据结构

通常情况下,建立索引是加快查询速度的有效手段。但索引不是万能的,靠索 引并不能实现对所有数据的快速存取。事实上,如果索引策略和数据检索需求严重不符的话,建立索引反而会降低查询性能。因此在实际使用当中,应该充分考虑到 索引的开销,包括磁盘空间的开销及处理开销(如资源竞争和加锁)。例如,如果数据频繁的更新或删加,就不宜建立索引。

本文简要讨论一下聚簇索引的特点及其与非聚簇索引的区别。

  • 建立索引:

在SQL语言中,建立聚簇索引使用CREATE INDEX语句,格式为:CREATE CLUSTER INDEX index_name ON table_name(column_name1,column_name2,…);

  • 存储特点:
  1. 聚集索引。表数据按照索引的顺序来存储的,也就是说索引项的顺序与表中记录的物理顺序一致。对于聚集索引,叶子结点即存储了真实的数据行,不再有另外单独的数据页。 在一张表上最多只能创建一个聚集索引,因为真实数据的物理顺序只能有一种。
  2. 非聚集索引。表数据存储顺序与索引顺序无关。对于非聚集索引,叶结点包含索引字段值及指向数据页数据行的逻辑指针,其行数量与数据表行数据量一致。

总结一下:聚集索引是一种稀疏索引,数据页上一级的索引页存储的是页指针,而不是行指针。而对于非聚集索引,则是密集索引,在数据页的上一级索引页它为每一个数据行存储一条索引记录。

  • 更新表数据

1、向表中插入新数据行
如果一张表没有聚集索引,那么它被称为“堆集”(Heap)。这样的表中的数据行没有特定的顺序,所有的新行将被添加到表的末尾位置。而建立了聚簇索引的数据表则不同:最简单的情况下,插入操作根据索引找到对应的数据页,然后通过挪动已有的记录为新数据腾出空间,最后插入数据。如果数据页已满,则需要拆分数据页,调整索引指针(且如果表还有非聚集索引,还需要更新这些索引指向新的数据页)。而类似于自增列为聚集索引的,数据库系统可能并不拆分数据页,而只是简单的新添数据页。

2、从表中删除数据行

对删除数据行来说:删除行将导致其下方的数据行向上移动以填充删除记录造成的空白。如果删除的行是该数据页中的最后一行,那么该数据页将被回收,相应的索 引页中的记录将被删除。对于数据的删除操作,可能导致索引页中仅有一条记录,这时,该记录可能会被移至邻近的索引页中,原索引页将被回收,即所谓的“索引 合并”。

聚簇索引确定表中数据的物理顺序。聚簇索引类似于电话簿,后者按姓氏排列数据。由于聚簇索引规定数据在表中的物理存 储顺序,因此一个表只能包含一个聚簇索引。但该索引可以包含多个列(组合索引),就像电话簿按姓氏和名字进行组织一样。汉语字典也是聚簇索引的典型应用, 在汉语字典里,索引项是字母+声调,字典正文也是按照先字母再声调的顺序排列。

聚簇索引对于那些经常要搜索范围值的列特别有效。使用聚簇索引找到包含第一个值的行后,便可以确保包含后续索引值的行在物理相邻。例如,如果应用程序执行的一个查询经常检索某一日期范围内的记录,则使用聚集索引可以迅速找到包含开始日期的行,然后检索表中所有相邻的行,直到到达结束日期。这样有助于提高此类查询的性能。同样,如果对从表中检索的数据进行排序时经常要用到某一列,则可以将该表在该列上聚簇(物理排序),避免每次查询该列时都进行排序,从而节省成本。

建立聚簇索引的思想

1、大多数表都应该有聚簇索引或使用分区来降低对表尾页的竞争,在一个高事务的环境中,对最后一页的封锁严重影响系统的吞吐量。

2、在聚簇索引下,数据在物理上按顺序排在数据页上,重复值也排在一起,因而在那些包含范围检查 (between、<、<=、>、>=)或使用group by或orderby的查询时,一旦找到具有范围中第一个键值的行,具有后续索引值的行保证物理上毗连在一起而不必进一步搜索,避免了大范围扫描,可以大 大提高查询速度。

3、在一个频繁发生插入操作的表上建立聚簇索引时,不要建在具有单调上升值的列(如IDENTITY)上,否则会经常引起封锁冲突。

4、在聚簇索引中不要包含经常修改的列,因为码值修改后,数据行必须移动到新的位置。

5、选择聚簇索引应基于where子句和连接操作的类型。

不知从什么角度来对比,只能说说各自的特点,希望对你有用。
1、聚簇索引
a) 一个索引项直接对应实际数据记录的存储页,可谓“直达”
b) 主键缺省使用它
c) 索引项的排序和数据行的存储排序完全一致,利用这一点,想修改数据的存储顺序,可以通过改变主键的方法(撤销原有主键,另找也能满足主键要求的一个字段或一组字段,重建主键)
d) 一个表只能有一个聚簇索引(理由:数据一旦存储,顺序只能有一种)

2、非聚簇索引
a) 不能“直达”,可能链式地访问多级页表后,才能定位到数据页
b) 一个表可以有多个非聚簇索引





第二种理解:

聚簇索引是对磁盘上实际数据重新组织以按指定的一个或多个列的值排序的算法。特点是存储数据的顺序和索引顺序一致。
一般情况下主键会默认创建聚簇索引,且一张表只允许存在一个聚簇索引。

在《数据库原理》一书中是这么解释聚簇索引和非聚簇索引的区别的:
聚簇索引的叶子节点就是数据节点,而非聚簇索引的叶子节点仍然是索引节点,只不过有指向对应数据块的指针。

因此,MYSQL中不同的数据存储引擎对聚簇索引的支持不同就很好解释了。
下面,我们可以看一下MYSQL中MYISAM和INNODB两种引擎的索引结构。

如原始数据为:
后端面试大纲_第7张图片
MyISAM引擎的数据存储方式如图:
后端面试大纲_第8张图片

MYISAM是按列值与行号来组织索引的。它的叶子节点中保存的实际上是指向存放数据的物理块的指针。
从MYISAM存储的物理文件我们能看出,MYISAM引擎的索引文件(.MYI)和数据文件(.MYD)是相互独立的。

而InnoDB按聚簇索引的形式存储数据,所以它的数据布局有着很大的不同。它存储数据的结构大致如下:
后端面试大纲_第9张图片
注**:聚簇索引中的每个叶子节点包含主键值、事务ID、回滚指针(rollback pointer用于事务和MVCC)和余下的列(如col2)。**

INNODB的二级索引与主键索引有很大的不同。InnoDB的二级索引的叶子包含主键值,而不是行指针(row pointers),这减小了移动数据或者数据页面分裂时维护二级索引的开销,因为InnoDB不需要更新索引的行指针。其结构大致如下:
后端面试大纲_第10张图片

INNODB和MYISAM的主键索引与二级索引的对比:
后端面试大纲_第11张图片

InnoDB的的二级索引的叶子节点存放的是KEY字段加主键值。因此,通过二级索引查询首先查到是主键值,然后InnoDB再根据查到的主键值通过主键
索引找到相应的数据块。而MyISAM的二级索引叶子节点存放的还是列值与行号的组合,叶子节点中保存的是数据的物理地址。所以可以看出MYISAM的主
键索引和二级索引没有任何区别,主键索引仅仅只是一个叫做PRIMARY的唯一、非空的索引,且MYISAM引擎中可以不设主键

mysql索引的数据结构,各自优劣

索引的数据结构和具体存储引擎的实现有关,在MySQL中使用较多的索引有Hash索引,B+树索引等**,InnoDB存储引擎的默认索引实现为:B+树索引**,对于哈希索引来说,底层的数据结构就是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能块,其余大部分场景,建议选择BTree索引

**B+树是一个平衡的多叉树, 从根结点到每个叶子结点的高度差值不超过1,**而且同层级的结点间有指针相互链接。在B+树上的常规检索,从根结点到叶子结点的搜索效率基本相当,不会出现大幅波动,而且基于索引的顺序扫描时,也可以利用双指针快速左右移动,效率非常高,因此,B+树索引被广泛应用于数据库、文件系统等场景。

哈希索引:

哈希索引就是采用一定的哈希算法,把键值换算成新的哈希值,检索时不需要类似B+树那样从根节点到叶子结点逐级查找,只需依次哈希算法即可定位到相应的位置,速度非常快

如果是等值查询,那么哈希索引明显有绝对优势,因为只需要经过一次算法即可找到对应的值,前提是键值都是唯一的,如果键值不是唯一的,就需要先找到该键所在位置,然后在根据链表往后扫描,直到找到对应的数据;

如果是范围查询检索,这时候哈希索引就毫无用武之地,因为原先是有序的键值,经过哈希算法后,有可能变成不连续的了,就没办法再利用索引完成范围查询检索。

B+树索引的关键字检索效率比较平均,不像B树那样波动幅度大,在有大量数据键值重复情况下,哈希索引的效率也是极低的,因为存在哈希碰撞问题

索引设计原则

查询更快,占用空间小

1、适合索引的列是出现在where子句中的列,或者连接子句中指定的列

2、基数较小的类,索引效果较差,没有必要在此列建立索引

3、使用短索引

4、不要过度索引,索引需要额外的磁盘空间

5、外键一定要建立索引

6、更新频繁的字段不适合建立索引

7、辨识度不高的字段不适合作索引 (查询的字段越少 区分度越高 比如性别1区分度就较低)

8、尽量的扩展索引,不要新建索引

9、查询中很少涉及的列,重复值较多的列不适合建立索引

10、对于定义为text、image和bit的数据类型不适合建立索引

mysql 锁的类型

基于锁的属性分类:共享锁、排它锁。

基于锁的粒度分类:行级锁(InnoDB)、表级锁(InnoDB、MYISAM)、页级锁(BDB引擎)记录锁、间隙锁、临建锁。

基于锁的状态分类:意向共享锁、意向排它锁

  • 共享锁
共享锁又称读锁,简称S锁;当一个事务为数据加上读锁之后,其他事务只能对该数据加读锁,而不能对数据加写锁,直到所有的读锁释放之后其他事务才能对其进行加持写锁,共享锁的特性主要是为了支持并发的读取数据,读取数据的时候不支持修改,避免出现重复读的问题
  • 排它锁
排它锁又称为写锁,简称X锁:当一个事务为数据加上写锁时,其他请求将不能再为数据加任何锁,直到该锁释放之后,其他事务才能对数据进行加锁,排它锁的目的是在数据修改时,不允许其他人同时修改,也不允许其他人读取,避免了出现脏数据和脏读的问题
  • 表锁
表锁是指上锁的时候锁住的是整个表,当下一个事务访问该表的时候,必须等前一个事务释放了锁才能对表进行访问
特点:力度大,加锁简单,容易冲突
  • 行锁
行锁是指上锁的时候锁住的是表的某一行或多行记录,其他事务访问同一张表时,只有被锁住的记录不能访问,其他的记录可正常访问:
特点:粒度小,加锁比表锁麻烦,不容易冲突,相比表锁支持的并发要高
  • 记录锁
记录锁也属于行锁中的一种,只不过记录锁的范围只是表中的某一条记录,记录锁是说事务在加锁后锁住的只是表的某一条记录。
精准条件命中,并且命中的条件字段是唯一索引
加了记录锁之后数据可以避免在查询的时候被修改的重复度问题,也避免了在修改的事务未提交之前被其他事务读取的脏读问题
  • 页锁
页级锁是MySQL中锁定粒度介于行锁和表锁中间的一种锁,表级锁速度快,但冲突多,行级冲突少,但速度较慢。所以取了折中的页级,一次锁定相邻的一组记录
特点:开销和加锁时间介于表锁和行锁之间:会出现死锁,锁定粒度介于表锁和行锁之间,并发度一般,
  • 间隙锁
属于行锁的一种,间隙锁是在事务加锁后其锁住的是表记录的某一个区间,当表的相邻ID之间出现空隙则会形成一个区间,遵循左开右闭原则
范围查询并且查询未命中记录,查询条件必须命中索引、间隙锁只会出现在(重复度)的事务级别中。
触发条件:防止幻读问题,事务并发的时候,如果没有间隙锁,就会发生如下图的问题,在同一个事务里,A事务的两次查询出的结果会不一样
  • 临建锁
也属于行锁的一种,并且它是INNODB的行锁默认算法,总结来说它就是记录锁和间隙锁的组合,临建锁会把查询出来的记录锁住,同时也会把啊该范围查询的所有间隙空间也会锁住,再之它会把相邻的下一个区间也会锁住
触发条件:范围查询并命中,查询命中了索引
结合记录锁和间隙锁的特性,临建锁避免了在范围查询时出现脏读、重复度、幻读问题,加了临建锁后,在范围区间内数据不允许被修改和插入。

SQL优化

一、为什么要对SQL进行优化

我们开发项目上线初期,由于业务数据量相对较少,一些SQL的执行效率对程序运行效率的影响不太明显,而开发和运维人员也无法判断SQL对程序的运行效率有多大,故很少针对SQL进行专门的优化,而随着时间的积累,业务数据量的增多,SQL的执行效率对程序的运行效率的影响逐渐增大,此时对SQL的优化就很有必要。

二、SQL优化的一些方法

1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。

2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num is null
可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:
select id from t where num=0

3.应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。

4.应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num=10 or num=20
可以这样查询:
select id from t where num=10
union all
select id from t where num=20

5.in 和 not in 也要慎用,否则会导致全表扫描,如:
select id from t where num in(1,2,3)
对于连续的数值,能用 between 就不要用 in 了:
select id from t where num between 1 and 3

6.下面的查询也将导致全表扫描:
select id from t where name like ‘%abc%’

7.应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where num/2=100
应改为:
select id from t where num=100*2

8.应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where substring(name,1,3)=‘abc’–name以abc开头的id
应改为:
select id from t where name like ‘abc%’

9.不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。

10.在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。

11.不要写一些没有意义的查询,如需要生成一个空表结构:
select col1,col2 into #t from t where 1=0
这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:
create table #t(…)

12.很多时候用 exists 代替 in 是一个好的选择:
select num from a where num in(select num from b)
用下面的语句替换:
select num from a where exists(select 1 from b where num=a.num)

13.并不是所有索引对查询都有效,SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引,如一表中有字段sex,male、female几乎各一半,那么即使在sex上建了索引也对查询效率起不了作用。

14.索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,
因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。
一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。

15.尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。
这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。

16.尽可能的使用 varchar 代替 char ,因为首先变长字段存储空间小,可以节省存储空间,
其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。

17.任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。

18.避免频繁创建和删除临时表,以减少系统表资源的消耗。

19.临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使用导出表。

20.在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,
以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。

21.如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。

22.尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。

23.使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。

24.与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。
在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。

25.尽量避免大事务操作,提高系统并发能力。

26.尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。

2.1 TCP的报文格式

想要了解TCP的三次握手与四次挥手,首先得了解一下TCP的报文格式,因为这两个过程需要依赖TCP报文中的一些字段。下面我们通过一幅图来讲解TCP的报文格式:

后端面试大纲_第12张图片

我只介绍TCP报文中,与三次握手和四次挥手有关的部分:

  • 源端口号:本次TCP连接中,发起连接的主机使用的端口号;
  • 目的端口号:本次TCP连接主,接受连接的主机使用的端口号;
  • 序号:通过TCP传输的每一个数据段,都有一个序号,作用是为了确认此数据段的顺序。网络中允许传输的数据长度是有限制的,所以当我们要通过TCP传输一个较大的数据时,TCP会将数据切割成很多小的数据段进行传输。而将这些小的数据段发送到目的主机时(发送方会同时发送多个数据),并不能保证它们是按顺序到达目的地,所以对于每一个数据段,都要有一个序号,来标识它们是属于总数据的哪一部分,以保证在目的主机中能将他们重新拼接。
  • 确认序号:接收方若接收到一个数据段,会发送一个确认报文给发送方,告诉发送方已经接收到这个数据段,而确认序号的作用就是告诉发送方接收到了哪条数据段。若接收方接收到了序号为n的报文段,则确认序号将是n+1,表示它已经接收了n,下一条想要接收n+1
  • 首部长度TCP报文的首部+选项的字节数;
  • ACK:只有1 bit的标志位,若为1,表示这个数据段中的确认序号是有效的,即这个数据报是对之前接收到的某个报文的确认(一个TCP报文可以同时作为确认报文和传递数据报文)。
  • RST:只有1 bit的标志位,若客户端向服务器的一个端口请求建立TCP连接,但是服务器的那个端口并不允许建立连接(比如没开启此端口),则服务器会回送一个TCP报文,将RST位置为1,告诉客户端不要再向这个端口发起连接;
  • SYN:只有1 bit的标志位,若为1,表示这是一条建立连接的TCP报文段;
  • FIN:只有1 bit的标志位,若为1,表示这是一条断开连接的TCP报文段;

对于TCP报文格式,就先介绍这么多,其余的部分虽然也很重要,但是并没有作用于TCP连接的建立与断开,所以就不在这里叙述了。

2.2 TCP三次握手的过程

TCP建立连接的过程中需要发送三次报文,所以TCP建立连接也被称为三次握手,接下来我就来讲讲这三次握手的过程,假设客户端向服务器发起TCP连接:

  • 第一步:客户端的TCP程序首先向服务器的TCP程序发送一个TCP报文。这个报文不包含数据,且它的SYN标志位被置为1,表示这是一条建立连接的TCP报文段,因此这个报文段也被称为SYN报文段。客户端的TCP程序随机选择一个序号作为客户端报文的初始序号(假设序号为client_isn),放入这个报文段的序号部分。这个报文段由运输层传递到网络层后,被封装在一个IP数据报中发往服务器;
  • 第二步:包含SYN报文段的IP数据报被服务器接收,服务器的网络层将SYN数据报抽取出来,交给运输层,同时服务器为该TCP连接分配资源(包括发送缓存、接收缓存和变量等),并向客户发送允许连接的TCP报文段。这条允许连接的报文段不包含数据,SYN标志位也被置为1,同时它的ACK标志位也被置为1,表示它是SYN报文段的确认报文,所以这条允许连接的报文段也被称为SYNACK报文段。服务器随机选择一个序号,作为服务器报文段的初始序号(假设称为server_isn),并将其放入SYNACK报文段的序号部分,同时确认号字段被设置为client_isn + 1SYN报文段的序号+1)。这个报文段可以解释为服务器向客户端说:“我收到了你的连接请求,我允许你连接,我的初始序号是server_isn”。
  • 第三步:当客户端接收到SYNACK报文段后,它也将为TCP连接分配资源(缓存和变量),同时生成一条SYNACK报文段的确认报文,并发送给服务器。由于经过上面两个步骤,已经算是建立了连接,所以这次的SYN标志位将被置为0,而不是1ACK标志位是1)。同时,这条报文段的序号被设置为client_isn + 1(第一条客户报文的序号是client_isn,而这是它的下一条,所以+1),而确认序号被设置为server_isn + 1(第一条服务器报文的序号是server_isn,客户端成功接收,所以期望服务器下一次发送server_isn + 1)。和上面两条报文不同,第三条报文可以携带数据,比如HTTP的请求就是在TCP的第三次握手报文中发送到服务器的。

经过上面这三个步骤,TCP连接就算正式建立完毕,客户端和服务器可以相互发送数据了。下面是这个过程的图片形式:

后端面试大纲_第13张图片

2.3 为什么是三次握手而不是两次

首先我们要明确,两次握手是必要的。第一次握手,客户端将SYN报文发送到服务器,服务器接收到报文后,即可确认客户端到服务器是可达的;而服务器向客户端发送响应的SYNACK报文,客户端接收到后,即可确认服务器到客户端也是可达的。至此,连接已经算是建立,那为什么还要有第三次握手呢?

客户端和服务器的握手过程,不仅仅是确认互相可达的过程,更重要的是一个同步的过程,SYN就是同步(Synchronize)的缩写。对于TCP报文段来说,序号是一个至关重要的部分,它保证了TCP传输数据的完整性。而我们上面也说过,TCP报文的初始序号不是从0开始的,而是一个随机的序号,而所谓的同步,就是TCP客户端和服务器互相同步初始序号的过程。第一次握手,客户端发送SYN报文,将自己的初始序号发送到了服务器,服务器接收到后,向客户端发送SYNACK报文段,告诉客户端已经收到了它的初始序号,同时在这个报文段中带上了自己的初始序号。这个时候,第三次握手的作用就出来了:第三次握手实际上就是客户端在告诉服务器,自己已经收到了它的初始序号,完成了同步,可以开始相互传输数据了。若没有第三次握手,服务器将无法保证客户端接收到了自己的SYNACK报文段,若此时SYNACK报文段丢失,客户端不知道服务器的初始序号,将无法处理之后到达客户端的数据。

在很多书籍和网上的博客中还流传另外一种说法。若仅仅是两次握手,将产生以下问题:客户端向服务器发送SYN报文段请求建立连接,但是没有在指定时间内收到SYNACK报文段,所以客户端认为SYN报文段在网络中丢失,则再次发送SYN报文段,并成功接收到了SYNACK报文段,但是客户端在很短的时间内就断开了TCP连接。然而,最初的SYN报文并没有丢失,只是传输时延太长,过了许久才到达。等它到达服务器时,其实客户端已经与服务器建立过TCP连接,并且已经断开了。此时服务器接收到这条SYN报文段,以为客户端又想建立一条新的连接,于是向客户端回送ACK报文,并为连接分配了资源。由于没有第三次握手,服务器将不知道这其实是上一次连接的报文,于是将它创建一个新的TCP连接并维持,直至因为太久没有接收到数据而释放。这种情况非常浪费资源,所以为了防止这种情况的发生,才需要客户端的再一次确认。

实际上,上面所述的第一点才是TCP三次握手的原因,第二个只能算是顺带的好处吧,从建立连接的报文被称为SYN(同步)就可以看出这点。

2.4 TCP四次挥手的过程

说完了三次握手,下面来说说四次挥手的过程。TCP在断开连接时,客户端与服务器之间要交换四次报文,所以,TCP的断开连接也叫四次挥手。

  • 第一步:客户端进程发出断开连接指令,这将导致客户端的TCP程序创建一个特殊的TCP报文段,发送到服务器。这个报文段的FIN字段被置为1,表示这是一条断开连接的报文;
  • 第二步:服务器接收到客户端发来的断开连接报文,向客户端回送这个报文的确认报文(ACK字段为1),告诉服务器已经接收到FIN报文,并允许断开连接;
  • 第三步:服务器发送完确认报文后,服务器的TCP程序创建一条自己的断开连接报文,此报文的FIN字段被置为1,然后发往客户端;
  • 第四步:客户端接收到服务器发来的FIN报文段,则产生一条确认报文(ACK1),发送给服务器,告知服务器已经接收到了它的断开报文。服务器接收到这条ACK报文段后,释放TCP连接相关的资源(缓存和变量),而客户端等待一段时间后(半分钟、一分钟或两分钟),也释放处于客户端的缓存和变量;

以上就是四次挥手的过程,相对建立连接来说要简单一些。以上是以客户端请求断开连接来举例,但其实也可以由服务器断开连接。

后端面试大纲_第14张图片

2.5 客户端为什么要等待一段时间再释放资源

看完上面四次挥手的过程,可能有的人会有疑问,四次挥手之后,连接不是已经断开了吗,为什么客户端还要等待一段时间再释放资源呢?原因有两个:

  • 原因一:客户端接收到服务器发送的FIN报文后(第三次挥手),会回送一条确认报文(第四次挥手),但是,客户端并不知道这条确认报文是否可以顺利到达服务器。若这条确认报文在传送到服务器的过程中损坏、丢失或超时,将引起服务器重新发送FIN报文,客户端接收到后,将需要再次发送一条确认报文,直到服务器正确接收。但是,客户端发送确认报文后,立刻释放资源,将导致无法处理重传的FIN报文,所以客户端需要等待一段时间,直到确认没有出现上述情况出现再释放资源。
  • 原因二TCP四次挥手完成后,理论上已经断开了连接,但是这不代表之前通过这条连接发送的所有数据都处理完毕了,有些可能还在网络中传输。若在四次挥手后,立即释放客户端的资源,然后客户端立即以同一个源端口,向服务器的同一个目的端口再次建立一个TCP连接,这个连接和上一个的 源端口+源IP+目的端口+目的IP 都一模一样,此时将会产生问题。若上一次连接遗留在网络中的报文此时到达,将会被当做新连接传输的数据处理,于是可能会产生一些不可预估的错误。所以,客户端在断开连接后,需要等待一段时间,直到网络中遗留的数据都死掉,才释放资源,而在资源没有被释放前,是不允许建立一个 源端口+源IP+目的端口+目的IP 都一模一样的TCP连接的(因为TCP套接字由这四部分标识)。

2.6 断开连接为什么需要四次挥手

有的人可能也会想,断开连接为什么是四次挥手,不能是两次呢?其中一方请求断开连接,另一方确认即可,为什么这个过程需要两边各发起一次?原因就是:TCP连接是全双工的。什么是全双工,即AB建立连接,则A可以向B发送数据,而B也可以向A发送数据。

我们知道断开连接的请求什么时候发起?当然就是在不再有数据需要发送时。我们依旧以客户端向服务器断开连接为例。假设客户端和服务器建立了一个TCP连接,在客户端需要向服务器发送的数据都发送完后,客户端就可以向服务器发送一个FIN报文段,请求断开连接;服务器接收到后,将会回送一个ACK报文,告诉客户端,自己已经收到了它断开连接的请求。若只有两次挥手,这个时候连接就算是断开了。但是这样真的合理吗?答案当然是否定的。

客户端发送完数据后,告诉服务器,我没有数据了,可以和你断开,但是不代表服务器没有数据需要发送到客户端了呀。TCP是一个全双工的连接,代表服务器也有可能有数据需要发送到客户端。所以,只有当两端的数据都发送完毕,连接才能安全的断开。因此,服务器接收到了客户端的FIN报文段,他会等到自己所有的数据发送完,然后也向客户端发送一个FIN报文,告诉客户端我也没数据了,这时候连接才能真正断开,两端各自释放资源。

三、总结

以上对TCP的三次握手和四次挥手做了一个比较全面的讲解,相信看完之后可以让人对TCP连接的建立和断开有更深入的理解。但是,这一部分内容实际上只是TCP中比较简单的一块,尽管如此,还是用了这么多篇幅去讲解,可想而知TCP的复杂性。我后面还将写几篇关于TCP其他方面的内容,希望在搜集资料和编写的过程中,能够加深我对它的理解。

linux根据文件名查找文件路径

以查找“dubbo-consumer.xml”为例

方法一:当前文件夹下操作

find ./ -name dubbo-consumer.xml

方法二:未知准确文件夹查找

find /home/ct/  -name dubbo-consumer.xml

使用grep可以查找包含指定字符串的文件

步骤详解

格式:

grep “要查找的字符串” 文件名

例子:

grep “192.168.0.1” /etc

文件名可以使用基本正则表达式(BRE),例如, 查找test目录下的所有文件,是否包含www.dutycode.com字符串。

grep “www.dutycode.com” /root/zzh/test/*

img

小贴士:使用-n 参数,可以显示字符串在文件中的行数

拓展内容

关于grep的命令的使用:

后端面试大纲_第15张图片

几个常用的查询指令:

1、查找时不区分字符串的大小写

grep -i “查找的字符串” 文件名

2、查找时使用正则表达式,匹配符合的字符串

grep -e “正则表达式” 文件名

3、查找不匹配指定字符串的行:

grep -v “被查找的字符串” 文件名

4、查找时显示被查找字符串所在的行数

grep -n “查找的字符串” 文件名

grep是通用正则表达式解析器(General Regular Expression Parser)的缩写。

一、grep命令的功能是分析一行信息,若其中有我们所需要的信息,就将其拿出来。需要注意的是它以整行为单位进行数据的选取。

语法:grep [-acinv] [–color=auto] ‘要查找的字符串’ filename

-a:将binary文件以text文件的方式查找数据

-c:计算找到查找字符串的次数

-i:忽略大小写的不同

-n:输出行号

-v:反向选择,显示出没有查找字符串的内容的行

–color-auto:将找到的字符串以特殊颜色显示

下面介绍几个使用grep命令的实例。

范例:

先将/etc目录下的manpath.config文件拷贝至/tmp文件夹下,来作实验。

cd /tmp

cp/etc/manpath.config .

将此文件中有包含MANPATH的那一行取出来。

Cat manpath.config| grep ‘MANPATH’

范例:

与上例子相反,只要没有包含MANPATH的那一行就取出来。

cat manpath.config| grep -v ‘MANPATH’

二、下面介绍grep的一些高级参数。

grep [-A] [-B] [–color=auto]‘查找字符串’ filename’

-A:后面可加数字,为after的意思,除了列出该行以外,后续的n行也列出来。

-B:后面可加数字,为before的意思,除了列出该行以外,前面的n行也列出。

范例:

用dmesg列出内核信息,然后用grep找出包含eth的那行,并且显示 行号。而且将关键字的前2行和后3行也列出来。

Dmesg | grep -n-A3 -B2 –color=auto ‘eth’

在关键字的显示上,grep可以用—color=auto来将关键字用特殊颜色显示。但是每次使用grep都得加上这个信息很麻烦,于是可以用alias进行一下处理就OK了。可以在~/.bashrc内加上这一行:alias grep=‘grep –color=auto’。

三、下面进行一些基础正则表达式的练习。

1、利用中括号[]来查找集合字符

比如我要查找man或者men字符串,可以这样来查找:

grep -n 'm[ae]n’manpath.config

Note:中括号[]里面不论有几个字符,它都只代表某一个字符。

2、反向选择^的使用

查找包含man而且前面没有/的那一行:

grep -n '[^/]man’manpath.config

查找包含man但是前面不是小写字符的那一行:

grep -n’[^a-z]man’ manpath.config

要取得有数字的那一行:

grep -n '[0-9]'manpath.config

考虑到语系对于编码顺序的影响,因此除了连续编码使用减号-之外,你也可以使用如下的方法取得前面测试的结果:

grep -n’[[:digit:]]’ manpath.config

关于语系编码:

不同语系的编码数据并不相同,所以会造成数据选取结果的区别。举例来说,在英文大小写的编码顺序中,zh_CN.big5及C这两种语系的输出结果分别是:

LANG=C时:012345…ABCD…Z…abcd…z

LANG=zh_CN时:012345…aAbBcCdD…zZ。

因此在使用正则表达式时,需要特别留意当时环境的语系为何,否则可能会发现与别人不同的选取结果。

另外,为了避免这样编码所造成的英文和数字的选取问题,有些特殊的符号需要我们了解以下。主要有下面这些:

[:alnum:]代表英文大小写字符及数字

[:blank:]代表空格和tab按键

[:punct:]代表标点符号

[:space:]任何会产生空白的字符

[:alpha:]代表任何英文大小写字符

[:digit:]代表数字

[:lower:]代表小写字符

[:upper:]代表大写字符

3、行首^和行尾$字符

列出行首为MANPATH_MAP的行:

grep -n’^MANPATH_MAP’ manpath.config

列出开头是大写字符的那一行:

grep -n '1'manpath.config

列出开头不是英文字母的行:

grep -n’[a-zA-Z]’ manpath.config

Note:那个^符号在字符集合(中括号[])之内和外面是不同的!!!在[]内面代表反向选择,在[]外面代表定位在行首的意思。

反过来思考:我们要找出行尾结束为感叹号.的行:

grep -n '.$'manpath.config

因为小数点具有特殊的意义,所以必须用转义字符加以解除其特殊意义。

查找出空白行:

grep -n '^$'manpath.config

4、任意一个字符.与重复字符*

在bash当中,通配符*可以用来代表任意(0或多个)字符,但是正则表达式并不是通配符,两者之间是不相同的。至于正则表达式当中:

.:代表绝对有一个字符的意思。

*:代表重复前一个字符0到无穷多次的意思,为组合形态。

实例:

查找包含一个o以上的行,需要oo*:

grep -n 'oo*'manpath.config

查找以g开头与以g结尾,中间至少存在一个o的行:

grep -n 'goo*g’manpath.config

5、限定连续RE字符范围

在前面的例题中,我们可以利用.与RE字符及*来设置0到无穷多个重复字符。那如果我要限制一个范围区间内的重复字符呢,比如要找出2-5个o的连续字符串,就要用到限定范围的字符{}了。

但是{}的符号在shell有特殊意义,因此要用到转义字符\。

实例:

找出g后面有两个到五个o后面再接一个g的字符串:

grep -n '{2,5}g’manpath.config

如果是2个以上呢:

grep -n '{2,}g’manpath.config

这样就OK了。

正则替换命令(search-and-replace)sed

sed是一个很好的文件处理工具,本身是一个管道命令,主要是以行为单位进行处理,可以将数据行进行替换、删除、新增、选取等特定工作,下面先了解一下sed的用法
sed命令行格式为:
sed [-nefri] ‘command’ 输入文本

常用选项:
-n∶使用安静(silent)模式。在一般 sed 的用法中,所有来自 STDIN的资料一般都会被列出到萤幕上。但如果加上 -n 参数后,则只有经过sed 特殊处理的那一行(或者动作)才会被列出来。
-e∶直接在指令列模式上进行 sed 的动作编辑;
-f∶直接将 sed 的动作写在一个档案内, -f filename 则可以执行 filename 内的sed 动作;
-r∶sed 的动作支援的是延伸型正规表示法的语法。(预设是基础正规表示法语法)
-i∶直接修改读取的档案内容,而不是由萤幕输出。

常用命令:
a ∶新增, a 的后面可以接字串,而这些字串会在新的一行出现(目前的下一行)~
c ∶取代, c 的后面可以接字串,这些字串可以取代 n1,n2 之间的行!
d ∶删除,因为是删除啊,所以 d 后面通常不接任何咚咚;
i ∶插入, i 的后面可以接字串,而这些字串会在新的一行出现(目前的上一行);
p ∶列印,亦即将某个选择的资料印出。通常 p 会与参数 sed -n 一起运作~
s ∶取代,可以直接进行取代的工作哩!通常这个 s 的动作可以搭配正规表示法!例如 1,20s/old/new/g 就是啦!

举例:(假设我们有一文件名为ab)
删除某行
[root@localhost ruby] # sed ‘1d’ ab #删除第一行
[root@localhost ruby] # sed ‘KaTeX parse error: Expected 'EOF', got '#' at position 20: …b #̲删除最后一行 [ro…d’ ab #删除第二行到最后一行

显示某行
. [root@localhost ruby] # sed -n ‘1p’ ab #显示第一行
[root@localhost ruby] # sed -n ‘KaTeX parse error: Expected 'EOF', got '#' at position 17: …' ab #̲显示最后一行 [ro…p’ ab #显示第二行到最后一行

使用模式进行查询
[root@localhost ruby] # sed -n ‘/ruby/p’ ab #查询包括关键字ruby所在所有行
[root@localhost ruby] # sed -n ‘/$/p’ ab #查询包括关键字$所在所有行,使用反斜线\屏蔽特殊含义

增加一行或多行字符串
[root@localhost ruby]# cat ab
Hello!
ruby is me,welcome to my blog.
end
[root@localhost ruby] # sed ‘1a drink tea’ ab #第一行后增加字符串"drink tea"
Hello!
drink tea
ruby is me,welcome to my blog.
end
[root@localhost ruby] # sed ‘1,3a drink tea’ ab #第一行到第三行后增加字符串"drink tea"
Hello!
drink tea
ruby is me,welcome to my blog.
drink tea
end
drink tea
[root@localhost ruby] # sed ‘1a drink tea\nor coffee’ ab #第一行后增加多行,使用换行符\n
Hello!
drink tea
or coffee
ruby is me,welcome to my blog.
end

代替一行或多行
[root@localhost ruby] # sed ‘1c Hi’ ab #第一行代替为Hi
Hi
ruby is me,welcome to my blog.
end
[root@localhost ruby] # sed ‘1,2c Hi’ ab #第一行到第二行代替为Hi
Hi
end

替换一行中的某部分
  格式:sed ‘s/要替换的字符串/新的字符串/g’ (要替换的字符串可以用正则表达式)
[root@localhost ruby] # sed -n ‘/ruby/p’ ab | sed ‘s/ruby/bird/g’ #替换ruby为bird
  [root@localhost ruby] # sed -n ‘/ruby/p’ ab | sed ‘s/ruby//g’ #删除ruby

 插入
 [root@localhost ruby] # sed -i '$a bye' ab         #在文件ab中最后一行直接输入"bye"
 [root@localhost ruby]# cat ab
 Hello!
 ruby is me,welcome to my blog.
 end

用sed删除一行字符开口的空格,如果确信开头只有空格,可用这条命令:

sed ‘s/^ *//’ infile

如果不确定是空格还是制表符,可用这条命令:

sed ‘s/2*//’ infile

用sed删除一行字符结尾的空格:

sed -e 's/[ ]*$//g

sed tr 将多个空格替换为一个空格
sed ‘s/[ ][ ]/ /g’
如果空格与tab共存时用
sed -e 's/[[:space:]][[:space:]]
/ /g’ filename

替换文档名中的空格
newfile=KaTeX parse error: Expected group after '_' at position 13: {oldfile// /_̲} 用 tr : find .…(echo $name | tr ’ ’ ‘’)
if [[ $name != n a ] ] ; t h e n m v " na ]]; then mv " na]];thenmv"name" $na
fi
done
修改 IFS
#!/bin/sh
IFS=@ read name address
echo “A mail to $name at $address”
read subject
echo “Subject: $subject”
or
#!/bin/sh
IFS=:
for p in $PATH
do
if [ -x $p/$1 ]
then
echo $p/$1
return
fi
done
echo “No $1 in your path” 1 > &2
return 1
or
( IFS=: ; for D in $PATH; do for F in $D/gif; do [ -x $F ] && echo F ; d o n e ; d o n e ) 今 天 做 了 一 个 S h e l l 程 序 , 结 果 传 递 过 来 的 文 件 名 有 些 有 空 格 , 导 致 不 能 执 行 , 找 不 到 原 文 件 , 急 于 解 决 先 把 文 件 名 的 空 格 都 用 下 划 线 都 给 替 换 掉 , 其 实 应 该 可 以 从 程 序 上 解 决 的 , 继 续 看 有 什 么 方 法 以 下 是 在 网 上 找 的 替 换 空 格 文 件 的 脚 本 用 t r : f i n d . − t y p e f − n a m e " ∗ ∗ " − p r i n t ∣ w h i l e r e a d n a m e ; d o n a = F; done; done ) 今天做了一个Shell程序,结果传递过来的文件名有些有空格,导致不能执行,找不到原文件,急于解决先把文件名的空格都用下划线都给替换掉,其实应该可以从程序上解决的,继续看有什么方法 以下是在网上找的替换空格文件的脚本 用 tr : find . -type f -name "* *" -print | while read name; do na= F;done;done)Shell线tr:find.typefname""printwhilereadname;dona=(echo $name | tr ’ ’ '
’)

if [[ $name != n a ] ] ; t h e n m v " na ]]; then mv " na]];thenmv"name" $na
fi
done
sed在行首,行尾追加不换行.

sed ‘s/^/append_helloworld/g’ yourfile
sed ‘s/$/insert_helloworld/g’ yourfile

以下是换行的:
追加命令
语法格式:
[line-address]a/text
如sed ‘10a/abcd’ sed.txt 在sed.txt文件中的第10行后面追加一行abcd字符。
sed ‘/unix/a/abcd//ndcba’ sed.txt 在sed.txt文件中所有出现unix字符的行后面追加一行abcd/ndcba字符
sed ‘/unix/a/abcd/n/dcba’ sed.txt 在sed.txt文件中所有出现unix字符的行后面追加两行字符,其中第一行为abcd第二行为dcba。
插入命令
语法格式:
[line-address]i/text
如 sed ‘/unix/i/adflajflad/n/adfadfajdlf’ sed-s.txt 在sed.txt文件中所有出现unix字符的行前面追加两行字符,其中第一行为abcd第二行为dcba。

blog.
end

用sed删除一行字符开口的空格,如果确信开头只有空格,可用这条命令:

sed ‘s/^ *//’ infile

如果不确定是空格还是制表符,可用这条命令:

sed ‘s/3*//’ infile

用sed删除一行字符结尾的空格:

sed -e 's/[ ]*$//g

sed tr 将多个空格替换为一个空格
sed ‘s/[ ][ ]/ /g’
如果空格与tab共存时用
sed -e 's/[[:space:]][[:space:]]
/ /g’ filename

替换文档名中的空格
newfile=KaTeX parse error: Expected group after '_' at position 13: {oldfile// /_̲} 用 tr : find .…(echo $name | tr ’ ’ ‘’)
if [[ $name != n a ] ] ; t h e n m v " na ]]; then mv " na]];thenmv"name" $na
fi
done
修改 IFS
#!/bin/sh
IFS=@ read name address
echo “A mail to $name at $address”
read subject
echo “Subject: $subject”
or
#!/bin/sh
IFS=:
for p in $PATH
do
if [ -x $p/$1 ]
then
echo $p/$1
return
fi
done
echo “No $1 in your path” 1 > &2
return 1
or
( IFS=: ; for D in $PATH; do for F in $D/gif; do [ -x $F ] && echo F ; d o n e ; d o n e ) 今 天 做 了 一 个 S h e l l 程 序 , 结 果 传 递 过 来 的 文 件 名 有 些 有 空 格 , 导 致 不 能 执 行 , 找 不 到 原 文 件 , 急 于 解 决 先 把 文 件 名 的 空 格 都 用 下 划 线 都 给 替 换 掉 , 其 实 应 该 可 以 从 程 序 上 解 决 的 , 继 续 看 有 什 么 方 法 以 下 是 在 网 上 找 的 替 换 空 格 文 件 的 脚 本 用 t r : f i n d . − t y p e f − n a m e " ∗ ∗ " − p r i n t ∣ w h i l e r e a d n a m e ; d o n a = F; done; done ) 今天做了一个Shell程序,结果传递过来的文件名有些有空格,导致不能执行,找不到原文件,急于解决先把文件名的空格都用下划线都给替换掉,其实应该可以从程序上解决的,继续看有什么方法 以下是在网上找的替换空格文件的脚本 用 tr : find . -type f -name "* *" -print | while read name; do na= F;done;done)Shell线tr:find.typefname""printwhilereadname;dona=(echo $name | tr ’ ’ '
’)

if [[ $name != n a ] ] ; t h e n m v " na ]]; then mv " na]];thenmv"name" $na
fi
done
sed在行首,行尾追加不换行.

sed ‘s/^/append_helloworld/g’ yourfile
sed ‘s/$/insert_helloworld/g’ yourfile

以下是换行的:
追加命令
语法格式:
[line-address]a/text
如sed ‘10a/abcd’ sed.txt 在sed.txt文件中的第10行后面追加一行abcd字符。
sed ‘/unix/a/abcd//ndcba’ sed.txt 在sed.txt文件中所有出现unix字符的行后面追加一行abcd/ndcba字符
sed ‘/unix/a/abcd/n/dcba’ sed.txt 在sed.txt文件中所有出现unix字符的行后面追加两行字符,其中第一行为abcd第二行为dcba。
插入命令
语法格式:
[line-address]i/text
如 sed ‘/unix/i/adflajflad/n/adfadfajdlf’ sed-s.txt 在sed.txt文件中所有出现unix字符的行前面追加两行字符,其中第一行为abcd第二行为dcba。


  1. A-Z ↩︎

  2. [:space:] ↩︎

  3. [:space:] ↩︎

你可能感兴趣的:(笔记,面试,算法,数据结构)