Spring 3.1新特性之三:Spring对声明式缓存的支持
一、概述:
Spring 3.1 引入了激动人心的基于注释(annotation)的缓存(cache)技术,它本质上不是一个具体的缓存实现方案(例如EHCache 或者 OSCache),而是一个对缓存使用的抽象,通过在既有代码中添加少量它定义的各种 annotation,即能够达到缓存方法的返回对象的效果。
Spring Cache特点:
Spring 的缓存技术还具备相当的灵活性,不仅能够使用 SpEL(Spring Expression Language)来定义缓存的 key 和各种 condition,还提供开箱即用的缓存临时存储方案,也支持和主流的缓存方式:例如 EHCache 等集成。
特点总结如下:
- 通过少量的配置 annotation 注释即可使得既有代码支持缓存
- 支持开箱即用 Out-Of-The-Box,即不用安装和部署额外第三方组件即可使用缓存
- 支持 Spring Express Language,能使用对象的任何属性或者方法来定义缓存的 key 和 condition
- 支持 AspectJ,并通过其实现任何方法的缓存支持
- 支持自定义 key 和自定义缓存管理者,具有相当的灵活性和扩展性
二、Spring对Cache的支持有两种方法
2.1、通过注解去使用到Cache
通过图书ID查询图书信息的方法做缓存,以图书ID为 key,图书名称为 value,当以相同的图书ID查询图书信息的时候,直接从缓存中返回结果,则不经过数据库查询,反之则查询数据库更新缓存。当然还支持 reload 缓存
具体的实体类代码如下:
- package com.my.data.cache.dao;
- import java.io.Serializable;
- /**
- * 图书领域对象
- * @author wbw
- *
- */
- public class Book implements Serializable {
- /**
- * 序列化版本号
- */
- private static final long serialVersionUID = -2710076757833997658L;
- /**
- * 图书ID
- */
- private String bookId;
- /**
- * 图书名称
- */
- private String bookName;
- /**
- * @return the 图书ID
- */
- public String getBookId() {
- return bookId;
- }
- /**
- * @param 图书ID the bookId to set
- */
- public void setBookId(String bookId) {
- this.bookId = bookId;
- }
- /**
- * @return the 图书名称
- */
- public String getBookName() {
- return bookName;
- }
- /**
- * @param 图书名称 the bookName to set
- */
- public void setBookName(String bookName) {
- this.bookName = bookName;
- }
- }
然后定义缓存管理器,主要用于处理新增缓存对象、删除缓存对象、更新缓存对象、查询缓存对象、清空缓存等操作
具体代码如下:
- package com.my.cacheManage;
- import java.util.concurrent.ConcurrentHashMap;
- /**
- * 自定义缓存控制器 、回调接口、监听器
- * 缓存代码和业务逻辑耦合度高
- * 不灵活
- * 缓存存储写的很死,不能灵活的与第三方缓存插件相结合
- *
- * @author wangbowen
- *
- * @param <T>
- */
- public class CacheManagerHandler<T> {
- //ConcurrentHashMap jdk1.5 线程安全 分段锁
- private ConcurrentHashMap<String,T> cache = new ConcurrentHashMap<String,T>();
- /**
- * 根据key获取缓存对象
- * @param key 缓存对象名
- * @return 缓存对象
- */
- public T getValue(Object key){
- return cache.get(key);
- }
- /**
- * 新增或更新
- * @param key
- * @param value
- */
- public void put(String key,T value){
- cache.put(key, value);
- }
- /**
- * 新增缓存对象
- * @param key 缓存对象名称
- * @param value 缓存对象
- * @param time 缓存时间(单位:毫秒) -1表示时间无限制
- * @param callBack
- */
- public void put(String key,T value,long time,CacheCallBack callBack){
- cache.put(key, value);
- if(time!=-1){
- //启动监听
- new CacheListener(key,time,callBack);
- }
- }
- /**
- * 根据key删除缓存中的一条记录
- * @param key
- */
- public void evictCache(String key){
- if(cache.containsKey(key)){
- cache.remove(key);
- }
- }
- /**
- * 获取缓存大小
- * @return
- */
- public int getCacheSize(){
- return cache.size();
- }
- /**
- * 清空缓存
- */
- public void evictCache(){
- cache.clear();
- }
- }
定义图书服务接口
- package com.my.data.cache.service;
- import com.my.data.cache.domain.Book;
- /**
- * 图书服务接口
- * @author wbw
- *
- */
- public interface BookService {
- /**
- * 根据图形ID查询图书
- * @param bookId 图书ID
- * @return 图书信息
- */
- public Book findBookById(String bookId);
- }
图书服务接口实现类
- package com.my.service.impl;
- import com.my.cacheManage.CacheManagerHandler;
- import com.my.domain.Account;
- import com.my.service.MyAccountService;
- /**
- * 实现类
- * @author wangbowen
- *
- */
- public class BookServiceImpl implements BookService {
- /**
- * 缓存控制器
- */
- private CacheManagerHandler<Book> myCacheManager;
- /**
- * 初始化
- */
- public BookServiceImpl(){
- myCacheManager = new CacheManagerHandler<Book>();
- }
- @Override
- public Book getBookByID(String id) {
- Account result = null;
- if(id!=null){
- //先查询缓存中是否有,直接返回
- result = myCacheManager.getValue(id);
- if(result!=null){
- System.out.println("从缓存查询到:"+id);
- return result;
- }else{
- result = getFormDB(id);
- if(result!=null){//将数据查询出来的结果更新到缓存集合中
- myCacheManager.put(id, result);
- return result;
- }else{
- System.out.println("数据库为查询到"+id+"账户信息");
- }
- }
- }
- return null;
- }
- /**
- * 从数据库中查询
- * @param name
- * @return
- */
- private Book getFormDB(String id) {
- System.out.println("从数据库中查询:"+id);
- return new Book(id);
- }
- }
运行执行:
- package com.my.cache.test;
- import com.my.service.MyAccountService;
- import com.my.service.impl.MyAccountServiceImpl;
- /**
- * 测试
- * @author wbw
- *
- */
- public class MyCacheTest {
- public static void main(String[] args) {
- BookService s = new BookServiceImpl();
- s.getBookByid("1");// 第一次查询,应该是数据库查询
- s.getBookByid("1");// 第二次查询,应该直接从缓存返回
- }
- }
控制台输出信息:
- 从数据库中查询:1
- 从缓存查询到:1
虽然自定义缓存能实现缓存的基本功能,但是这种自定义缓存存在很大的缺点:
1.缓存代码和实际业务耦合度高,不便于后期修改。
2.不灵活,需要按照某种缓存规则进行缓存,不能根据不同的条件进行缓存
3.兼容性太差,不能与第三方缓存组件兼容。
Spring Cache基于注解的实现方式:
领域对象:
- package com.my.data.cache.domain;
- import java.io.Serializable;
- import javax.persistence.Column;
- import javax.persistence.Entity;
- import javax.persistence.GeneratedValue;
- import javax.persistence.GenerationType;
- import javax.persistence.Id;
- import javax.persistence.Table;
- @Entity
- @Table(name="book")
- public class Book implements Serializable {
- /**
- *
- */
- private static final long serialVersionUID = -6283522837937163003L;
- @Id
- @GeneratedValue(strategy = GenerationType.AUTO)
- @Column(name = "id", nullable = true)
- private Integer id;
- private String isbn;
- private String title;
- public Book(String isbn, String title) {
- this.isbn = isbn;
- this.title = title;
- }
- public Book() {
- }
- public Book(int id, String isbn, String title) {
- super();
- this.id = id;
- this.isbn = isbn;
- this.title = title;
- }
- public int getId() {
- return id;
- }
- public void setId(int id) {
- this.id = id;
- }
- public String getIsbn() {
- return isbn;
- }
- public void setIsbn(String isbn) {
- this.isbn = isbn;
- }
- public String getTitle() {
- return title;
- }
- public void setTitle(String title) {
- this.title = title;
- }
- @Override
- public String toString() {
- return "Book{" + "isbn='" + isbn + '\'' + ", title='" + title + '\'' + '}';
- }
- }
图书服务接口
- package com.my.data.cache.service;
- import java.util.List;
- import com.my.data.cache.domain.Book;
- public interface BookService {
- public Book findById(Integer bid);
- public List<Book> findBookAll();
- public void insertBook(Book book);
- public Book findByTitle(String title);
- public int countBook();
- public void modifyBook(Book book);
- public Book findByIsbn(String isbn);
- }
图书服务接口,这里 ORM框架使用的是Spring Data 通过基于注解的查询方式能更简便的与数据交互
- package com.my.data.cache.service.impl;
- import java.util.List;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.cache.annotation.CacheEvict;
- import org.springframework.cache.annotation.Cacheable;
- import org.springframework.stereotype.Service;
- import org.springframework.transaction.annotation.Transactional;
- import com.my.data.cache.annotation.LogAnnotation;
- import com.my.data.cache.domain.Book;
- import com.my.data.cache.exception.MyException;
- import com.my.data.cache.repository.BookRepository;
- import com.my.data.cache.service.BookService;
- @Service
- @Transactional
- public class BookServiceImpl implements BookService{
- private static final Logger log = LoggerFactory.getLogger(BookServiceImpl.class);
- @Autowired
- private BookRepository bookRepository;
- //将缓存保存进andCache,并使用参数中的bid加上一个字符串(这里使用方法名称)作为缓存的key
- @Cacheable(value="andCache",key="#bid+'findById'")
- @LogAnnotation(value="通过Id查询Book")
- public Book findById(Integer bid) {
- this.simulateSlowService();
- return bookRepository.findById(bid);
- }
- @Override
- public List<Book> findBookAll() {
- return bookRepository.findBookAll();
- }
- //将缓存保存进andCache,并当参数title的长度小于32时才保存进缓存,默认使用参数值及类型作为缓存的key
- @Cacheable(value="andCache",condition="#title.length >5")
- public Book findByTitle(String title){
- return null;
- }
- /**
- * 新增
- * @param book
- * @return
- */
- public void insertBook(Book book){
- bookRepository.save(book);
- }
- @Override
- public int countBook() {
- return bookRepository.countBook();
- }
- //清除掉指定key中的缓存
- @CacheEvict(value="andCache",key="#book.id + 'findById'")
- public void modifyBook(Book book) {
- log.info("清除指定缓存"+book.getId()+"findById");
- bookRepository.save(book);
- }
- //清除掉全部缓存
- @CacheEvict(value="andCache",allEntries=true,beforeInvocation=true)
- public void ReservedBook() {
- log.info("清除全部的缓存");
- }
- // Don't do this at home
- private void simulateSlowService() {
- try {
- long time = 5000L;
- Thread.sleep(time);
- } catch (InterruptedException e) {
- throw new MyException("程序出错", e);
- }
- }
- @Override
- public Book findByIsbn(String isbn) {
- return bookRepository.findByIsbn(isbn);
- }
- }
BookRepository接口
- package com.my.data.cache.repository;
- import java.util.List;
- import org.springframework.data.jpa.repository.Query;
- import org.springframework.data.repository.CrudRepository;
- import com.my.data.cache.dao.CommonRepository;
- import com.my.data.cache.domain.Book;
- /**
- * 接口
- * @author wbw
- *
- */
- public interface BookRepository extends CrudRepository<Book, Integer> {
- @Query("select b from Book b where 1=1")
- public List<Book> findBookAll();
- /**
- * 根据isbn查询
- * @param name
- * @return
- */
- @Query("select b from Book b where b.id =?1")
- public Book findById(Integer bid);
- /**
- * 统计size
- * @return
- */
- @Query("select count(*) from Book where 1=1 ")
- public int countBook();
- /**
- * 根据命名规范查询 findBy+属性
- * @param isbn
- * @return
- */
- public Book findByIsbn(String isbn);
- }
Controller 代码:
- package com.my.data.cache.controller;
- import java.util.List;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.web.bind.annotation.PathVariable;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RequestMethod;
- import org.springframework.web.bind.annotation.ResponseBody;
- import org.springframework.web.bind.annotation.RestController;
- import com.my.data.cache.domain.Book;
- import com.my.data.cache.service.BookService;
- @RestController
- @RequestMapping("/book")
- public class BookController {
- private static final Logger log = LoggerFactory.getLogger(BookController.class);
- @Autowired
- private BookService bookService;
- @RequestMapping("/{id}")
- public @ResponseBody Book index(@PathVariable("id") Integer id){
- Book b = bookService.findById(id);
- log.info(b.getIsbn()+"------>"+b.getTitle());
- return b;
- }
- @RequestMapping(value = "/list", method = RequestMethod.GET)
- public @ResponseBody List<Book> list(){
- List<Book> b = bookService.findBookAll();
- return b;
- }
- @RequestMapping(value = "/add")
- public String insertBook(){
- Book b = new Book();
- b.setId(4);
- b.setIsbn("1111");
- b.setTitle("相信自己");
- bookService.insertBook(b);
- return "success";
- }
- /**
- * 更新
- * @return
- */
- @RequestMapping(value = "/update")
- public String update(){
- Book b = new Book();
- b.setId(1);
- b.setIsbn("1");
- b.setTitle("爱的力量");
- bookService.modifyBook(b);
- return "success";
- }
- }
测试-------这里我们采用Spring Boot 启动服务的方式,
- package com.my.data.cache;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.boot.CommandLineRunner;
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
- import org.springframework.cache.annotation.EnableCaching;
- import com.my.data.cache.domain.Book;
- import com.my.data.cache.service.BookService;
- /**
- *
- * 启动器
- *
- */
- @SpringBootApplication
- @EnableCaching//扫描cahce注解
- public class Application1 implements CommandLineRunner{
- @Autowired
- private BookService bookService;
- @Override
- public void run(String... args) throws Exception {
- Book b1 = bookService.findByIsbn("1");
- Book b2 = bookService.findByIsbn("2");
- Book b3 = bookService.findById(3);
- System.out.println(b1);
- System.out.println(b2);
- System.out.println(b3);
- }
- public static void main(String[] args) {
- SpringApplication.run(Application1.class,args);
- }
- }
第一次访问indexI()方法,可以从下面的控制台信息看出:发出了sql语句从数据库查询数据,然后将查询的数据缓存,下次有相同条件访问相同的请求则直接从缓存中取数据
- Hibernate: select book0_.id as id1_0_, book0_.isbn as isbn2_0_, book0_.title as title3_0_ from book book0_ where book0_.isbn=?
- Hibernate: select book0_.id as id1_0_, book0_.isbn as isbn2_0_, book0_.title as title3_0_ from book book0_ where book0_.isbn=?
- Hibernate: select book0_.id as id1_0_, book0_.isbn as isbn2_0_, book0_.title as title3_0_ from book book0_ where book0_.id=?
- Book{isbn='1', title='爱的力量'}
- 2016-03-10 11:22:40.107 INFO 8132 --- [ restartedMain] com.my.data.cache.Application1 : Started Application1 in 42.661 seconds (JVM running for 46.34)
第二次访问indexI()方法,则直接从缓存中获取数据,不在查询数据库
- Book{isbn='1', title='爱的力量'}
- 2016-03-10 11:27:43.936 INFO 6436 --- [ restartedMain] com.my.data.cache.Application1 : Started Application1 in 19.363 seconds (JVM running for 20.063)
从上面Spring Cahce的示例代码可以看出,Spring Cache通过在既有代码中添加少量它定义的各种 annotation,即能够达到缓存方法的返回对象的效果,并没有太多的缓存业务逻辑代码。
Spring Cache 部分注解介绍:
- @Cacheable 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存
- @CachePut 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存,和 @Cacheable 不同的是,它每次都会触发真实方法的调用
- @CacheEvict 主要针对方法配置,能够根据一定的条件对缓存进行清空,清除全部的缓存@CacheEvict(value="缓存名字",allEntries=true,beforeInvocation=true)
首先,我们需要提供一个 CacheManager
接口的实现,这个接口告诉 spring 有哪些 cache 实例,spring 会根据 cache 的名字查找 cache 的实例。另外还需要自己实现 Cache 接口,Cache 接口负责实际的缓存逻辑,例如增加键值对、存储、查询和清空等。
利用 Cache 接口,我们可以对接任何第三方的缓存系统,例如 EHCache
、OSCache
,甚至一些内存数据库例如 memcache
或者redis
等。下面我举一个简单的例子说明如何做。
- import java.util.Collection;
- import org.springframework.cache.support.AbstractCacheManager;
- public class MyCacheManager extends AbstractCacheManager {
- private Collection<? extends MyCache> caches;
- /**
- * Specify the collection of Cache instances to use for this CacheManager.
- */
- public void setCaches(Collection<? extends MyCache> caches) {
- this.caches = caches;
- }
- @Override
- protected Collection<? extends MyCache> loadCaches() {
- return this.caches;
- }
- }
上面的自定义的 CacheManager 实际继承了 spring 内置的 AbstractCacheManager,实际上仅仅管理 MyCache 类的实例。
下面是MyCache的定义:
- import java.util.HashMap;
- import java.util.Map;
- import org.springframework.cache.Cache;
- import org.springframework.cache.support.SimpleValueWrapper;
- public class MyCache implements Cache {
- private String name;
- private Map<String,Account> store = new HashMap<String,Account>();;
- public MyCache() {
- }
- public MyCache(String name) {
- this.name = name;
- }
- @Override
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- @Override
- public Object getNativeCache() {
- return store;
- }
- @Override
- public ValueWrapper get(Object key) {
- ValueWrapper result = null;
- Account thevalue = store.get(key);
- if(thevalue!=null) {
- thevalue.setPassword("from mycache:"+name);
- result = new SimpleValueWrapper(thevalue);
- }
- return result;
- }
- @Override
- public void put(Object key, Object value) {
- Account thevalue = (Account)value;
- store.put((String)key, thevalue);
- }
- @Override
- public void evict(Object key) {
- }
- @Override
- public void clear() {
- }
- }
上面的自定义缓存只实现了很简单的逻辑,主要看 get 和 put 方法,其中的 get 方法留了一个后门,即所有的从缓存查询返回的对象都将其 password 字段设置为一个特殊的值,这样我们等下就能演示“我们的缓存确实在起作用!”了。
这还不够,spring 还不知道我们写了这些东西,需要通过 spring*.xml 配置文件告诉它
- <cache:annotation-driven />
- <bean id="cacheManager" class="com.rollenholt.spring.cache.MyCacheManager">
- <property name="caches">
- <set>
- <bean
- class="com.rollenholt.spring.cache.MyCache"
- p:name="accountCache" />
- </set>
- </property>
- </bean>
测试:
- Account account = accountService.getAccountByName("someone");
- logger.info("passwd={}", account.getPassword());
- account = accountService.getAccountByName("someone");
- logger.info("passwd={}", account.getPassword());
Spring Cache的注意和限制
基于 proxy 的 spring aop 带来的内部调用问题
上面介绍过 spring cache 的原理,即它是基于动态生成的 proxy 代理机制来对方法的调用进行切面,这里关键点是对象的引用问题.
如果对象的方法是内部调用(即 this 引用)而不是外部引用,则会导致 proxy 失效,那么我们的切面就失效,也就是说上面定义的各种注释包括 @Cacheable、@CachePut 和 @CacheEvict 都会失效,我们来演示一下。
- public Account getAccountByName2(String accountName) {
- return this.getAccountByName(accountName);
- }
- @Cacheable(value="accountCache")// 使用了一个缓存名叫 accountCache
- public Account getAccountByName(String accountName) {
- // 方法内部实现不考虑缓存逻辑,直接实现业务
- return getFromDB(accountName);
- }
上面我们定义了一个新的方法 getAccountByName2,其自身调用了 getAccountByName 方法,这个时候,发生的是内部调用(this),所以没有走 proxy,导致 spring cache 失效
要避免这个问题,就是要避免对缓存方法的内部调用,或者避免使用基于 proxy 的 AOP 模式,可以使用基于 aspectJ 的 AOP 模式来解决这个问题。
@CacheEvict 的可靠性问题
我们看到,@CacheEvict
注释有一个属性 beforeInvocation
,缺省为 false,即缺省情况下,都是在实际的方法执行完成后,才对缓存进行清空操作。期间如果执行方法出现异常,则会导致缓存清空不被执行。我们演示一下
- // 清空 accountCache 缓存
- @CacheEvict(value="accountCache",allEntries=true)
- public void reload() {
- throw new RuntimeException();
- }
测试:
- accountService.getAccountByName("someone");
- accountService.getAccountByName("someone");
- try {
- accountService.reload();
- } catch (Exception e) {
- //...
- }
- accountService.getAccountByName("someone");
注意上面的代码,我们在 reload 的时候抛出了运行期异常,这会导致清空缓存失败。上面的测试代码先查询了两次,然后 reload,然后再查询一次,结果应该是只有第一次查询走了数据库,其他两次查询都从缓存,第三次也走缓存因为 reload 失败了。
那么我们如何避免这个问题呢?我们可以用 @CacheEvict 注释提供的 beforeInvocation 属性,将其设置为 true,这样,在方法执行前我们的缓存就被清空了。可以确保缓存被清空。
Spring 3.1新特性之三:Spring对声明式缓存的支持的更多相关文章
- Spring 3.1新特性之三:Spring支持Servlet 3.0(待补充)
高效并发是JDK 1.6的一个重要主题,HotSpot虚拟机开发团队在这个版本上花费了大量的精力去实现各种锁优化技术,如适应性自旋(Adaptive Spinning).锁削除(Lock Elimin ...
- 【2.0新特性】Spring Boot 2.0新特性
以Java 8 为基准 Spring Boot 2.0 要求Java 版本必须8以上, Java 6 和 7 不再支持. 内嵌容器包结构调整 为了支持reactive使用场景,内嵌的容器包结构被重构了 ...
- Spring Cloud Greenwich 新特性和F升级分享
2019.01.23 期待已久的Spring Cloud Greenwich 发布了release版本,作为我们团队也第一时间把RC版本替换为release,以下为总结,希望对你使用Spring Cl ...
- 我这样回答了Spring 5的新特性,面试官对我刮目相看
最近,有一个小伙伴拿到了自己满意的Offer,和他交谈的过程中得知他面试官问他关于Spring的问题比较多,其中最让面试官满意的就是自己回答关于Spring 5的知识点回答的不错. Spring5于2 ...
- 【Finchley】【新特性】Spring Cloud Finchley 新特性
Finchley 正式版的发布貌似经历了相当长的时间,这次的重大发布主要带来了以下 4 项重大更新. 重大更新 1.新增 Spring Cloud Gateway 组件 Spring Cloud Ga ...
- Spring 3.1新特性之一:spring注解之@profile
前言 由于在项目中使用Maven打包部署的时候,经常由于配置参数过多(比如Nginx服务器的信息.ZooKeeper的信息.数据库连接.Redis服务器地址等),导致实际现网的配置参数与测试服务器参数 ...
- Spring官方发布新成员:Spring GraphQL
近日,在GraphQL Java诞生6周年的时候,Spring社区通过博客宣布正式创建全新项目:Spring GraphQL,同时还发布了这个新项目的里程碑1.0版本. 博客原文:https://sp ...
- Spring 事务配置管理,简单易懂,详细 [声明式]
Spring 事务配置说明 Spring 如果没有特殊说明,一般指是跟数据存储有关的数据操作事务操作:对于数据持久操作的事务配置,一般有三个对象,数据源,事务管理器,以及事务代理机制: Spring ...
- Oracle 12C 新特性之 db默认字符集AL32UTF8、PDB支持不同字符集
一. db默认字符集AL32UTF8Specify the database character set when you create the database. Starting from Ora ...
随机推荐
- java 常用设计模式(转载)
http://www.cnblogs.com/hnrainll/archive/2011/12/29/2305582.html 设计模式:一个程序员对设计模式的理解:“不懂”为什么要把很简单的东西搞得 ...
- 认识XmlReader
认识XmlReader 摘要 XmlReader类是组成.NET的关键技术之一,极大地方便了开发人员对Xml的操作.通过本文您将对XmlReader有一个很好的认识,并将其应用到实际开发中. 目录 ...
- FPGA学习记录 - Quartus II 未使用管脚设置为三态输入
未使用管脚设置为三态输入 Assignments -> Device 或双击器件
- Unix环境高级编程—进程关系
终端登录 网络登录 进程组 getpgrp(void) setpgid(pid_t pid, pid_) 会话: 是一个或多个进程组的集合,通常由shell的管道将几个进程编成一组. setsid(v ...
- native2ascii转码工具的使用
native2ascii转码工具是JDK自带的一种,方便我们将非unicode的编码文件转为unicode格式的文件,位置一般是位于JAVA_HOME/bin目录下. Why? 在做Java开发的时候 ...
- 九度OJ 1008:最短路径问题 (最短路)
时间限制:1 秒 内存限制:32 兆 特殊判题:否 提交:8064 解决:2685 题目描述: 给你n个点,m条无向边,每条边都有长度d和花费p,给你起点s终点t,要求输出起点到终点的最短距离及其花费 ...
- 【足迹C++primer】35、特定容器算法
版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/cutter_point/article/details/33732681 特定容器算法 lst.me ...
- Linux 设备和模块的分类
概念:在Linux系统中,所有设备都被映射成 [设备文件] 来处理,设备文件,应用程序可以像操作普通文件一样对硬件设备进行操作. 一.设备类型 整理自:(相当不错,建议有时间看下原文) <第一章 ...
- 3D焦点图插件
在线演示 本地下载
- 20145239杜文超 《Java程序设计》第1周学习总结
20145239<Java程序设计>第1周学习总结 教材学习内容总结 第一周. 通过教材简单的了解了java的历史.因为之前看过视频,所以有一个大致明了的认识. 识记了Java三大平台:J ...