胡乱整理的问题集

到处都是问题,查漏补缺

1.String的APi

String类属于引用数据类型,且不能被继承。

String s = new String("TEST");
  1. 获取类的方法

length()获取类的长度

int i = s.length()

获取指定位置的字符

char ch = s.charAt(0)

返回”ch”在字符串中第一次出现的的位置。四种重载

int j = s.indexOf("T");//0

int lastIndexOf(参数)方法也有四种重载,找ch最后一次出现的位置。

  1. 判断类的方法

boolean contains(CharSequence s):当且仅当包含指定的char值序列(字符串)时,返回true。

Boolean b = s.contains("T");//true

boolean isEmpty():字符中是否为空,当且仅当length()为0是返回true。

Boolean b1 = s.isEmpty();

boolean startsWith(String str):字符串是否是以指定内容开头。

Boolean b2 = s.startsWith("S");//fasle

boolean endsWith(String str):字符串是否是以指定内容结尾。

Boolean b3 = s.startsWith("T");

boolean equals(Object obj):判断此字符串是否与一个对象相等。重写了Object类中的equals()方法。

重写为内容比较

boolean equalsIgnoreCase(String anotherString):判断此String是否与另一个String相同,并忽略大小写。

  1. 转换类方法
char [] sch  = {'a','b','c'};

String(char [] chars) 将字符数组转换成字符串。

String s2 = new String(sch);

String(char [] value,int offset,int count) 将字符数组中的一部分转换成字符串。第一个参数是char类型的数组,第二个参数为第一个字符的索引(注意:从0开始计数),第三个参数表示长度。

String s3 = new String(sch,1,2);//截取为bc
String s4 = new String(sch,2,1);//截取为c

static String copyValueOf(char [] data) 返回指定数组中表示该字符序列的 String。
实质是调用了无参的构造方法,底层都调用的this.value = Arrays.copyOf(value, value.length);

static String copyValueOf(char [] data,int offset , int count) 返回指定数组中表示该字符序列的 String。

同样是调用了有参的构造方法,底层调用Arrays.copyOfRange(value, offset, offset+count);

char [] toCharArray() 将字符串转换成字符数组。

char [] ch1 = s.toCharArray();

使用平台的默认字符集将此 String编码为 byte 序列,并将结果存储到一个新的 byte 数组中。

byte[] bt = s.getBytes();
  1. 替换类方法

String replace(char oldChar,char newChar); 返回一个新的字符串,它是通过用 newChar替换此字符串中出现的所有 oldChar得到的。如果要替换的字符不存在,则返回的还是原串。

String st = s.replace("T","t");//tESt

public String replaceAll(String regex,String replacement)使用给定的 replacement 字符串替换此字符串匹配给定的正则表达式的每个子字符串。

  1. 切割方法

String [] split(String regex); 根据给定正则表达式拆分此字符串,将拆分的字符串按次序放入一个String数组返回。

String spr = "A1B2C3D4";
String[] chsp = spr.split("\\d+");//数字拆分{'A','B','C','D'}
  1. 子串方法

String substring(int beginIndex); 返回一个新的字符串,它是此字符串的一个子字符串,从指定的位置开始。

String st2 = s.substring(1);//EST

String substring(int beginIndex,int endIndex); 获取字符串中的一部分。从指定的位置开始,到指定的位置前一位结束。

  1. 其他方法

String toUpperCase():使用默认语言环境的规则将此 String中的所有字符都转换为大写。
String toLowerCase(): 使用默认语言环境的规则将此 String中的所有字符都转换为小写。
String trim():返回字符串的副本,忽略前导空白和尾部空白,即将字符串两端的多个空格去除。
int compareTo(String )对两个字符串进行自然顺序的比较。此方法多用于集合框架中。

2. JAVA链表的实现

先看结构图,链表的实现类

image-20191202154956412

  1. ArrayList的实现,底层为数组队列,相当于动态数组。

    继承类以及实现的接口

    public class ArrayList<E> extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    {
    }

    ArrayList 实现了RandomAccess 接口, RandomAccess 是一个标志接口,表明实现这个这个接口的 List 集合是支持快速随机访问的。在 ArrayList 中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。

      ArrayList 实现了Cloneable 接口,即覆盖了函数 clone(),能被克隆

      ArrayList 实现java.io.Serializable 接口,这意味着ArrayList支持序列化能通过序列化去传输

      和 Vector 不同,ArrayList 中的操作不是线程安全的!所以,建议在单线程中才使用 ArrayList,而在多线程中可以选择 Vector(不用) 或者 CopyOnWriteArrayList

静态变量

   //初始化默认大小10
private static final int DEFAULT_CAPACITY = 10;

//空数组 用于空实例
private static final Object[] EMPTY_ELEMENTDATA = {};

//默认大小空实例数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

//保存ArrayList数据的数组
transient Object[] elementData;

//数组元素个数
private int size;

构造方法

//有参构造方法 用户自定义大小
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}

//默认构造方法 10大小的空数组
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

//构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序。
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
//c.toArray 可能返回的不是Object类型的数组所以加上下面的语句用于判断,
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 用空数组替换
this.elementData = EMPTY_ELEMENTDATA;
}
}
  1. LinkedList

继承类以及实现的接口

public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
transient int size = 0;

transient Node<E> first;

transient Node<E> last;

}

LinkedList是一个实现了List接口和Deque接口的双端链表

LinkedList底层的链表结构使它支持高效的插入和删除操作,另外它实现了Deque接口,使得LinkedList类也具有队列的特性。

LinkedList不是线程安全的,如果想使LinkedList变成线程安全的,可以调用静态类Collections类中的synchronizedList方法。

内部结构

private static class Node<E> {
E item;//节点值
Node<E> next;//前驱节点
Node<E> prev;//后继节点

Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}

构造方法

//空构造方法
public LinkedList() {
}

//用已有的集合创建链表的构造方法
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}

add方法

//将元素添加到链表尾
public boolean add(E e) {
linkLast(e);
return true;
}

void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}

add(int index,E e):在指定位置添加元素

public void add(int index, E element) {
    checkPositionIndex(index);//检查索引是否处于[0-size]之间

    if (index == size)
        linkLast(element);
    else
        linkBefore(element, node(index));
}

void linkLast(E e) {
    final Node<E> l = last;
    final Node<E> newNode = new Node<>(l, e, null);
    last = newNode;
    if (l == null)
        first = newNode;//添加在链表尾部
    else
        l.next = newNode;////添加在链表中间
    size++;
    modCount++;
}

void linkBefore(E e, Node<E> succ) {
    //指定坐标节点不为空
    final Node<E> pred = succ.prev;
    final Node<E> newNode = new Node<>(pred, e, succ);
    succ.prev = newNode;
    if (pred == null)
        first = newNode;
    else
        pred.next = newNode;
    size++;
    modCount++;
}

addAll(Collection c ):将集合插入到链表尾部

public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);//size为尾部节点坐标
}

addAll(int index, Collection c): 将集合从指定位置开始插入

public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index);

Object[] a = c.toArray();//把集合的数据存到对象数组中
int numNew = a.length;
if (numNew == 0)
return false;

Node<E> pred, succ;//得到插入位置的前驱节点和后继节点
if (index == size) {//如果插入位置为尾部,前驱节点为last,后继节点为null
succ = null;
pred = last;
} else {//否则,调用node()方法得到后继节点,再得到前驱节点
succ = node(index);
pred = succ.prev;
}

for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null)//如果插入位置在链表头部
first = newNode;
else
pred.next = newNode;
pred = newNode;
}

if (succ == null) {////如果插入位置在尾部,重置last节点
last = pred;
} else {//否则,将插入的链表与先前链表连接起来
pred.next = succ;
succ.prev = pred;
}

size += numNew;
modCount++;
return true;
}

addFirst(E e): 将元素添加到链表头部

public void addFirst(E e) {
linkFirst(e);
}

private void linkFirst(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f);//新建节点,以头节点为后继节点
first = newNode;
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}

addLast(E e): 将元素添加到链表尾部,与 add(E e) 方法一样

public void addLast(E e) {
linkLast(e);
}

get(int index): 根据指定索引返回数据

public E get(int index) {
checkElementIndex(index);////检查index范围是否在size之内
return node(index).item;
}

获取头节点(index=0)数据方法:

public E element() {
return getFirst();
}

public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}

public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}

public E peekFirst() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}

区别: getFirst(),element(),peek(),peekFirst() 这四个获取头结点方法的区别在于对链表为空时的处理,是抛出异常还是返回null,其中getFirst()element() 方法将会在链表为空时,抛出异常

获取尾节点(index=-1)数据方法:

public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}

public E peekLast() {
final Node<E> l = last;
return (l == null) ? null : l.item;
}

根据对象获取索引

int indexOf(Object o): 从头遍历找

public int indexOf(Object o) {
int index = 0;
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
return -1;
}

int lastIndexOf(Object o): 从尾遍历找

public int lastIndexOf(Object o) {
int index = size;
if (o == null) {
//从尾遍历
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (x.item == null)
return index;
}
} else {
//从尾遍历
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (o.equals(x.item))
return index;
}
}
return -1;
}

contains(Object o): 检查对象o是否存在于链表中

public boolean contains(Object o) {
return indexOf(o) != -1;
}

删除方法

remove() ,removeFirst(),pop(): 删除头节点

public E remove() {
return removeFirst();
}

public E pop() {
return removeFirst();
}

public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}

private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}

removeLast(),pollLast(): 删除尾节点

public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}

public E pollLast() {
final Node<E> l = last;
return (l == null) ? null : unlinkLast(l);


private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
final E element = l.item;
final Node<E> prev = l.prev;
l.item = null;
l.prev = null; // help GC
last = prev;
if (prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}

remove(int index):删除指定位置的元素

  public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}

E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;

if (prev == null) {//如果删除的节点是头节点,令头节点指向该节点的后继节点
first = next;
} else {//将前驱节点的后继节点指向后继节点
prev.next = next;
x.prev = null;
}

//删除后继指针
if (next == null) {//如果删除的节点是尾节点,令尾节点指向该节点的前驱节点
last = prev;
} else {
next.prev = prev;
x.next = null;
}

x.item = null;
size--;
modCount++;
return element;
}

3.JAVA8的新特性

编辑中

Java 8 Lambda实现原理分析

5.Spring的依赖注入的方式

Spring的常用三种注解方式.

1.构造器注入

2.setter注入

3.基于注解的注入

6.@requestbody和@responsebody的区别

@requestbody:将前台的key,value数据data:name=“1”&id=1

或者json数据data :{name:“1”,id:1 } 转换成java对象入形参。

@responsebody:将返回到前台的java对象转换成json串输出。

7.Content-type的常见类型

1、application/x-www-form-urlencoded

1)浏览器的原生form表单
2) 提交的数据按照 key1=val1&key2=val2 的方式进行编码,key和val都进行了URL转码

2.multipart/form-data

常见的 POST 数据提交的方式。我们使用表单上传文件时,必须让 form 的 enctype 等于这个值。

3.pplication/json

消息主体是序列化后的 JSON 字符串。

4.text/xml

是一种使用 HTTP 作为传输协议,XML 作为编码方式的远程调用规范

8.数据库的三大范式

第一范式:当关系模式R的所有属性都不能在分解为更基本的数据单位时,称R是满足第一范式的,简记为1NF

如果数据库表中的所有字段值都是不可分解的原子值,就说明该数据库满足第一范式。

第二范式:如果关系模式R满足第一范式,并且R得所有非主属性都完全依赖于R的每一个候选关键属性,称R满足第二范式,简记为2NF。

第二范式需要确保数据库表中每一列都和主键相关,而不能只与主键的某一部分相关(主要针对联合主键而言)。也就是说在一个数据库表中,一个表中只能保存一种数据,不可以把多种数据保存在同一张数据库表中。

第三范式:设R是一个满足第一范式条件的关系模式,X是R的任意属性集,如果X非传递依赖于R的任意一个候选关键字,称R满足第三范式,简记为3NF.

第三范式需要确保数据表中的每一列数据都和主键直接相关,而不能间接相关。

9.数据库中时间的类型

Mysql中经常用来存储日期的数据类型有三种:Date、Datetime、Timestamp

1.Date数据类型:用来存储没有时间的日期。Mysql获取和显示这个类型的格式为“YYYY-MM-DD”。支持的时间范围为“1000-00-00”到“9999-12-31”。

2.Datetime类型:存储既有日期又有时间的数据。存储和显示的格式为 “YYYY-MM-DD HH:MM:SS”。支持的时间范围是“1000-00-00 00:00:00”到“9999-12-31 23:59:59”。

3.Timestamp类型:也是存储既有日期又有时间的数据。存储和显示的格式跟Datetime一样。支持的时间范围是“1970-01-01 00:00:01”到“2038-01-19 03:14:07”。

10.字符串中出现次数最多的字符

思路:
1.通过 char[] toCharArray() 将字符串保存到字符数组中
2.遍历字符数组
3.将数组里的每一个不重复的字符作为键保存到map中,将每一个相同字符的数量作为值保存到map中
4.求出出现次数最多的键值对

    //寻找出现次数最多的字符
public void findMaxTimeChar(String str){
Map<Character,Integer> map = new HashMap<>();
char[] chars = str.toCharArray();

// for(int i=0;i<chars.length;i++){
//
// if(map.containsKey(chars[i])){
// map.put(chars[i],map.get(chars[i])+1);
// }else{
// map.put(chars[i],1);
// }
// }

for(char ch:chars){
if(map.containsKey(ch)){
map.put(ch,map.get(ch)+1);
}else{
map.put(ch,1);
}
}
System.out.println(map);

//求出现字符次数最多的键值对
Collection values = map.values();
int max = 0;
max = (Integer) Collections.max(values);

Set entrySet = map.entrySet();
for(Object obj:entrySet){
Map.Entry entry = (Map.Entry)obj;
if((Integer) entry.getValue()==max){
System.out.println("出现次数最多的字符为"+entry.getKey()+"次数为"+entry.getValue());
}
}
}

测试

两种情况,有多个相同次数的字符和单个字符

    public static void main(String[] args) {
MaxTimeChar maxTimeChar = new MaxTimeChar();
String str = "asdfsfsaassss";
maxTimeChar.findMaxTimeChar(str);

String str2 = "abcabcab";
maxTimeChar.findMaxTimeChar(str2);
}

---------------------------------------------------
{a=3, s=7, d=1, f=2}
出现次数最多的字符为s次数为7
{a=3, b=3, c=2}
出现次数最多的字符为a次数为3
出现次数最多的字符为b次数为3

keySet是键的集合,Set里面的类型即key的类型
entrySet是 键-值 对的集合,Set里面的类型是Map.Entry

java8中Map的使用entry的迭代方式

Map.entrySet().forEach(entry -> System.out.println(entry.getKey() + " " + entry.getValue()));