头像

RyanL

大连理工大学


访客:5394

离线:20小时前


分享 Java集合

RyanL
6天前

Set

set集合作用:去重,不能存放重复的元素

TreeSet

底层用红黑树实现
支持有序性操作,可以把存入其中的元素自然排序(从小到大或从大到小),要求元素必须具备可比较性
如果元素本身可比较,则需要实现Comparable接口,重写compareTo()方法
如果元素本身无法比较,则需要实现Comparator接口,重写compare()方法,重新定义比较规则
查找效率不如HashSet,时间复杂度为O(logn)

HashSet

底层用哈希表实现、
不支持有序性操作,但是查找效率极高,时间复杂度可以控制在O(1),但插入顺序不能确定

LinkedHashSet

底层用哈希表和双向链表实现
具有HashSet的查找效率,且用双向链表维护了元素的插入顺序

Map

Map是映射,存储的是<K,V>键值对

TreeMap

TreeMap的Key集合就是TreeSet,基于红黑树实现

HashMap

HashMap的Key集合就是HashSet,基于哈希表实现

HashTable

和HashMap类似,但它是线程安全的。HashTable可以使得多个线程同时操作哈希表,并且不会导致数据不一致
但这是一个遗留类,我们不应该去使用它。
现在可以使用ConcurrentHashMap来支持线程安全,因为引入了分段锁,所以效率会更高

LinkedHashMap

在HashMap的内部使用了双向链表,可以维护元素的插入顺序

简述集合主要有哪些类和接口,各自的特点

集合的接口有Collection和Map,其中Collection包括List,Set,Queue

List是有序的,主要包括:ArrayList,LinkedList,Vector
ArrayList底层用数组实现,线程不安全,Vector是线程安全的ArrayList,但效率较低
LinkedList底层用双向链表实现,与ArrayList相比增删快查询慢

Set是无重复元素集合,主要包括:TreeSet,HashSet,LinkedHashSet
HashSet底层就是HashMap,利用key来保证元素的唯一性,LinkedHashSet可以按key的操作顺序排序
TreeSet可按默认规则排序或自定义规则排序

Queue是队列结构,主要有:PriorityQueue优先级队列,ArrayBlockingQueue,LinkedBlockingQueue等

Map以key-value键值对的形式存储元素,主要包括HashMap,LinkedHashMap,TreeMap
HashMap底层用数组+链表/红黑树实现,LinkedHashMap可以按照对key的操作顺序对集合排序
TreeMap可以按照默认排序规则或指定比较规则对集合排序

List,Set,Map有什么区别

List是有序,可重复,有索引的集合,继承了Collection接口的所有功能,可以用索引遍历
Set是无序,不可重复的集合,Set的实现类LinkedHashSet,TreeSet是有序的,LinkedHashSet可以按照元素插入顺序排序
也可以按元素操作的时间顺序排序,TreeSet可以按默认规则或自定义规则排序
Map是无序,以key-value的键值对形式存储数据的集合,键不可重复,值没有要求,重复的键对应的值会覆盖之前的值

HashSet去重原理

1.对基本类型的包装类,可以直接按值进行比较
2.对于引用类型,会先比较hashCode()是否相同,不同表示不是同一个对象,如果相同则用equals()判断是否同一个对象
3.如果只希望内容相同的对象就表示对象相同,除了重写equals()方法,也要重写hashCode()方法
因为内容相同的对象会因为内存地址的不同而获取的哈希码值不同

HashMap和HashSet是怎么实现的

JDK1.8之前,HashMap的底层是用数组加链表实现的,数组中的每个元素都是一条单链表,链表中的每个元素都是Entry实
现类Node的一个实例,包括key,value,hash,next四个属性。
HashMap在查找数据时,可以通过hash值快速定位到数组下标,然后对链表进行遍历查找,复杂度O(n)
JDK1.8进行了优化,当链表元素超过8个时,将链表转换为红黑树来提高查询效率,时间复杂度为O(logn)

HashSet底层是基于HashMap实现的,HashSet的元素只是存放在底层的HashMap的key上,而value用一个static final的
Object对象,因此HashSet的实现都是底层直接调用HashMap的相关方法实现的

Collection和Collections区别

Collection是一个集合接口,定义了相关集合应该实现的方法,包括List,Set,Queue等
Collections是一个集合工具类,为Collection类型的集合提供很多便捷的方法,例如addAll()可以批量添加元素,
sort()可以对List集合进行排序,shuffle()可以随机打乱List集合中的元素

什么是迭代器

迭代器实现了Iterator接口,是用于遍历Collection集合的第一个指针
主要有三个方法:
1.iterator():获取集合的迭代器
2.hasNext():判断集合中是否还有元素,有返回true,反之返回false。初始时迭代器位于第一个元素之前
3.next():用于获取集合的下一个元素,并将迭代器向后移动一个元素的单位

使用foreach循环遍历集合元素时能否添加或者删除元素

不能。
foreach实际上就是用Iterator迭代器实现的,如果进行添加或删除会抛出ConcurrentModificationException异常。
因为添加或删除元素会改变modCount的值,这个值是成员变量,代表集合的修改次数,当这个值与预期的修改次数不一致
就会抛出异常

Queue接口中的add()/offer()、remove()/poll()、element()/peek()方法有什么区别

add()和offer()都是向队列尾部插入一个元素,区别是超出队列界限时,add()抛出异常,offer()返回false
remove()和poll()都是移除队头元素并返回,队列为空时remove()抛出异常,poll()则是返回null值
element()和peek()都是获取队头元素,队列为空时element()抛出异常,peek()则是返回null值

有哪些线程安全的集合类

1.Vector:是线程安全的ArrayList,底层用数组实现,用synchronized修饰方法保证线程安全
2.HashTable:线程安全的HashMap,继承自Dictionary,通过synchronized修饰方法保证线程安全,性能差
3.ConcurrentHashMap:线程安全的HashMap,通过分段锁实现线程安全,性能较好


分享 Java异常

RyanL
7天前

Java异常体系

异常都是Throwable的子类,分为Exception和Error
Error是JVM无法处理的错误
Exception分为检查性异常和非检查型异常
检查型异常:一般难以避免,编译器会进行检查,如果开发者没有处理,则编译器将会报错。这种异常可以恢复。
非检查型异常:一般可以人为避免,编译器也不会进行检查。程序会崩溃且无法恢复,例如除0的Arithmetic Exception

throw和throws的区别

throws是用来声明一个方法可能抛出的所有异常信息,throws是将异常声明但是不处理,而是将异常往上抛出。
如果一直抛出到JVM里都没有被处理,则程序崩溃停止
throw则是指抛出的一个具体的异常类型。

try-catch-finally中哪个部分可以省略?

catch可以省略

return和finally的执行顺序

finally会在return之前执行

看一段代码:
//返回2
public static int test(){
    int i = 2;
    try {
        return i;
    }finally {
        i++;
    }
}

//返回3
public static int test(){
    int i = 2;
    try {
        return i;
    }finally {
        i++;
        return i;
    }
}

分析:
1.先将i存储在操作数栈
2.由于还有finally语句,把i保存到局部变量区临时存储。
3.执行finally语句,给finally语句中的临时变量提供操作数栈来存储数据
4.把局部变量区的数据复制回操作数栈的栈顶
5.返回栈顶的数
注意:如果finally中也有return语句,则直接用finally语句中更新的栈顶数据返回

常见异常种类

NullPointerException:当应用程序试图访问空对象时,则抛出该异常。

SQLException:提供关于数据库访问错误或其他错误信息的异常。

IndexOutOfBoundsException:指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。

NumberFormatException:当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常

FileNotFoundException:当试图打开指定路径名表示的文件失败时,抛出此异常。

IOException:当发生某种I/O异常时,抛出此异常。此类是失败或中断的I/O操作生成的异常的通用类。

ClassCastException:当试图将对象强制转换为不是实例的子类时,抛出该异常。

ArrayStoreException:试图将错误类型的对象存储到一个对象数组时抛出的异常。

IllegalArgumentException:抛出的异常表明向方法传递了一个不合法或不正确的参数。

ArithmeticException:当出现异常的运算条件时,抛出此异常。例如,一个整数“除以零”时,抛出此类的一个实例。

NegativeArraySizeException:如果应用程序试图创建大小为负的数组,则抛出该异常。

NoSuchMethodException:无法找到某一特定方法时,抛出该异常。

SecurityException:由安全管理器抛出的异常,指示存在安全侵犯。

UnsupportedOperationException:当不支持请求的操作时,抛出该异常。

RuntimeExceptionRuntimeException:是那些可能在Java虚拟机正常运行期间抛出的异常的超类。



RyanL
10天前

Java有什么特点?

1.面向对象(封装,继承,多态)
2.平台无关性(JVM实现的平台无关性)
3.GC垃圾回收机制
4.异常处理机制
5.支持多线程
6.支持网络编程

面向对象和面向过程的区别

* 面向过程:把问题分解为一个个的步骤,再用代码把这些步骤依次实现,性能较高。
比如单片机,嵌入式开发,Linux/Unix等一般采用面向过程开发。

* 面向对象:容易维护,复用,扩展。类调用需要实例化,开销大。性能低于面向过程。
因为有封装,继承,多态的特性,可以设计出低耦合的系统,使系统更加灵活,更加容易维护。

Java和C++的区别

* 都是面向对象的语言,支持封装,继承,多态
* Java用引用代替指针,不能直接访问内存,更安全
* Java的类是单继承的,而C++支持多重继承。Java的接口可以多继承
* Java有自动内存管理机制,不需要程序员手动释放内存

Java基本数据类型

* Java有8种基本数据类型
byte:1字节
short:2字节
int:4字节
long:8字节
char:2字节
float:4字节
double:8字节
boolean:1字节

基本类型和引用类型的区别

* 除了基本数据类型,其他全都是引用数据类型,包括数组,集合,基本数据类型的包装类等等
* 区别:
    1.赋值方法不同:基本类型直接赋值,应用类型通过new创建对象,再为对象的属性进行赋值
    2.比较方法不同:对于==来说,引用类型比较引用地址,基本类型比较值
    3.参数传递不同:基本类型传递是值传递,引用数据类型是引用传递(地址传递)
    4.存储位置不同:基本类型直接存储在JVM的栈上,引用数据类型被创建时,先在栈上分配一块地址存储引用,
                    对象的具体信息都存储在堆内存上,由栈中的引用指向堆中对象的地址

引用类型如何创建?

例如:Person p = new Person(20);
1.首先在栈内存中为p分配一块空间
2.然后在堆内存中为Person对象分配一块空间,并为其赋初值0
3.根据Person对象的定义,调用构造方法,为其属性赋值20
4.将Person对象在堆内存的地址,赋值给栈内存中的p,可以通过p来找到堆中对象的具体信息

重载和重写的区别

* 重载:发生在同一个类中,方法名必须相同,参数类型不同,个数不同,顺序不同,返回值和访问修饰符可以不同
* 重写:发生在子类中,是子类对于父类允许访问范围内的方法的实现过程的重新编写。方法名,参数列表必须相同
        返回值,异常小等于父类,访问修饰符的范围大于等于父类。
        总而言之:方法提供的行为改变,而方法的外表并没有改变

面向对象三大特性

* 封装
把描述一个对象的属性和行为的代码封装在一个类中,属性用变量定义,行为用方法定义
方法可以直接访问同一个对象中的属性

* 继承
子类继承父类的特征和行为,子类可以拥有父类非私有的方法和属性
同时,子类可以对父类进行扩展,也可以重写父类的方法,但会提高代码之间的耦合性

* 多态
编译时多态:主要指方法的重载
运行时多态:指程序中定义的对象引用所指向的类型在运行期间才能确定
运行时多态有三个条件:继承,重写,向上转型

向上转型,向下转型

向上转型:用子类对象来实例化父类对象,可以实现多态
向下转型:用父类对象来实例化子类对象,可以调用子类扩展的方法

final关键字的作用

* final修饰的类叫最终类,不能被继承
* final修饰的方法不能被重写
* final修饰的变量不能被修改
    1.基本变量被修饰则数值不能修改
    2.引用变量被修饰则不能指向别的对象,但被引用对象的数值可以修改

final、finally、finalize区别

* final可以修饰类,变量,方法
修饰类表示该类不能被继承,修饰方法表示该方法不能被重写,修饰变量则表示值不能修改

* finally:一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块中,
表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码

* finalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,该方法一般由垃圾回收器来调用,
当我们调用System的gc()方法的时候,由垃圾回收器调用finalize(),回收垃圾。

hashCode()和equals()

* hashCode()和equals()的作用其实一样,都是用来比较两个对象是否相等一致
* hashCode()注重性能,equals()注重可靠性

1.hashCode()和equals()区别

* 既然equals()可靠性强为什么还要hashCode()?
重写的equals()中比较手段比较全面而复杂,效率较低。
用hashCode()来比较,只需要生成一个hash值就可以了,效率较高

* 既然hashCode()效率高为什么还要用equals()?
因为hashCode()并不是完全可靠,有时候由于公式的问题,不同对象生成的hashCode也会一样,无法保证绝对可靠
equals()相等的两个对象,他们的hashCode肯定相等,也就是说equals()绝对可靠
hashCode()相等的两个对象,他们的equals()不一定相等,也就是说hashCode()不是绝对可靠的

2.注意事项

1.每次对比,先比较hashCode,如果不相等,则两个对象肯定不同,不需要再用equals了
如果hashCode相等,再比较equals(),保证了效率和可靠性

2.hashCode()只有在hash容器中才需要重写,如HashSet,HashMap,HashTable等等。
由于哈希集合中要求能存放不同类型的对象,所以必须避免hashCode相同,但对象不同的情况,必须两个方法都重写

3.如果不重写的话,hashCode()和equals()都是属于Object类的方法,hashCode()由内存地址生成哈希值,
而equals()对于引用类型变量来说是比较引用地址,所以在很多情况下需要重写这两个方法。

4.String类型重写了hashCode()和equals(),可以把String对象作为哈希集合(映射)的key使用

接口和抽象类的区别

1.接口方法默认是public,所有方法在接口中不能实现(Java8开始可以有默认实现),抽象类可以有非抽象的方法
2.接口中除了static,final变量,不能有其他变量,抽象类则不一定
3.类可以实现多个接口,但只能实现一个抽象类。接口可以多继承而类不行
4.抽象是对类的抽象,是一种模板设计。而接口是对行为的抽象,是一种行为的规范

Object类有什么常用方法

equals():比较两个对象的引用地址值是否相等,可被子类重写修改比较规则
hashCode():获取哈希码
toString():把数据转换成字符串
getClass():获取类的结构信息
finalize():垃圾回收前执行的方法
clone():克隆对象
wait():多线程等待
notify():唤醒随机一个线程
notifyAll():唤醒所有线程

缓存池

基本类型的valueOf()方法会调用缓存池获取数据
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

判断值是否在缓存池中,如果在直接返回缓存池的内容,不在就新建一个。
编译器在自动装箱的过程中调用valueOf()方法

Integer m = 123;
Integer n = 123;
System.out.println(m == n); // true

字符串常量池

字符串常量池用来保存所有字符串字面量,这些字面量在编译时期就确定了
String的intern()方法会在运行过程中将字符串添加到字符串常量池中,调用该方法时,
如果字符串常量池中已经有这个字符串字面量,则直接返回这个字符串的引用
如果字符串常量池中不存在这个字符串字面量,则创建一个新字符串,并返回这个字符串的引用

问:String s = new String("abc")会创建几个对象
一共创建两个对象(前提是常量池中还没有"abc"字符串对象)
1."abc"属于字符串字面量,因此编译时期会在常量池中创建一个字符串对象,指向"abc"
2.使用new会在堆中创建一个字符串对象



RyanL
10天前

TCP和UDP的区别

UDP(用户数据报协议):
面向无连接的,尽最大可能交付,没有拥塞控制
面向报文(对于应用层传下来的报文不合并,不拆分,直接添加到UDP首部)
支持一对一、一对多、多对一、多对多的交互通信
==============================
TCP(传输控制协议):
面向连接,提供可靠交付,有流量控制,拥塞控制,提供全双工通信
面向字节流(把应用层传下来的报文看成字节流,把字节流组织成大小不等的数据块)
只支持一对一通信

TCP三次握手


TCP四次挥手





RyanL
22天前

Redis

1.概念

redis是一款高性能的NOSQL系列的非关系型数据库

1.1.什么是NOSQL
    NoSQL(NoSQL = Not Only SQL),意即“不仅仅是SQL”,是一项全新的数据库理念,泛指非关系型的数据库。
    随着互联网web2.0网站的兴起,传统的关系数据库在应付web2.0网站,
    特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,暴露了很多难以克服的问题,
    而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。
    NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题。

    1.1.1.  NOSQL和关系型数据库比较
        优点:
            1)成本:nosql数据库简单易部署,基本都是开源软件,不需要像使用oracle那样花费大量成本购买使用,相比关系型数据库价格便宜。
            2)查询速度:nosql数据库将数据存储于缓存之中,关系型数据库将数据存储在硬盘中,自然查询速度远不及nosql数据库。
            3)存储数据的格式:nosql的存储格式是key,value形式、文档形式、图片形式等等,所以可以存储基础类型以及对象或者是集合等各种格式,而数据库则只支持基础类型。
            4)扩展性:关系型数据库有类似join这样的多表查询机制的限制导致扩展很艰难。

        缺点:
            1)维护的工具和资料有限,因为nosql是属于新的技术,不能和关系型数据库10几年的技术同日而语。
            2)不提供对sql的支持,如果不支持sql这样的工业标准,将产生一定用户的学习和使用成本。
            3)不提供关系型数据库对事务的处理。

    1.1.2.  非关系型数据库的优势:
        1)性能NOSQL是基于键值对的,可以想象成表中的主键和值的对应关系,而且不需要经过SQL层的解析,所以性能非常高。
        2)可扩展性同样也是因为基于键值对,数据之间没有耦合性,所以非常容易水平扩展。

    1.1.3.  关系型数据库的优势:
        1)复杂查询可以用SQL语句方便的在一个表以及多个表之间做非常复杂的数据查询。
        2)事务支持使得对于安全性能很高的数据访问要求得以实现。对于这两类数据库,对方的优势就是自己的弱势,反之亦然。

    1.1.4.  总结
        关系型数据库与NoSQL数据库并非对立而是互补的关系,即通常情况下使用关系型数据库,在适合使用NoSQL的时候使用NoSQL数据库,
        让NoSQL数据库对关系型数据库的不足进行弥补。
        一般会将数据存储在关系型数据库中,在nosql数据库中备份存储关系型数据库的数据

1.2.主流的NOSQL产品
    •   键值(Key-Value)存储数据库
            相关产品: Tokyo Cabinet/Tyrant、Redis、Voldemort、Berkeley DB
            典型应用: 内容缓存,主要用于处理大量数据的高访问负载。 
            数据模型: 一系列键值对
            优势: 快速查询
            劣势: 存储的数据缺少结构化
    •   列存储数据库
            相关产品:Cassandra, HBase, Riak
            典型应用:分布式的文件系统
            数据模型:以列簇式存储,将同一列数据存在一起
            优势:查找速度快,可扩展性强,更容易进行分布式扩展
            劣势:功能相对局限
    •   文档型数据库
            相关产品:CouchDB、MongoDB
            典型应用:Web应用(与Key-Value类似,Value是结构化的)
            数据模型: 一系列键值对
            优势:数据结构要求不严格
            劣势: 查询性能不高,而且缺乏统一的查询语法
    •   图形(Graph)数据库
            相关数据库:Neo4J、InfoGrid、Infinite Graph
            典型应用:社交网络
            数据模型:图结构
            优势:利用图结构相关算法。
            劣势:需要对整个图做计算才能得出结果,不容易做分布式的集群方案。
1.3 什么是Redis
    Redis是用C语言开发的一个开源的高性能键值对(key-value)数据库,官方提供测试数据,50个并发执行100000个请求,读的速度是110000次/s,写的速度是81000次/s ,且Redis通过提供多种键值数据类型来适应不同场景下的存储需求,目前为止Redis支持的键值数据类型如下:
        1) 字符串类型 string
        2) 哈希类型 hash
        3) 列表类型 list
        4) 集合类型 set
        5) 有序集合类型 sortedset
    1.3.1 redis的应用场景
        •   缓存(数据查询、短连接、新闻内容、商品内容等等)
        •   聊天室的在线好友列表
        •   任务队列。(秒杀、抢购、12306等等)
        •   应用排行榜
        •   网站访问统计
        •   数据过期处理(可以精确到毫秒)
        •   分布式集群架构中的session分离

2.下载安装

1. 官网:https://redis.io
2. 中文网:http://www.redis.net.cn/
3. 解压直接可以使用:
    * redis.windows.conf:配置文件
    * redis-cli.exe:redis的客户端
    * redis-server.exe:redis服务器端

3.命令操作

1. redis的数据结构:
    * redis存储的是:key,value格式的数据,其中key都是字符串,value有5种不同的数据结构
        * value的数据结构:
            1) 字符串类型 string
            2) 哈希类型 hash:map格式  
            3) 列表类型 list:linkedlist格式。支持重复元素
            4) 集合类型 set:不允许重复元素
            5) 有序集合类型 sortedset:不允许重复元素,且元素有顺序

2. 字符串类型 string
    1. 存储: set key value
        127.0.0.1:6379> set username zhangsan
        OK
    2. 获取: get key
        127.0.0.1:6379> get username
        "zhangsan"
    3. 删除: del key
        127.0.0.1:6379> del age
        (integer) 1
3. 哈希类型 hash
    1. 存储: hset key field value
        127.0.0.1:6379> hset myhash username lisi
        (integer) 1
        127.0.0.1:6379> hset myhash password 123
        (integer) 1
    2. 获取: 
        * hget key field: 获取指定的field对应的值
            127.0.0.1:6379> hget myhash username
            "lisi"
        * hgetall key:获取所有的field和value
            127.0.0.1:6379> hgetall myhash
            1) "username"
            2) "lisi"
            3) "password"
            4) "123"

    3. 删除: hdel key field
        127.0.0.1:6379> hdel myhash username
        (integer) 1

4. 列表类型 list:可以添加一个元素到列表的头部(左边)或者尾部(右边)
    1. 添加:
        1. lpush key value: 将元素加入列表左表

        2. rpush key value:将元素加入列表右边

            127.0.0.1:6379> lpush myList a
            (integer) 1
            127.0.0.1:6379> lpush myList b
            (integer) 2
            127.0.0.1:6379> rpush myList c
            (integer) 3
    2. 获取:
        * lrange key start end :范围获取
            127.0.0.1:6379> lrange myList 0 -1
            1) "b"
            2) "a"
            3) "c"
    3. 删除:
        * lpop key:删除列表最左边的元素,并将元素返回
        * rpop key:删除列表最右边的元素,并将元素返回


5. 集合类型 set : 不允许重复元素
    1. 存储:sadd key value
        127.0.0.1:6379> sadd myset a
        (integer) 1
        127.0.0.1:6379> sadd myset a
        (integer) 0
    2. 获取:smembers key:获取set集合中所有元素
        127.0.0.1:6379> smembers myset
        1) "a"
    3. 删除:srem key value:删除set集合中的某个元素  
        127.0.0.1:6379> srem myset a
        (integer) 1
6. 有序集合类型 sortedset:不允许重复元素,且元素有顺序.每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。

    1. 存储:zadd key score value
        127.0.0.1:6379> zadd mysort 60 zhangsan
        (integer) 1
        127.0.0.1:6379> zadd mysort 50 lisi
        (integer) 1
        127.0.0.1:6379> zadd mysort 80 wangwu
        (integer) 1
    2. 获取:zrange key start end [withscores]
        127.0.0.1:6379> zrange mysort 0 -1
        1) "lisi"
        2) "zhangsan"
        3) "wangwu"

        127.0.0.1:6379> zrange mysort 0 -1 withscores
        1) "zhangsan"
        2) "60"
        3) "wangwu"
        4) "80"
        5) "lisi"
        6) "500"
    3. 删除:zrem key value
        127.0.0.1:6379> zrem mysort lisi
        (integer) 1

7. 通用命令
    1. keys * : 查询所有的键
    2. type key : 获取键对应的value的类型
    3. del key:删除指定的key value

4.持久化

1. redis是一个内存数据库,当redis服务器重启,获取电脑重启,数据会丢失,我们可以将redis内存中的数据持久化保存到硬盘的文件中。
2. redis持久化机制:
    1. RDB:默认方式,不需要进行配置,默认就使用这种机制
        * 在一定的间隔时间中,检测key的变化情况,然后持久化数据
        1. 编辑redis.windwos.conf文件
            # after 900 sec (15 min) if at least 1 key changed
            save 900 1
            # after 300 sec (5 min) if at least 10 keys changed
            save 300 10
            # after 60 sec if at least 10000 keys changed
            save 60 10000

        2. 重新启动redis服务器,并指定配置文件名称
            D:\redis\windows-64\redis-2.8.9>redis-server.exe redis.windows.conf 

    2. AOF:日志记录的方式,可以记录每一条命令的操作。可以每一次命令操作后,持久化数据
        1. 编辑redis.windwos.conf文件
            appendonly no(关闭aof) --> appendonly yes (开启aof)

            # appendfsync always : 每一次操作都进行持久化
            appendfsync everysec : 每隔一秒进行一次持久化
            # appendfsync no     : 不进行持久化


分享 Ajax & JSON

RyanL
24天前

Ajax

1.概念

ASynchronous JavaScript And XML 异步的JavaScript 和 XML
Ajax 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。
通过在后台与服务器进行少量数据交换,Ajax可以使网页实现异步更新。
以在不重新加载整个网页的情况下,对网页的某部分进行更新。这意味着可以不用加载整个网页。
传统的网页(不使用 Ajax)如果需要更新内容,必须重载整个网页页面。

同步和异步:客户端和服务器端相互通信的基础上
    同步:客户端必须等待服务器端的响应。在等待的期间客户端不能做其他操作。
    异步:客户端不需要等待服务器端的响应。在服务器处理请求的过程中,客户端可以进行其他的操作。

2.实现方式

1.原生的JS实现方式(了解即可)
//定义方法
function  fun() {
    //发送异步请求
    //1.创建核心对象
    var xmlhttp;
    if (window.XMLHttpRequest)
    {// code for IE7+, Firefox, Chrome, Opera, Safari
        xmlhttp=new XMLHttpRequest();
    }
    else
    {// code for IE6, IE5
        xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
    }

    //2. 建立连接
    /*
        参数:
            1. 请求方式:GET、POST
                * get方式,请求参数在URL后边拼接。send方法为空参
                * post方式,请求参数在send方法中定义
            2. 请求的URL:
            3. 同步或异步请求:true(异步)或 false(同步)

     */
    xmlhttp.open("GET","ajaxServlet?username=tom",true);

    //3.发送请求
    xmlhttp.send();

    //4.接受并处理来自服务器的响应结果
    //获取方式 :xmlhttp.responseText
    //什么时候获取?当服务器响应成功后再获取

    //当xmlhttp对象的就绪状态改变时,触发事件onreadystatechange。
    xmlhttp.onreadystatechange=function()
    {
        //判断readyState就绪状态是否为4,判断status响应状态码是否为200
        if (xmlhttp.readyState==4 && xmlhttp.status==200)
        {
           //获取服务器的响应结果
            var responseText = xmlhttp.responseText;
            alert(responseText);
        }
    }
}
2.JQuery实现方式
1. $.ajax()
$.ajax({
    url:"ajaxServlet" , // 请求路径
    type:"POST" , //请求方式
    //data: "username=jack&age=23",//请求参数
    data:{"username":"jack","age":23},
    success:function (data) {
        alert(data);
    },//响应成功后的回调函数
    error:function () {
        alert("出错啦...")
    },//表示如果请求响应出现错误,会执行的回调函数

    dataType:"text"//设置接受到的响应数据的格式
});
2. $.get():发送get请求
语法:$.get(url, [data], [callback], [type])
参数:
    * url:请求路径
    * data:请求参数
    * callback:回调函数
    * type:响应结果的类型
3. $.post():发送post请求
语法:$.post(url, [data], [callback], [type])
参数:
    * url:请求路径
    * data:请求参数
    * callback:回调函数
    * type:响应结果的类型

JSON

1.概念

JavaScript Object Notation      JavaScript对象表示法
* json现在多用于存储和交换文本信息的语法
* 进行数据的传输
* JSON 比 XML 更小、更快,更易解析。

2.语法

1.基本规则
* 数据在名称/值对中:json数据是由键值对构成的
    * 键用引号(单双都行)引起来,也可以不使用引号
    * 值得取值类型:
        1. 数字(整数或浮点数)
        2. 字符串(在双引号中)
        3. 逻辑值(true 或 false)
        4. 数组(在方括号中)    {"persons":[{},{}]}
        5. 对象(在花括号中) {"address":{"province":"陕西"....}}
        6. null
* 数据由逗号分隔:多个键值对由逗号分隔
* 花括号保存对象:使用{}定义json 格式
* 方括号保存数组:[]
2.获取数据
1. json对象.键名
2. json对象["键名"]
3. 数组对象[索引]
4. 遍历
    //获取person对象中所有的键和值
    //for in 循环
    for(var key in person){
        //这样的方式获取不行。因为相当于  person."name"
        //alert(key + ":" + person.key);
        alert(key+":"+person[key]);
    }

   //获取ps中的所有值
    for (var i = 0; i < ps.length; i++) {
        var p = ps[i];
        for(var key in p){
            alert(key+":"+p[key]);
        }
    }
3.JSON对象和Java对象的相互转换
  • JSON解析器:常见的解析器:Jsonlib,Gson,fastjson,jackson
1.JSON对象转Java对象
1. 导入jackson的相关jar包
2. 创建Jackson核心对象 ObjectMapper
3. 调用ObjectMapper的相关方法进行转换:readValue(json字符串数据,Class)
    =====================示例=====================
    @Test
    public void test() throws Exception {
       //1.初始化JSON字符串
        String json = "{\"gender\":\"男\",\"name\":\"张三\",\"age\":23}";

        //2.创建ObjectMapper对象
        ObjectMapper mapper = new ObjectMapper();
        //3.转换为Java对象 Person对象
        Person person = mapper.readValue(json, Person.class);
        System.out.println(person);
    }
2.Java对象转JSON对象
1. 使用步骤:
    1. 导入jackson的相关jar包
    2. 创建Jackson核心对象 ObjectMapper
    3. 调用ObjectMapper的相关方法进行转换
2. 转换方法:
    * writeValue(参数1,obj):
        参数1:
            File:将obj对象转换为JSON字符串,并保存到指定的文件中
            Writer:将obj对象转换为JSON字符串,并将json数据填充到字符输出流中
            OutputStream:将obj对象转换为JSON字符串,并将json数据填充到字节输出流中
    * writeValueAsString(obj):将对象转为json字符串

3. 注解:
    1. @JsonIgnore:排除属性。
    2. @JsonFormat:属性值得格式化
        * @JsonFormat(pattern = "yyyy-MM-dd")

    @Test
    public void test() throws Exception {
        //1.创建Person对象
        Person p = new Person();
        p.setName("张三");
        p.setAge(23);
        p.setGender("男");
        p.setBirthday(new Date());

        //2.转换
        ObjectMapper mapper = new ObjectMapper();
        String json = mapper.writeValueAsString(p);

        System.out.println(json);
        //{"name":"张三","age":23,"gender":"男","birthday":1589356687292}
        //{"name":"张三","age":23,"gender":"男","birthday":"2020-05-13"}
    }

4. 复杂java对象转换
    1. List:数组
    2. Map:对象格式一致

    ====================List示例====================
    @Test
    public void test() throws Exception {
        //1.创建Person对象
        Person p = new Person();
        p.setName("张三");
        p.setAge(23);
        p.setGender("男");
        p.setBirthday(new Date());

        Person p1 = new Person();
        p1.setName("张三");
        p1.setAge(23);
        p1.setGender("男");
        p1.setBirthday(new Date());

        Person p2 = new Person();
        p2.setName("张三");
        p2.setAge(23);
        p2.setGender("男");
        p2.setBirthday(new Date());


        //创建List集合
        List<Person> ps = new ArrayList<Person>();
        ps.add(p);
        ps.add(p1);
        ps.add(p2);


        //2.转换
        ObjectMapper mapper = new ObjectMapper();
        String json = mapper.writeValueAsString(ps);
        // [{},{},{}]
        //[{"name":"张三","age":23,"gender":"男","birthday":"2018-07-07"},{"name":"张三","age":23,"gender":"男","birthday":"2018-07-07"},{"name":"张三","age":23,"gender":"男","birthday":"2018-07-07"}]
        System.out.println(json);
    }
    ====================Map示例====================
    @Test
    public void test() throws Exception {
        //1.创建map对象
        Map<String,Object> map = new HashMap<String,Object>();
        map.put("name","张三");
        map.put("age",23);
        map.put("gender","男");


        //2.转换
        ObjectMapper mapper = new ObjectMapper();
        String json = mapper.writeValueAsString(map);
        //{"name":"张三","age":23,"gender":"男"}
        System.out.println(json);//{"gender":"男","name":"张三","age":23}
    }

小案例:注册时的用户名合法性判断

    ===================表单==================
    <form>

        <input type="text" id="username" name="username" placeholder="请输入用户名">
        <span id="s_username"></span>
        <br>
        <input type="password" name="password" placeholder="请输入密码"><br>
        <input type="submit" value="注册"><br>

    </form>

    ================前端代码=================
    $(function () {
       //给username绑定blur事件
       $("#username").blur(function () {
           //获取username文本输入框的值
           var username = $(this).val();
           //发送ajax请求
           //期望服务器响应回的数据格式:{"userExist":true,"msg":"此用户名太受欢迎,请更换一个"}
           //                         {"userExist":false,"msg":"用户名可用"}
           $.get("findUserServlet",{username:username},function (data) {
               //判断userExsit键的值是否是true

               // alert(data);
               var span = $("#s_username");
               if(data.userExist){
                   //用户名存在
                   span.css("color","red");
                   span.html(data.msg);
               }else{
                   //用户名不存在
                   span.css("color","green");
                   span.html(data.msg);
               }
           });
       }); 
    ================后端代码=================   
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1.获取用户名
        String username = request.getParameter("username");

        //2.调用service层判断用户名是否存在

        //期望服务器响应回的数据格式:{"userExist":true,"msg":"此用户名太受欢迎,请更换一个"}
        //                         {"userExist":false,"msg":"用户名可用"}

        //设置响应的数据格式为json
        response.setContentType("application/json;charset=utf-8");
        Map<String,Object> map = new HashMap<String,Object>();

        if("tom".equals(username)){
            //存在
            map.put("userExist",true);
            map.put("msg","此用户名太受欢迎,请更换一个");
        }else{
            //不存在
            map.put("userExist",false);
            map.put("msg","用户名可用");
        }

        //将map转为json,并且传递给客户端
        //将map转为json
        ObjectMapper mapper = new ObjectMapper();
        //并且传递给客户端
        mapper.writeValue(response.getWriter(),map);
    }



分享 JQuery

RyanL
30天前

JQuery基础

1.概念

一个JavaScript框架。简化JS开发
jQuery是一个快速、简洁的JavaScript框架,是继Prototype之后又一个优秀的JavaScript代码库(或JavaScript框架)
jQuery设计的宗旨是“write Less,Do More”,即倡导写更少的代码,做更多的事情。它封装JavaScript常用的功能代码,
提供一种简便的JavaScript设计模式,优 化HTML文档操作、事件处理、动画设计和Ajax交互。

jquery-xxx.js 与 jquery-xxx.min.js区别:
    1. jquery-xxx.js:开发版本。给程序员看的,有良好的缩进和注释。体积大一些
    2. jquery-xxx.min.js:生产版本。程序中使用,没有缩进。体积小一些。程序加载更快

2.JQuery和JS的转换

1.JQuery对象和JS对象区别与转换
    1.JQuery对象在操作时,更加方便。
    2.JQuery对象和js对象方法不通用的.
2.两者相互转换
    1.jq -- > js : jq对象[索引] 或者 jq对象.get(索引)
    2.js -- > jq : $(js对象)

3.基本操作

1.事件绑定
//b1是button的id号,function是匿名函数
$("#b1").click(function () {
    alert("abc");
});
2.入口函数
//相当于JS的window.onload,等页面加载完才开始处理函数内容
$(function () {

})
3.样式控制(css)
//第一种和第二种的区别在于:第二种ctrl加鼠标可以给出属性是否正确的提示
$(function () {
    $("#div1").css("background-color", "pink");
    $("#div2").css("backgroundColor", "red");
})

4.选择器

1. 基本选择器
    1. 标签选择器(元素选择器)
        * 语法: $("html标签名") 获得所有匹配标签名称的元素
    2. id选择器 
        * 语法: $("#id的属性值") 获得与指定id属性值匹配的元素
    3. 类选择器
        * 语法: $(".class的属性值") 获得与指定的class属性值匹配的元素
    4. 并集选择器:
        * 语法: $("选择器1,选择器2....") 获取多个选择器选中的所有元素
2. 层级选择器
    1. 后代选择器
        * 语法: $("A B ") 选择A元素内部的所有B元素       
    2. 子选择器
        * 语法: $("A > B") 选择A元素内部的所有B子元素
3. 属性选择器
    1. 属性名称选择器 
        * 语法: $("A[属性名]") 包含指定属性的选择器
    2. 属性选择器
        * 语法: $("A[属性名='值']") 包含指定属性等于指定值的选择器
    3. 复合属性选择器
        * 语法: $("A[属性名='值'][]...") 包含多个属性条件的选择器
4. 过滤选择器
    1. 首元素选择器 
        * 语法: :first 获得选择的元素中的第一个元素
    2. 尾元素选择器 
        * 语法: :last 获得选择的元素中的最后一个元素
    3. 非元素选择器
        * 语法: :not(selector) 不包括指定内容的元素
    4. 偶数选择器
        * 语法: :even 偶数,从 0 开始计数
    5. 奇数选择器
        * 语法: :odd 奇数,从 0 开始计数
    6. 等于索引选择器
        * 语法: :eq(index) 指定索引元素
    7. 大于索引选择器 
        * 语法: :gt(index) 大于指定索引元素
    8. 小于索引选择器 
        * 语法: :lt(index) 小于指定索引元素
    9. 标题选择器
        * 语法: :header 获得标题(h1~h6)元素,固定写法
5. 表单过滤选择器
    1. 可用元素选择器 
        * 语法: :enabled 获得可用元素
    2. 不可用元素选择器 
        * 语法: :disabled 获得不可用元素
    3. 选中选择器 
        * 语法: :checked 获得单选/复选框选中的元素
    4. 选中选择器 
        * 语法: :selected 获得下拉框选中的元素

5.DOM操作

1. 内容操作
    1. html(): 获取/设置元素的标签体内容   <a><font>内容</font></a>  --> <font>内容</font>
    2. text(): 获取/设置元素的标签体纯文本内容   <a><font>内容</font></a> --> 内容
    3. val(): 获取/设置元素的value属性值
2. 属性操作
    1. 通用属性操作
        1. attr(): 获取/设置元素的属性
        2. removeAttr():删除属性
        3. prop():获取/设置元素的属性
        4. removeProp():删除属性

        * attr和prop区别?
            1. 如果操作的是元素的固有属性,则建议使用prop
            2. 如果操作的是元素自定义的属性,则建议使用attr
    2. 对class属性操作
        1. addClass():添加class属性值
        2. removeClass():删除class属性值
        3. toggleClass():切换class属性
            * toggleClass("one"): 
                * 判断如果元素对象上存在class="one",则将属性值one删除掉。  如果元素对象上不存在class="one",则添加
        4. css():
    3. CRUD操作:
        1. append():父元素将子元素追加到末尾
            * 对象1.append(对象2): 将对象2添加到对象1元素内部,并且在末尾
        2. prepend():父元素将子元素追加到开头
            * 对象1.prepend(对象2):将对象2添加到对象1元素内部,并且在开头
        3. appendTo():
            * 对象1.appendTo(对象2):将对象1添加到对象2内部,并且在末尾
        4. prependTo():
            * 对象1.prependTo(对象2):将对象1添加到对象2内部,并且在开头


        5. after():添加元素到元素后边
            * 对象1.after(对象2): 将对象2添加到对象1后边。对象1和对象2是兄弟关系
        6. before():添加元素到元素前边
            * 对象1.before(对象2): 将对象2添加到对象1前边。对象1和对象2是兄弟关系
        7. insertAfter()
            * 对象1.insertAfter(对象2):将对象2添加到对象1后边。对象1和对象2是兄弟关系
        8. insertBefore()
            * 对象1.insertBefore(对象2): 将对象2添加到对象1前边。对象1和对象2是兄弟关系

        9. remove():移除元素
            * 对象.remove():将对象删除掉
        10. empty():清空元素的所有后代元素。
            * 对象.empty():将对象的后代元素全部清空,但是保留当前对象以及其属性节点



RyanL
1个月前

题目描述

翻转一棵二叉树。

样例

输入:

     4
   /   \
  2     7
 / \   / \
1   3 6   9
输出:

     4
   /   \
  7     2
 / \   / \
9   6 3   1

算法1

(递归) $O(n)$
  • 遍历二叉树,依次交换每个节点的左右子节点
  • 需要注意:需要先保存一边的节点,否则在把另一边的节点拼过来之后这个节点会丢失

Java代码

class Solution {
    public TreeNode mirrorTree(TreeNode root) {
        if(root == null) return null;
        TreeNode tmp = root.left;
        root.left = mirrorTree(root.right);
        root.right = mirrorTree(tmp);
        return root;
    }
}

算法2

(栈/队列) $O(n)$
  • 和递归一样的思路,用栈和队列来BFS,遍历到的节点交换其左右节点,并把这个节点的左右孩子都加入栈/队列中
  • 在这里,先把孩子节点加入栈/队列中和先交换左右孩子的效果是一样的

Java 代码

class Solution {
    public TreeNode mirrorTree(TreeNode root) {
        if(root == null) return null;
        Stack<TreeNode> stk = new Stack<>();
        stk.push(root);
        while(!stk.isEmpty()){
            TreeNode node = stk.pop();
            if(node.left != null) stk.push(node.left);
            if(node.right != null) stk.push(node.right);
            TreeNode tmp = node.left;
            node.left = node.right;
            node.right = tmp;
        }
        return root;
    }
}
队列
class Solution {
    public TreeNode mirrorTree(TreeNode root) {
        if(root == null) return null;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while(!queue.isEmpty()){
            TreeNode node = queue.poll();
            if(node.left != null) queue.offer(node.left);
            if(node.right != null) queue.offer(node.right);
            TreeNode tmp = node.left;
            node.left = node.right;
            node.right = tmp;
        }
        return root;
    }
}



RyanL
1个月前

题目描述

实现 pow(x, n) ,即计算 x 的 n 次幂函数。

样例1

输入: 2.00000, 10
输出: 1024.00000

样例2

输入: 2.00000, -2
输出: 0.25000
解释: 2-2 = 1/22 = 1/4 = 0.25

算法1

(快速幂的递归写法) $O(logn)$

  • 每次把数字缩小一半再相乘

  • n为偶数:$3^{20}=3^{10}·3^{10}$

  • n为奇数:$3^{21}=3^{10}·3^{10}·3^1$
  • 注意:负数且为奇数,>>1结果并不是除2,例如-5>>1=-3,且-1>>1=-1,所以递归到-1需要返回,否则栈会溢出
  • 所以$3^{-5}=3^{-3}·3^{-3}*3$

Java 代码

class Solution {
    //递归法:分治
    public double myPow(double x, int n) {
        if(n == 0) return 1;
        if(n == -1) return 1/x;
        double half = myPow(x,n>>1);
        half *= half;
        return (n & 1) == 1 ? half*x : half;
    }
}

算法2

(快速幂的非递归写法) $O(logn)$
  • $21=(10101)2$
  • $3^{21}=3^{1·2^{4}}·3^{0·2^{3}}·3^{1·2^{2}}·3^{0·2^{1}}·3^{1·2^{0}}$
  • $3^{2^0}=3$,所以$3^2$,$3^4$可以依次由前面平方计算得出,并且在计算的过程中,判断每一位二进制位,决定是否将某一项加入结果中
  • 注意:n为负数先转为正数,最后再取倒数。整型最小值取反会越溢出,需要转换为long类型

Java 代码

class Solution {
    public double myPow(double x, int n) {
        double res = 1.0;
        boolean isMinus = n < 0 ? true : false;
        long m = isMinus ? -((long)n) : n;
        while(m > 0){
            if((m & 1) == 1) res *= x;
            x *= x;
            m >>= 1;
        }
        return isMinus ? 1/res : res;
    }
}



RyanL
1个月前

题目描述

在一个火车旅行很受欢迎的国度,你提前一年计划了一些火车旅行。在接下来的一年里,你要旅行的日子将以一个名为 days 的数组给出。每一项是一个从 1 到 365 的整数。

火车票有三种不同的销售方式:

一张为期一天的通行证售价为 costs[0] 美元;
一张为期七天的通行证售价为 costs[1] 美元;
一张为期三十天的通行证售价为 costs[2] 美元。
通行证允许数天无限制的旅行。 例如,如果我们在第 2 天获得一张为期 7 天的通行证,那么我们可以连着旅行 7 天:第 2 天、第 3 天、第 4 天、第 5 天、第 6 天、第 7 天和第 8 天。

返回你想要完成在给定的列表 days 中列出的每一天的旅行所需要的最低消费。

样例1

输入:days = [1,4,6,7,8,20], costs = [2,7,15]
输出:11
解释: 
例如,这里有一种购买通行证的方法,可以让你完成你的旅行计划:
在第 1 天,你花了 costs[0] = $2 买了一张为期 1 天的通行证,它将在第 1 天生效。
在第 3 天,你花了 costs[1] = $7 买了一张为期 7 天的通行证,它将在第 3, 4, ..., 9 天生效。
在第 20 天,你花了 costs[0] = $2 买了一张为期 1 天的通行证,它将在第 20 天生效。
你总共花了 $11,并完成了你计划的每一天旅行。

样例2

输入:days = [1,2,3,4,5,6,7,8,9,10,30,31], costs = [2,7,15]
输出:17
解释:
例如,这里有一种购买通行证的方法,可以让你完成你的旅行计划: 
在第 1 天,你花了 costs[2] = $15 买了一张为期 30 天的通行证,它将在第 1, 2, ..., 30 天生效。
在第 31 天,你花了 costs[0] = $2 买了一张为期 1 天的通行证,它将在第 31 天生效。 
你总共花了 $17,并完成了你计划的每一天旅行。

算法1

(动态规划) $O(n)$
  • 由于每一天出行日所买的通行证决定了后面其他出行日的花费,想到可以从后往前推导每一次通行证的买法

状态定义:$dp[i]$表示在第i天之后花费最少的金额
状态方程:当i是出行日时,$dp[i]=min(c[0]+dp[i+1],c[1]+dp[i+7],c[2]+dp[i+30])$,否则$dp[i]=dp[i+1]$

  • 买了7天通行证,则往后7天都不需要再买了,30天同理
  • 如果不是出行日,花费最少的金额延续后一天的最小金额即可

Java 代码

class Solution {
    public int mincostTickets(int[] days, int[] costs) {
        int n = days.length;
        int min = days[0], max = days[n-1];
        int[] dp = new int[400];
        for(int i = max, j = n-1; i >= min; i--){
            if(i == days[j]){
                dp[i] = Math.min(Math.min(costs[0]+dp[i+1],costs[1]+dp[i+7]),costs[2]+dp[i+30]);
                j--;
            }else{
                dp[i] = dp[i+1];
            }
        }
        return dp[min];
    }
}