
暂无个人介绍
前段时间在我厂卷爷的指导下将Docker在我的实际项目中落地,最近几个小demo都尽量熟悉docker的使用,希望通过这篇文章分享我截止目前的使用经验(如有不准确的表述,欢迎帮我指出)。本文的主要内容是关于Java应用程序的docker化,首先简单介绍了docker和docker-compose,然后利用两个案例进行实践说明。 简单说说Docker,现在云计算领域火得一塌糊涂的就是它了吧。Docker的出现是为了解决PaaS的问题:运行环境与具体的语言版本、项目路径强关联,因此干脆利用lxc技术进行资源隔离,构造出跟随应用发布的运行环境,这样就解决了语言版本的限制问题。PaaS的出现是为了让运维人员不需要管理一台虚拟机,IaaS的出现是为了让运维人员不需要管理物理机。云计算,说到底都是俩字——运维。 云计算领域的技术分为虚拟化技术和资源管理两个方面,正好对应我们今天要讲的两个工具:Docker和docker-compose。Docker的主要概念有:容器、镜像、仓库;docker-compose是fig的后续版本,负责将多个docker服务整合起来,对外提供一致服务。 1. Spring Boot应用的docker化 首先看Spring Boot应用程序的docker化,由于Spring Boot内嵌了tomcat、Jetty等容器,因此我们对docker镜像的要求就是需要java运行环境。我的应用代码的的Dockerfile文件如下: #基础镜像:仓库是java,标签用8u66-jdk FROM java:8u66-jdk #当前镜像的维护者和联系方式 MAINTAINER duqi duqi@example.com #将打包好的spring程序拷贝到容器中的指定位置 ADD target/bookpub-0.0.1-SNAPSHOT.jar /opt/bookpub-0.0.1-SNAPSHOT.jar #容器对外暴露8080端口 EXPOSE 8080 #容器启动后需要执行的命令 CMD java -Djava.security.egd=file:/dev/./urandom -jar /opt/bookpub-0.0.1-SNAPSHOT.jar 因为目前的示例程序比较简单,这个dockerfile并没有在将应用程序的数据存放在宿主机上。如果你的应用程序需要写文件系统,例如日志,最好利用VOLUME /tmp命令,这个命令的效果是:在宿主机的/var/lib/docker目录下创建一个临时文件并把它链接到容器中的/tmp目录。 把这个Dockerfile放在项目的根目录下即可,后续通过docker-compose build统一构建:基础镜像是只读的,然后会在该基础镜像上增加新的可写层来供我们使用,因此java镜像只需要下载一次。 docker-compose是用来做docker服务编排,参看《Docker从入门到实践》中的解释: Compose 项目目前在 Github 上进行维护,目前最新版本是 1.2.0。Compose 定位是“defining and running complex applications with Docker”,前身是 Fig,兼容 Fig 的模板文件。 Dockerfile 可以让用户管理一个单独的应用容器;而 Compose 则允许用户在一个模板(YAML 格式)中定义一组相关联的应用容器(被称为一个 project,即项目),例如一个 Web 服务容器再加上后端的数据库服务容器等。 单个docker用起来确实没什么用,docker技术的关键在于持续交付,通过与jekins的结合,可以实现这样的效果:开发人员提交push,然后jekins就自动构建并测试刚提交的代码,这就是我理解的持续交付。 2. spring boot + redis + mongodb 在这个项目中,我启动三个容器:web、redis和mongodb,然后将web与redis连接,web与mongodb连接。首先要进行redis和mongodb的docker化,redis镜像的Dockerfile内容是: FROM ubuntu:14.04 RUN apt-get update RUN apt-get -y install redis-server EXPOSE 6379 ENTRYPOINT ["/usr/bin/redis-server"] Mongodb镜像的Dockerfile内容是,docker官方给了mongodb的docker化教程,我直接拿来用了,参见Dockerizing MongoDB: # Format: FROM repository[:version] FROM ubuntu:14.04 # Format: MAINTAINER Name <email@addr.ess> MAINTAINER duqi duqi@example.com # Installation: # Import MongoDB public GPG key AND create a MongoDB list file RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10 RUN echo "deb http://repo.mongodb.org/apt/ubuntu "$(lsb_release -sc)"/mongodb-org/3.0 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-3.0.list # Update apt-get sources AND install MongoDB RUN apt-get update && apt-get install -y mongodb-org # Create the MongoDB data directory RUN mkdir -p /data/db # Expose port 27017 from the container to the host EXPOSE 27017 # Set usr/bin/mongod as the dockerized entry-point application ENTRYPOINT ["/usr/bin/mongod"] 使用docker-compose编排三个服务,具体的模板文件如下: web: build: . ports: - "49161:8080" links: - redis - mongodb redis: image: duqi/redis ports: - "6379:6379" mongodb: image: duqi/mongodb ports: - "27017:27017" 架构比较简单,第一个区块的build,表示docker中的命令“docker build .”,用于构建web镜像;ports这块表示将容器的8080端口与宿主机(IP地址是:192.168.99.100)的49161对应。因为现在docker不支持原生的osx,因此在mac下使用docker,实际上是在mac上的一台虚拟机(docker-machine)上使用docker,这台机器的地址就是192.168.99.100。参见:在mac下使用docker links表示要连接的服务,redis与下方的redis区块对应、mongodb与下方的mongodb区块对应。redis和mongodb类似,首先说明要使用的镜像,然后规定端口映射。 那么,如何运行呢? 命令docker-compose build,表示构建web服务,目前我用得比较土,就是编译jar之后还需要重新更新docker,优雅点不应该这样。 docker-compose build 命令docker-compose up,表示启动web服务,可以看到mongodb、redis和web依次启动,启动后用docker ps查看当前的运行容器。 docker ps 特别注意,在配置文件中写redis和mongodb的url时,要用虚拟机的地址,即192.168.99.100。例如,redis的一个配置应该为:spring.redis.host=192.168.99.100。 3. spring boot + mysql 拉取mysql镜像的指令是:docker run --name db001 -p 3306:3306 -e MYSQL_ROOT_PASSWORD=admin -d mysql:5.7,表示启动的docker容器名字是db001,登录密码一定要设定, -d表示设置Mysql版本。 我的docker-compose模板文件是: web: build: . ports: - "49161:8080" links: - mysql mysql: image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: admin MYSQL_DATABASE: springbootcookbook ports: - "3306:3306" 主要内容跟之前的类似,主要讲下mysql部分,通过environement来设置进入mysql容器后的环境变量,即连接数据库的密码MYSQL_ROOT_PASSWORD,使用的数据库名称MSYQL_DATABASE等等。 一直想写这篇文章做个总结,写来发现还是有点薄,对于docker我还需要系统得学习,不过,针对上面的例子,我都是亲自实践过的,大家有什么问题可以与我联系。 参考资料 Docker从入门到实践 Docker - Initialize mysql database with schema 使用Docker搭建基础的mysql应用 Spring Boot with docker
移动开发本质上就是手机和服务器之间进行通信,需要从服务端获取数据。反复通过网络获取数据是比较耗时的,特别是访问比较多的时候,会极大影响了性能,Android中可通过缓存机制来减少频繁的网络操作,减少流量、提升性能。 实现原理 把不需要实时更新的数据缓存下来,通过时间或者其他因素 来判别是读缓存还是网络请求,这样可以缓解服务器压力,一定程度上提高应用响应速度,并且支持离线阅读。 Bitmap的缓存 在许多的情况下(像 ListView, GridView 或 ViewPager 之类的组件 )我们需要一次性加载大量的图片,在屏幕上显示的图片和所有待显示的图片有可能需要马上就在屏幕上无限制的进行滚动、切换。 像ListView, GridView 这类组件,它们的子项当不可见时,所占用的内存会被回收以供正在前台显示子项使用。垃圾回收器也会释放你已经加载了的图片占用的内存。如果你想让你的UI运行流畅的话,就不应该每次显示时都去重新加载图片。保持一些内存和文件缓存就变得很有必要了。 使用内存缓存 通过预先消耗应用的一点内存来存储数据,便可快速的为应用中的组件提供数据,是一种典型的以空间换时间的策略。 LruCache 类(Android v4 Support Library 类库中开始提供)非常适合来做图片缓存任务 ,它可以使用一个LinkedHashMap 的强引用来保存最近使用的对象,并且当它保存的对象占用的内存总和超出了为它设计的最大内存时会把不经常使用的对象成员踢出以供垃圾回收器回收。 给LruCache 设置一个合适的内存大小,需考虑如下因素: 还剩余多少内存给你的activity或应用使用 屏幕上需要一次性显示多少张图片和多少图片在等待显示 手机的大小和密度是多少(密度越高的设备需要越大的 缓存) 图片的尺寸(决定了所占用的内存大小) 图片的访问频率(频率高的在内存中一直保存) 保存图片的质量(不同像素的在不同情况下显示) 具体的要根据应用图片使用的具体情况来找到一个合适的解决办法,一个设置 LruCache 例子: private LruCache<String, Bitmap> mMemoryCache; @Override protected void onCreate(Bundle savedInstanceState) { ... // 获得虚拟机能提供的最大内存,超过这个大小会抛出OutOfMemory的异常 final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); // 用1/8的内存大小作为内存缓存 final int cacheSize = maxMemory / 8; mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { // 这里返回的不是item的个数,是cache的size(单位1024个字节) return bitmap.getByteCount() / 1024; } }; ... } public void addBitmapToMemoryCache(String key, Bitmap bitmap) { if (getBitmapFromMemCache(key) == null) { mMemoryCache.put(key, bitmap); } } public Bitmap getBitmapFromMemCache(String key) { return mMemoryCache.get(key); } 当为ImageView加载一张图片时,会先在LruCache 中看看有没有缓存这张图片,如果有的话直接更新到ImageView中,如果没有的话,一个后台线程会被触发来加载这张图片。 public void loadBitmap(int resId, ImageView imageView) { final String imageKey = String.valueOf(resId); // 查看下内存缓存中是否缓存了这张图片 final Bitmap bitmap = getBitmapFromMemCache(imageKey); if (bitmap != null) { mImageView.setImageBitmap(bitmap); } else { mImageView.setImageResource(R.drawable.image_placeholder); BitmapWorkerTask task = new BitmapWorkerTask(mImageView); task.execute(resId); } } 在图片加载的Task中,需要把加载好的图片加入到内存缓存中。 class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { ... // 在后台完成 @Override protected Bitmap doInBackground(Integer... params) { final Bitmap bitmap = decodeSampledBitmapFromResource( getResources(), params[0], 100, 100)); addBitmapToMemoryCache(String.valueOf(params[0]), bitmap); return bitmap; } ... } 使用磁盘缓存 内存缓存能够快速的获取到最近显示的图片,但不一定就能够获取到。当数据集过大时很容易把内存缓存填满(如GridView )。你的应用也有可能被其它的任务(比如来电)中断进入到后台,后台应用有可能会被杀死,那么相应的内存缓存对象也会被销毁。 当你的应用重新回到前台显示时,你的应用又需要一张一张的去加载图片了。 磁盘文件缓存能够用来处理这些情况,保存处理好的图片,当内存缓存不可用的时候,直接读取在硬盘中保存好的图片,这样可以有效的减少图片加载的次数。读取磁盘文件要比直接从内存缓存中读取要慢一些,而且需要在一个UI主线程外的线程中进行,因为磁盘的读取速度是不能够保证的,磁盘文件缓存显然也是一种以空间换时间的策略。 如果图片使用非常频繁的话,一个 ContentProvider 可能更适合代替去存储缓存图片,比如图片gallery 应用。 下面是一个DiskLruCache的部分代码: private DiskLruCache mDiskLruCache; private final Object mDiskCacheLock = new Object(); private boolean mDiskCacheStarting = true; private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB private static final String DISK_CACHE_SUBDIR = "thumbnails"; @Override protected void onCreate(Bundle savedInstanceState) { ... // 初始化内存缓存 ... // 在后台线程中初始化磁盘缓存 File cacheDir = getDiskCacheDir(this, DISK_CACHE_SUBDIR); new InitDiskCacheTask().execute(cacheDir); ... } class InitDiskCacheTask extends AsyncTask<File, Void, Void> { @Override protected Void doInBackground(File... params) { synchronized (mDiskCacheLock) { File cacheDir = params[0]; mDiskLruCache = DiskLruCache.open(cacheDir, DISK_CACHE_SIZE); mDiskCacheStarting = false; // 结束初始化 mDiskCacheLock.notifyAll(); // 唤醒等待线程 } return null; } } class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { ... // 在后台解析图片 @Override protected Bitmap doInBackground(Integer... params) { final String imageKey = String.valueOf(params[0]); // 在后台线程中检测磁盘缓存 Bitmap bitmap = getBitmapFromDiskCache(imageKey); if (bitmap == null) { // 没有在磁盘缓存中找到图片 final Bitmap bitmap = decodeSampledBitmapFromResource( getResources(), params[0], 100, 100)); } // 把这个final类型的bitmap加到缓存中 addBitmapToCache(imageKey, bitmap); return bitmap; } ... } public void addBitmapToCache(String key, Bitmap bitmap) { // 先加到内存缓存 if (getBitmapFromMemCache(key) == null) { mMemoryCache.put(key, bitmap); } //再加到磁盘缓存 synchronized (mDiskCacheLock) { if (mDiskLruCache != null && mDiskLruCache.get(key) == null) { mDiskLruCache.put(key, bitmap); } } } public Bitmap getBitmapFromDiskCache(String key) { synchronized (mDiskCacheLock) { // 等待磁盘缓存从后台线程打开 while (mDiskCacheStarting) { try { mDiskCacheLock.wait(); } catch (InterruptedException e) {} } if (mDiskLruCache != null) { return mDiskLruCache.get(key); } } return null; } public static File getDiskCacheDir(Context context, String uniqueName) { // 优先使用外缓存路径,如果没有挂载外存储,就使用内缓存路径 final String cachePath = Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !isExternalStorageRemovable() ?getExternalCacheDir(context).getPath():context.getCacheDir().getPath(); return new File(cachePath + File.separator + uniqueName); } 不能在UI主线程中进行这项操作,因为初始化磁盘缓存也需要对磁盘进行操作。上面的程序片段中,一个锁对象确保了磁盘缓存没有初始化完成之前不能够对磁盘缓存进行访问。 内存缓存在UI线程中进行检测,磁盘缓存在UI主线程外的线程中进行检测,当图片处理完成之后,分别存储到内存缓存和磁盘缓存中。 设备配置参数改变时加载问题 由于应用在运行的时候设备配置参数可能会发生改变,比如设备朝向改变,会导致Android销毁你的Activity然后按照新的配置重启,这种情况下,我们要避免重新去加载处理所有的图片,让用户能有一个流畅的体验。 使用Fragment 能够把内存缓存对象传递到新的activity实例中,调用setRetainInstance(true)) 方法来保留Fragment实例。当activity重新创建好后, 被保留的Fragment依附于activity而存在,通过Fragment就可以获取到已经存在的内存缓存对象了,这样就可以快速的获取到图片,并设置到ImageView上,给用户一个流畅的体验。 下面是一个示例程序片段: private LruCache<String, Bitmap> mMemoryCache; @Override protected void onCreate(Bundle savedInstanceState) { ... RetainFragment mRetainFragment = RetainFragment.findOrCreateRetainFragment(getFragmentManager()); mMemoryCache = RetainFragment.mRetainedCache; if (mMemoryCache == null) { mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { ... //像上面例子中那样初始化缓存 } mRetainFragment.mRetainedCache = mMemoryCache; } ... } class RetainFragment extends Fragment { private static final String TAG = "RetainFragment"; public LruCache<String, Bitmap> mRetainedCache; public RetainFragment() {} public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) { RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG); if (fragment == null) { fragment = new RetainFragment(); } return fragment; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 使得Fragment在Activity销毁后还能够保留下来 setRetainInstance(true); } } 可以在不适用Fragment(没有界面的服务类Fragment)的情况下旋转设备屏幕。在保留缓存的情况下,你应该能发现填充图片到Activity中几乎是瞬间从内存中取出而没有任何延迟的感觉。任何图片优先从内存缓存获取,没有的话再到硬盘缓存中找,如果都没有,那就以普通方式加载图片。 参考: Caching Bitmaps LruCache 使用SQLite进行缓存 网络请求数据完成后,把文件的相关信息(如url(一般作为唯一标示),下载时间,过期时间)等存放到数据库。下次加载的时候根据url先从数据库中查询,如果查询到并且时间未过期,就根据路径读取本地文件,从而实现缓存的效果。 注意:缓存的数据库是存放在/data/data//databases/目录下,是占用内存空间的,如果缓存累计,容易浪费内存,需要及时清理缓存。 文件缓存 思路和一般缓存一样,把需要的数据存储在文件中,下次加载时判断文件是否存在和过期(使用File.lastModified()方法得到文件的最后修改时间,与当前时间判断),存在并未过期就加载文件中的数据,否则请求服务器重新下载。 注意,无网络环境下就默认读取文件缓存中的。 原文地址:http://blog.csdn.net/amazing7/article/details/51353817
概述 数组 是将元素在内存中连续存放,由于每个元素占用内存相同,可以通过下标迅速访问数组中任何元素。但是如果要在数组中增加一个元素,需要移动大量元素,在内存中空出一个元素的空间,然后将要增加的元素放在其中。同样的道理,如果想删除一个元素,同样需要移动大量元素去填掉被移动的元素。如果应用需要快速访问数据,很少插入和删除元素,就应该用数组。 链表 中的元素在内存中不是顺序存储的,而是通过存在元素中的指针联系到一起,每个结点包括两个部分:一个是存储 数据元素 的 数据域,另一个是存储下一个结点地址的 指针。 如果要访问链表中一个元素,需要从第一个元素开始,一直找到需要的元素位置。但是增加和删除一个元素对于链表数据结构就非常简单了,只要修改元素中的指针就可以了。如果应用需要经常插入和删除元素你就需要用链表。 内存存储区别 数组从栈中分配空间, 对于程序员方便快速,但自由度小。 链表从堆中分配空间, 自由度大但申请管理比较麻烦. 逻辑结构区别 数组必须事先定义固定的长度(元素个数),不能适应数据动态地增减的情况。当数据增加时,可能超出原先定义的元素个数;当数据减少时,造成内存浪费。 链表动态地进行存储分配,可以适应数据动态地增减的情况,且可以方便地插入、删除数据项。(数组中插入、删除数据项时,需要移动其它数据项) 总结 1、存取方式上,数组可以顺序存取或者随机存取,而链表只能顺序存取; 2、存储位置上,数组逻辑上相邻的元素在物理存储位置上也相邻,而链表不一定; 3、存储空间上,链表由于带有指针域,存储密度不如数组大; 4、按序号查找时,数组可以随机访问,时间复杂度为O(1),而链表不支持随机访问,平均需要O(n); 5、按值查找时,若数组无序,数组和链表时间复杂度均为O(1),但是当数组有序时,可以采用折半查找将时间复杂度降为O(logn); 6、插入和删除时,数组平均需要移动n/2个元素,而链表只需修改指针即可; 7、空间分配方面: 数组在静态存储分配情形下,存储元素数量受限制,动态存储分配情形下,虽然存储空间可以扩充,但需要移动大量元素,导致操作效率降低,而且如果内存中没有更大块连续存储空间将导致分配失败; 链表存储的节点空间只在需要的时候申请分配,只要内存中有空间就可以分配,操作比较灵活高效; 原文地址:http://blog.csdn.net/amazing7/article/details/51362071
队列 队列是一种操作受限制的线性表,它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作。进行插入操作的端称为队尾,进行删除操作的端称为队头。 队列中没有元素时,称为空队列。在队列这种数据结构中,最先插入的元素将是最先被删除的元素;反之最后插入的元素将是最后被删除的元素,因此队列又称为“先进先出”(FIFO—first in first out)的线性表。 队列空的条件:front=rear 队列满的条件: rear = MAXSIZE 顺序队列 建立顺序队列结构必须为其静态分配或动态申请一片连续的存储空间,并设置两个指针进行管理。一个是队头指针front,它指向队头元素;另一个是队尾指针rear,它指向下一个入队元素的存储位置. 顺序队列中的溢出现象: 下溢:当队列为空时,做出队运算产生的溢出现象。“下溢”是正常现象,常用作程序控制转移的条件。 真上溢:当队列满时,做进栈运算产生空间溢出的现象。“真上溢”是一种出错状态,应设法避免。 假上溢:由于入队和出队操作中,头尾指针只增加不减小,致使被删元素的空间永远无法重新利用。当队列中实际的元素个数远远小于向量空间的规模时,也可能由于尾指针已超越向量空间的上界而不能做入队操作。该现象称为”假上溢”现象。 java中Queue Queue继承于Collection,除了基本的 Collection 操作外,队列还提供其他的插入、提取和检查操作。每个方法都存在两种形式:一种抛出异常(操作失败时),另一种返回一个特殊值(null 或 false,具体取决于操作)。 add()和offer()都是将指定的元素插入队列 add() 在成功时返回 true,如果当前没有可用的空间,则抛出 IllegalStateException。 offer()当使用有容量限制的队列时,无法插入元素,而只是抛出一个异常。 element() 和 peek() 返回但不移除队列的头,如果队列为空,peek()返回 null,element()抛出异常。 remove() 和 poll() 方法可移除和返回队列的头,它们仅在队列为空时其行为有所不同:remove() 方法抛出一个异常,而 poll() 方法则返回 null。 一般使用: public class TestQueue { /** * @param args * @author JavaAlpha * Info 测试队列 */ public static void main(String[] args) { Queue<String> queue = new LinkedList<String>(); queue.offer("1");//插入元素 queue.offer("2"); queue.offer("3"); //打印元素个数 System.out.println("queue.size()"+queue.size()); //遍历打印所有的元素(按插入顺序) for (String string : queue) { System.out.println(string); } } } 结果: queue.size()3 1 2 3 JDK中包含了 双向顺序队列ArrayDeque、双向链式队列LinkedList和PriorityQueue优先队列。 ArrayDeque包括顺序栈和顺序队列,LinkedList包含链式栈和链式队列。ArrayDeque和LinkedList都是线程不安全的。 Java顺序队列的实现: package lang; import java.io.Serializable; import java.util.Arrays; public class ArrayQueue<T> implements Serializable{ private static final long serialVersionUID = 7333344126529379197L; private int DEFAULT_SIZE = 10; private int capacity;//保存数组的长度 private Object[] elementData;//定义一个数组用于保存顺序队列的元素 private int front = 0;//队头 private int rear = 0;//队尾 //以默认数组长度创建空顺序队列 public ArrayQueue() { capacity = DEFAULT_SIZE; elementData = new Object[capacity]; } //以一个初始化元素来创建顺序队列 public ArrayQueue(T element) { this(); elementData[0] = element; rear++; } public ArrayQueue(int initSize) { elementData = new Object[initSize]; } /** * 以指定长度的数组来创建顺序队列 * @param element 指定顺序队列中第一个元素 * @param initSize 指定顺序队列底层数组的长度 */ public ArrayQueue(T element, int initSize) { this.capacity = initSize; elementData = new Object[capacity]; elementData[0] = element; rear++; } /** * @Title: size * @Description: 获取顺序队列的大小 * @return */ public int size() { return rear - front; } /** * @Title: offer * @Description: 入队 * @param element */ public void offer(T element) { ensureCapacity(rear + 1); elementData[rear++] = element; } private void ensureCapacity(int minCapacity) { //如果数组的原有长度小于目前所需的长度 int oldCapacity = elementData.length; if (minCapacity > oldCapacity) { int newCapacity = (oldCapacity * 3) / 2 + 1; if (newCapacity < minCapacity) newCapacity = minCapacity; // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); } } /** * @Title: poll * @Description: 出队 * @return */ public T poll() { if (isEmpty()) { throw new IndexOutOfBoundsException("空队列异常"); } //保留队列的front端的元素的值 T oldValue = (T) elementData[front]; //释放队列的front端的元素 elementData[front++] = null; return oldValue; } /** * @Title: peek * @Description: 返回队列顶元素,但不删除队列顶元素 * @return */ public T peek() { if (isEmpty()) { throw new IndexOutOfBoundsException("空队列异常"); } return (T) elementData[front]; } /** * @Title: isEmpty * @Description: 判断顺序队列是否为空队列 * @return */ public boolean isEmpty() { return rear == front; } /** * @Title: clear * @Description: 清空顺序队列 */ public void clear() { //将底层数组所有元素赋为null Arrays.fill(elementData, null); front = 0; rear = 0; } public String toString() { if (isEmpty()) { return "[]"; } else { StringBuilder sb = new StringBuilder("["); for (int i = front; i < rear; i++) { sb.append(elementData[i].toString() + ", "); } int len = sb.length(); return sb.delete(len - 2, len).append("]").toString(); } } } java链式队列的实现 package lang; import java.io.Serializable; public class LinkQueue<T> implements Serializable{ private static final long serialVersionUID = -6726728595616312615L; //定义一个内部类Node,Node实例代表链队列的节点。 private class Node { private T data;//保存节点的数据 private Node next;//指向下个节点的引用 //无参数的构造器 public Node() { } //初始化全部属性的构造器 public Node(T data, Node next) { this.data = data; this.next = next; } } private Node front;//保存该链队列的头节点 private Node rear;//保存该链队列的尾节点 private int size;//保存该链队列中已包含的节点数 /** * <p>Title: LinkQueue </p> * <p>Description: 创建空链队列 </p> */ public LinkQueue() { //空链队列,front和rear都是null front = null; rear = null; } /** * <p>Title: LinkQueue </p> * <p>Description: 以指定数据元素来创建链队列,该链队列只有一个元素</p> */ public LinkQueue(T element) { front = new Node(element, null); //只有一个节点,front、rear都指向该节点 rear = front; size++; } /** * @Title: size * @Description: 获取顺序队列的大小 * @return */ public int size() { return size; } /** * @Title: offer * @Description: 入队 * @param element */ public void offer(T element) { //如果该链队列还是空链队列 if (front == null) { front = new Node(element, null); rear = front;//只有一个节点,front、rear都指向该节点 } else { Node newNode = new Node(element, null);//创建新节点 rear.next = newNode;//让尾节点的next指向新增的节点 rear = newNode;//以新节点作为新的尾节点 } size++; } /** * @Title: poll * @Description: 出队 * @return */ public T poll() { Node oldFront = front; front = front.next; oldFront.next = null; size--; return oldFront.data; } /** * @Title: peek * @Description: 返回队列顶元素,但不删除队列顶元素 * @return */ public T peek() { return rear.data; } /** * @Title: isEmpty * @Description: 判断顺序队列是否为空队列 * @return */ public boolean isEmpty() { return size == 0; } /** * @Title: clear * @Description: 清空顺序队列 */ public void clear() { //将front、rear两个节点赋为null front = null; rear = null; size = 0; } public String toString() { //链队列为空链队列时 if (isEmpty()) { return "[]"; } else { StringBuilder sb = new StringBuilder("["); for (Node current = front; current != null; current = current.next) { sb.append(current.data.toString() + ", "); } int len = sb.length(); return sb.delete(len - 2, len).append("]").toString(); } } public static void main(String[] args) { LinkQueue<String> queue = new LinkQueue<String>("aaaa"); //添加两个元素 queue.offer("bbbb"); queue.offer("cccc"); System.out.println(queue); //删除一个元素后 queue.poll(); System.out.println("删除一个元素后的队列:" + queue); //再次添加一个元素 queue.offer("dddd"); System.out.println("再次添加元素后的队列:" + queue); //删除一个元素后,队列可以再多加一个元素 queue.poll(); //再次加入一个元素 queue.offer("eeee"); System.out.println(queue); } } 循环队列 为充分利用向量空间,克服”假溢出”现象的方法是:将向量空间想象为一个首尾相接的圆环,并称这种向量为循环向量。存储在其中的队列称为循环队列(Circular Queue)。这种循环队列可以以单链表的方式来在实际编程应用中来实现。 循环队列的队空与队满的条件 初始化建空队时, front=rear=0 当队空时:front=rear 当队满时:front=rear 亦成立 因此只凭等式front=rear无法判断队空还是队满。 有两种方法处理上述问题: 1、另设一个标志位以区别队列是空还是满。 2、少用一个元素空间,约定以“队列头指针front在队尾指针rear的下一个位置上”作为队列“满”状态的标志。即: 队空时: front=rear 队满时: (rear+1)%maxsize=front front指向队首元素,rear指向队尾元素的下一个元素(始终指向空)。 循环队列的Java实现: package lang; import java.io.Serializable; import java.util.Arrays; public class LoopQueue<T> implements Serializable{ private static final long serialVersionUID = -3670496550272478781L; private int DEFAULT_SIZE = 10; private int capacity;//保存数组的长度 private Object[] elementData;//定义一个数组用于保存循环队列的元素 private int front = 0;//队头 private int rear = 0;//队尾 //以默认数组长度创建空循环队列 public LoopQueue() { capacity = DEFAULT_SIZE; elementData = new Object[capacity]; } //以一个初始化元素来创建循环队列 public LoopQueue(T element) { this(); elementData[0] = element; rear++; } /** * 以指定长度的数组来创建循环队列 * @param element 指定循环队列中第一个元素 * @param initSize 指定循环队列底层数组的长度 */ public LoopQueue(T element, int initSize) { this.capacity = initSize; elementData = new Object[capacity]; elementData[0] = element; rear++; } //获取循环队列的大小 public int size() { if (isEmpty()) { return 0; } return rear > front ? rear - front : capacity - (front - rear); } //插入队列 public void add(T element) { if (rear == front && elementData[front] != null) { throw new IndexOutOfBoundsException("队列已满的异常"); } elementData[rear++] = element; //如果rear已经到头,那就转头 rear = rear == capacity ? 0 : rear; } //移除队列 public T remove() { if (isEmpty()) { throw new IndexOutOfBoundsException("空队列异常"); } //保留队列的rear端的元素的值 T oldValue = (T) elementData[front]; //释放队列的rear端的元素 elementData[front++] = null; //如果front已经到头,那就转头 front = front == capacity ? 0 : front; return oldValue; } //返回队列顶元素,但不删除队列顶元素 public T element() { if (isEmpty()) { throw new IndexOutOfBoundsException("空队列异常"); } return (T) elementData[front]; } //判断循环队列是否为空队列 public boolean isEmpty() { //rear==front且rear处的元素为null return rear == front && elementData[rear] == null; } //清空循环队列 public void clear() { //将底层数组所有元素赋为null Arrays.fill(elementData, null); front = 0; rear = 0; } public String toString() { if (isEmpty()) { return "[]"; } else { //如果front < rear,有效元素就是front到rear之间的元素 if (front < rear) { StringBuilder sb = new StringBuilder("["); for (int i = front; i < rear; i++) { sb.append(elementData[i].toString() + ", "); } int len = sb.length(); return sb.delete(len - 2, len).append("]").toString(); } //如果front >= rear,有效元素为front->capacity之间、0->front之间的 else { StringBuilder sb = new StringBuilder("["); for (int i = front; i < capacity; i++) { sb.append(elementData[i].toString() + ", "); } for (int i = 0; i < rear; i++) { sb.append(elementData[i].toString() + ", "); } int len = sb.length(); return sb.delete(len - 2, len).append("]").toString(); } } } public static void main(String[] args) { LoopQueue<String> queue = new LoopQueue<String>("aaaa", 3); //添加两个元素 queue.add("bbbb"); queue.add("cccc"); //此时队列已满 System.out.println(queue); //删除一个元素后,队列可以再多加一个元素 queue.remove(); System.out.println("删除一个元素后的队列:" + queue); //再次添加一个元素,此时队列又满 queue.add("dddd"); System.out.println(queue); System.out.println("队列满时的长度:" + queue.size()); //删除一个元素后,队列可以再多加一个元素 queue.remove(); //再次加入一个元素,此时队列又满 queue.add("eeee"); System.out.println(queue); } } 阻塞队列(BlockingQueue) 几种主要的阻塞队列 自从Java 1.5之后,在java.util.concurrent包下提供了若干个阻塞队列,主要有以下几个: ArrayBlockingQueue:基于数组实现的一个阻塞队列,在创建ArrayBlockingQueue对象时必须制定容量大小。并且可以指定公平性与非公平性,默认情况下为非公平的,即不保证等待时间最长的队列最优先能够访问队列。 LinkedBlockingQueue:基于链表实现的一个阻塞队列,在创建LinkedBlockingQueue对象时如果不指定容量大小,则默认大小为Integer.MAX_VALUE。 PriorityBlockingQueue:以上2种队列都是先进先出队列,而PriorityBlockingQueue却不是,它会按照元素的优先级对元素进行排序,按照优先级顺序出队,每次出队的元素都是优先级最高的元素。注意,此阻塞队列为无界阻塞队列,即容量没有上限(通过源码就可以知道,它没有容器满的信号标志),前面2种都是有界队列。 DelayQueue:基于PriorityQueue,一种延时阻塞队列,DelayQueue中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue也是一个无界队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。 阻塞队列方法对比 1. 非阻塞队列中的几个主要方法: add(E e):将元素e插入到队列末尾,如果插入成功,则返回true;如果插入失败(即队列已满),则会抛出异常; remove():移除队首元素,若移除成功,则返回true;如果移除失败(队列为空),则会抛出异常; offer(E e):将元素e插入到队列末尾,如果插入成功,则返回true;如果插入失败(即队列已满),则返回false; poll():移除并获取队首元素,若成功,则返回队首元素;否则返回null; peek():获取队首元素,若成功,则返回队首元素;否则返回null 对于非阻塞队列,一般情况下建议使用offer、poll和peek三个方法,不建议使用add和remove方法。因为使用offer、poll和peek三个方法可以通过返回值判断操作成功与否,而使用add和remove方法却不能达到这样的效果。注意,非阻塞队列中的方法都没有进行同步措施。 2. 阻塞队列中的几个主要方法: 阻塞队列包括了非阻塞队列中的大部分方法,上面列举的5个方法在阻塞队列中都存在,但是要注意这5个方法在阻塞队列中都进行了同步措施。除此之外,阻塞队列提供了另外4个非常有用的方法: put(E e) take() offer(E e,long timeout, TimeUnit unit) poll(long timeout, TimeUnit unit) put方法用来向队尾存入元素,如果队列满,则等待; take方法用来从队首取元素,如果队列为空,则等待; offer方法用来向队尾存入元素,如果队列满,则等待一定的时间,当时间期限达到时,如果还没有插入成功,则返回false;否则返回true; poll方法用来从队首取元素,如果队列空,则等待一定的时间,当时间期限达到时,如果取到,则返回null;否则返回取得的元素; 阻塞队列内部是通过Object.wait()、Object.notify()实现的,下面来看 使用阻塞队列实现的生产者-消费者的例子: public class Test { private static Integer count = 0; final BlockingQueue<Integer> bq = new ArrayBlockingQueue<Integer>(5);//容量为5的阻塞队列 public static void main(String[] args) { Test t = new Test(); new Thread(t.new Producer()).start(); new Thread(t.new Consumer()).start(); new Thread(t.new Consumer()).start(); new Thread(t.new Producer()).start(); } class Producer implements Runnable { @Override public void run() { for (int i = 0; i < 5; i++) { try { Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } try { bq.put(1); count++; System.out.println(Thread.currentThread().getName() + "produce:: " + count); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } class Consumer implements Runnable { @Override public void run() { for (int i = 0; i < 5; i++) { try { Thread.sleep(1000); } catch (InterruptedException e1) { e1.printStackTrace(); } try { bq.take(); count--; System.out.println(Thread.currentThread().getName()+ "consume:: " + count); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } } } } } 参考: http://blog.csdn.net/jiutianhe/article/details/18606295 http://www.cnblogs.com/dolphin0520/p/3932906.html 原文地址:http://blog.csdn.net/amazing7/article/details/51362782
栈(stack)又名堆栈,它是一种运算受限的线性表。其限制是仅允许在表的一端进行插入和删除运算。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。 栈的示意图: java中的Stack Stack 类表示后进先出(LIFO)的对象堆栈。它通过五个操作对类 Vector 进行了扩展 ,允许将向量视为堆栈。它提供了通常的 push 和 pop 操作,以及取堆栈顶点的 peek 方法、测试堆栈是否为空的 empty 方法、在堆栈中查找项并确定到堆栈顶距离的 search 方法。首次创建堆栈时,它不包含项。 Deque 接口及其实现提供了 LIFO 堆栈操作的更完整和更一致的 set,应该优先使用此 set,而非此类。例如: Deque<Integer> stack = new ArrayDeque<Integer>(); 成员方法: E push(E item) 把项压入堆栈顶部。 E pop() 移除堆栈顶部的对象,并作为此函数的值返回该对象。 E peek() 查看堆栈顶部的对象,但不从堆栈中移除它。 boolean empty() 测试堆栈是否为空。 int search(Object o) 返回对象在堆栈中的位置,以 1 为基数。 Stack继承于Vector,Vector本身是一个可增长的对象数组。 Stack并不要求其中保存数据的唯一性,当Stack中有多个相同的item时,调用search方法,只返回与查找对象equal并且离栈顶最近的item与栈顶间距离。 empty() 判断stack是否为空,就需要有一个变量来计算当前栈的长度,如果该变量为0,则表示该栈为空。 源码: public boolean empty() { return size() == 0; } size()方法在父类Vector中实现了,在Vector里面有一个变量elementCount来表示容器里元素的个数。如果为0,则表示容器空。 public synchronized int size() { return elementCount; } peek() 返回栈顶端的元素,如果栈为空的话,则要抛出异常。 public synchronized E peek() { int len = size(); if (len == 0) throw new EmptyStackException(); return elementAt(len - 1); } elementAt方法也是在Vector里面实现的,实际上是用一个elementData的Object数组来存储元素的。 public synchronized E elementAt(int index) { if (index >= elementCount) { throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount); } return elementData(index); } @SuppressWarnings("unchecked") E elementData(int index) { return (E) elementData[index]; } peek() 将栈顶的元素弹出来,如果栈里有元素,就取最顶端的那个,否则就要抛出异常。 public synchronized E pop() { E obj; int len = size(); obj = peek(); removeElementAt(len - 1); return obj; } 通过peek()取到顶端的元素之后,我们需要用removeElementAt()方法将最顶端的元素移除。 removeElementAt()方法定义在vector中。 public synchronized void removeElementAt(int index) { modCount++; if (index >= elementCount) { throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount); } else if (index < 0) { throw new ArrayIndexOutOfBoundsException(index); } int j = elementCount - index - 1; if (j > 0) { System.arraycopy(elementData, index + 1, elementData, index, j); } elementCount--; elementData[elementCount] = null; /* to let gc do its work */ } 这里用待删除元素的后面元素依次覆盖前面一个元素。这样,就相当于将数组的实际元素长度给缩短了。 push() 将数据入栈 public E push(E item) { addElement(item); return item; } 将要入栈的元素放到数组的末尾,再将数组长度加1就可以了。addElement()方法也在vector中(好父亲啊)。 public synchronized void addElement(E obj) { modCount++; ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = obj; } private void ensureCapacityHelper(int minCapacity) { // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); } private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copyOf(elementData, newCapacity); } private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; } search() 找到一个最靠近栈顶端的匹配元素,然后返回这个元素到栈顶的距离 public synchronized int search(Object o) { int i = lastIndexOf(o); if (i >= 0) { return size() - i; } return -1; } 对应在vector里面的实现也相对容易理解: public synchronized int lastIndexOf(Object o) { return lastIndexOf(o, elementCount-1); } public synchronized int lastIndexOf(Object o, int index) { if (index >= elementCount) throw new IndexOutOfBoundsException(index + " >= "+ elementCount); if (o == null) { for (int i = index; i >= 0; i--) if (elementData[i]==null) return i; } else { for (int i = index; i >= 0; i--) if (o.equals(elementData[i])) return i; } return -1; } lastIndexOf是从数组的末端往前遍历,如果找到这个对象就返回。如果到头了,还未找到就返回个-1。 栈和队列的区别 队列是FIFO的(先进先出),堆栈是FILO的(现今后出) 栈是限定只能在表的一端进行插入和删除操作的线性表。 队列是限定只能在表的一端进行插入和在另一端进行删除操作的线性表 栈只能从头部取数据,也就最先放入的需要遍历整个栈最后才能取出来,而且在遍历数据的时候还得为数据开辟临时空间; 队列基于地址指针进行遍历,而且可以从头或尾部开始遍历,但不能同时遍历,无需开辟临时空间,因为在遍历的过程中不影像数据结构,速度要快的多。
1.Hash表 哈希表(Hash table,也叫散列表),是根据key而直接进行访问的数据结构。也就是说,它通过把key映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。 以数据中每个元素的关键字K为自变量,通过散列函数H(k)计算出函数值,以该函数值作为一块连续存储空间的的单元地址,将该元素存储到函数值对应的单元中。 哈希表存储的是键值对,其查找的时间复杂度与元素数量多少无关,哈希表在查找元素时是通过计算哈希码值来定位元素的位置从而直接访问元素的,因此,哈希表查找的时间复杂度为O(1)。 2.哈希表的构造方法 2.1直接定址法 取关键字或者关键字的某个线性函数值作为哈希地址,即 H(Key)=Key或者H(Key)=a*Key+b(a,b为整数) 这种散列函数也叫做自身函数.如果H(Key)的哈希地址上已经有值了,那么就往下一个位置找,直到找到H(Key)的位置没有值了就把元素放进去. 此法仅适合于:地址集合的大小 等于 关键字集合的大小 2.2 数字分析法 分析一组数据,比如一组员工的出生年月,这时我们发现出生年月的前几位数字一般都相同,因此,出现冲突的概率就会很大,但是我们发现年月日的后几位表示月份和具体日期的数字差别很大,如果利用后面的几位数字来构造散列地址,则冲突的几率则会明显降低. 因此数字分析法就是找出数字的规律,尽可能利用这些数据来构造冲突几率较低的散列地址. 此法适于:能预先估计出全体关键字的每一位上各种数字出现的频度。 2.3 平方取中法 以关键字的平方值的中间几位作为存储地址(哈希地址)。求“关键字的平方值” 的目的是“扩大差别” ,同时平方值的中间各位又能受到整个关键字中各位的影响。 此法适于:关键字中的每一位都有某些数字重复出现频度很高的现象。 2.4 折叠法 将关键字分割成若干部分,然后取它们的叠加和为哈希地址。两种叠加处理的方法:移位叠加:将分 割后的几部分低位对齐相加;间界叠加:从一端沿分割界来回折叠,然后对齐相加。 此法适于:关键字的数字位数特别多。 2.5随机数法 设定哈希函数为:H(key) = Random(key)其中,Random 为伪随机函数 此法适于:对长度不等的关键字构造哈希函数。 2.6除留余数法 取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址.即 哈希函数为:H(key) = key MOD p ( p≤m ),其中, m为表长,p 为不大于 m 的素数。 3.哈希表冲突解决方法 哈希表处理冲突主要有开放寻址法、再散列法、链地址法(拉链法)和建立一个公共溢出区四种方法。 通过构造性能良好的哈希函数,可以减少冲突,但一般不可能完全避免冲突,因此解决冲突是哈希法的另一个关键问题。 “处理冲突” 的实际含义是:为产生冲突的关键字寻找下一个哈希地址。 3.1开放定址法 一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入。 3.1.1线性探测 冲突发生时,顺序查看表中下一单元,直到找出一个空单元或查遍全表。 公式: fi(key) = (f(key)+di) MOD m (di=1,2,3,......,m-1) 3.1.2二次探测法 冲突发生时,在表的左右进行跳跃式探测,双向寻找到可能的空位置。 公式: fi(key) = (f(key)+di) MOD m (di = 12, -12, 22, -22,……, q2, -q2, q <= m/2 3.1.3随机探测法 在冲突时,对于位移量 di 采用随机函数计算得到,我们称之为随机探测法。 公式: fi(key) = (f(key)+di) MOD m (di是一个随机数列) 线性探测再散列容易产生“二次聚集”,即在处理同义词的冲突时又导致非同义词的冲突。 线性探测再散列的优点是:只要哈希表不满,就一定能找到一个不冲突的哈希地址,而二次探测再散列和伪随机探测再散列则不一定。 3.2链地址法 将所有哈希地址相同的记录都链接在同一链表中。各链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况。 处理冲突简单,且无堆积现象,即非同义词决不会发生冲突,因此平均查找长度较短; 3.3再哈希法 这种方法是同时构造多个不同的哈希函数: Hi=RH1(key),i=1,2,3,…,n. 当哈希地址Hi=RH1(key)发生冲突时,再计算Hi=RH2(key)……,直到冲突不再产生。这种方法不易产生聚集,但增加了计算时间。 3.4建立公共溢出区 这种方法的基本思想是:将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表.(注意:在这个方法里面是把元素分开两个表来存储) 版权声明:请尊重个人劳动成果,转载注明出处,谢谢! http://blog.csdn.net/amazing7/article/details/51364667
选择排序 常用的选择排序方法有简单选择排序和堆排序,这里只说简单选择排序,堆排序后面再说。 简单选择排序 设所排序序列的记录个数为n,i 取 1,2,…,n-1 。 从所有n-i+1个记录(Ri,Ri+1,…,Rn)中找出排序码最小(或最大)的记录,与第i个记录交换。执行n-1趟 后就完成了记录序列的排序。 以排序数组{3,2,1,4,6,5}为例 简单选择排序性能 在简单选择排序过程中,所需移动记录的次数比较少。最好情况下,即待排序记录初始状态就已经是正序排列了,则不需要移动记录。 最坏情况下,即待排序记录初始状态是按第一条记录最大,之后的记录从小到大顺序排列,则需要移动记录的次数最多为3(n-1)。 简单选择排序过程中需要进行的比较次数与初始状态下待排序的记录序列的排列情况无关。 当i=1时,需进行n-1次比较;当i=2时,需进行n-2次比较;依次类推,共需要进行的比较次数是(n-1)+(n-2)+…+2+1=n(n-1)/2,即进行比较操作的时间复杂度为O(n^2),进行移动操作的时间复杂度为O(n)。 简单选择排序是不稳定排序。 简单选择排序Java实现 public static void main(String[] args) { int[] number = {3,1,2,8,4,5,24,12}; SimpleSort(number); for(int i = 0; i < number.length; i++) { System.out.print(number[i] + " "); } } public static void SimpleSort(int[] arr) { int length=arr.length; int temp; for(int i=0;i<length-1;i++){ int min=i; for(int j=i+1;j<length;j++){ //寻找最小的数 if(arr[j]<arr[min]){ min =j; } } if(min!=i){ temp = arr[min]; arr[min]=arr[i]; arr[i]=temp; } } } 文中图片来源 http://blog.csdn.net/shuilan0066/article/details/8659163 版权声明:请尊重个人劳动成果,转载注明出处,谢谢! http://blog.csdn.net/amazing7/article/details/51372976
概述 Android 也提供了几种方法用来保存数据,使得这些数据即使在程序结束以后依然不会丢失。这些方法有: 文本文件: 可以保存在应用程序自己的目录下,安装的每个app都会在/data/data/目录下创建个文件夹,名字和应用程序中AndroidManifest.xml文件中的package一样。 SDcard保存: Preferences保存: 这也是一种经常使用的数据存储方法,因为它们对于用户而言是透明的,并且从应用安装的时候就存在了。 Assets保存: 用来存储一些只读数据,Assets是指那些在assets目录下的文件,这些文件在你将你的应用编译打包之前就要存在,并且可以在应用程序运行的时候被访问到。 但有时候我们需要对保存的数据进行一些复杂的操作,或者数据量很大,超出了文本文件和Preference的性能能的范围,所以需要一些更加高效的方法来管理,从Android1.5开始,Android就自带SQLite数据库了。 SQLite它是一个独立的,无需服务进程,支持事务处理,可以使用SQL语言的数据库。 SQLite的特性 1、 ACID事务 ACID: 指数据库事务正确执行的四个基本要素的缩写。包含:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。一个支持事务(Transaction)的数据库,必需要具有这四种特性,否则在事务过程(Transaction processing)当中无法保证数据的正确性,交易过程极可能达不到交易方的要求。 2、 零配置 – 无需安装和管理配置 3、储存在单一磁盘文件中的一个完整的数据库 4、数据库文件可以在不同字节顺序的机器间自由的共享 5、支持数据库大小至2TB 6、 足够小, 大致3万行C代码, 250K 7、比一些流行的数据库在大部分普通数据库操作要快 8、简单, 轻松的API 9、 包含TCL绑定, 同时通过Wrapper支持其他语言的绑定 http://www.sqlite.org/tclsqlite.html 10、良好注释的源代码, 并且有着90%以上的测试覆盖率 11、 独立: 没有额外依赖 12、 Source完全的Open, 你可以用于任何用途, 包括出售它 13、支持多种开发语言,C,PHP,Perl,Java,ASP.NET,Python Android 中使用 SQLite Activites 可以通过 Content Provider 或者 Service 访问一个数据库。 创建数据库 Android 不自动提供数据库。在 Android 应用程序中使用 SQLite,必须自己创建数据库,然后创建表、索引,填充数据。Android 提供了 SQLiteOpenHelper 帮助你创建一个数据库,你只要继承 SQLiteOpenHelper 类根据开发应用程序的需要,封装创建和更新数据库使用的逻辑就行了。 SQLiteOpenHelper 的子类,至少需要实现三个方法: public class DatabaseHelper extends SQLiteOpenHelper { /** * @param context 上下文环境(例如,一个 Activity) * @param name 数据库名字 * @param factory 一个可选的游标工厂(通常是 Null) * @param version 数据库模型版本的整数 * * 会调用父类 SQLiteOpenHelper的构造函数 */ public DatabaseHelper(Context context, String name, CursorFactory factory, int version) { super(context, name, factory, version); } /** * 在数据库第一次创建的时候会调用这个方法 * *根据需要对传入的SQLiteDatabase 对象填充表和初始化数据。 */ @Override public void onCreate(SQLiteDatabase db) { } /** * 当数据库需要修改的时候(两个数据库版本不同),Android系统会主动的调用这个方法。 * 一般我们在这个方法里边删除数据库表,并建立新的数据库表. */ @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { //三个参数,一个 SQLiteDatabase 对象,一个旧的版本号和一个新的版本号 } @Override public void onOpen(SQLiteDatabase db) { // 每次成功打开数据库后首先被执行 super.onOpen(db); } } 继承SQLiteOpenHelper之后就拥有了以下两个方法: getReadableDatabase() 创建或者打开一个查询数据库 getWritableDatabase() 创建或者打开一个可写数据库 DatabaseHelper database = new DatabaseHelper(context);//传入一个上下文参数 SQLiteDatabase db = null; db = database.getWritableDatabase(); 上面这段代码会返回一个 SQLiteDatabase 类的实例,使用这个对象,你就可以查询或者修改数据库。 SQLiteDatabase类为我们提供了很多种方法,而较常用的方法如下: (int) delete(String table,String whereClause,String[] whereArgs) 删除数据行 (long) insert(String table,String nullColumnHack,ContentValues values) 添加数据行 (int) update(String table, ContentValues values, String whereClause, String[] whereArgs) 更新数据行 (void) execSQL(String sql) 执行一个SQL语句,可以是一个select或其他的sql语句 (void) close() 关闭数据库 (Cursor) query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit) 查询指定的数据表返回一个带游标的数据集。 各参数说明: table:表名称 colums:列名称数组 selection:条件子句,相当于where selectionArgs:条件语句的参数数组 groupBy:分组 having:分组条件 orderBy:排序类 limit:分页查询的限制 Cursor:返回值,相当于结果集ResultSet (Cursor) rawQuery(String sql, String[] selectionArgs) 运行一个预置的SQL语句,返回带游标的数据集(与上面的语句最大的区别就是防止SQL注入) 当你完成了对数据库的操作(例如你的 Activity 已经关闭),需要调用 SQLiteDatabase 的 Close() 方法来释放掉数据库连接。 创建表和索引 为了创建表和索引,需要调用 SQLiteDatabase 的 execSQL() 方法来执行 DDL 语句。如果没有异常,这个方法没有返回值。 例如,你可以执行如下代码: db.execSQL("CREATE TABLE user(_id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT, password TEXT);"); 这条语句会创建一个名为 user的表,表有一个列名为 _id,并且是主键,这列的值是会自动增长的整数。另外还有两列:username( 字符 ) 和 password( 字符 )。 SQLite 会自动为主键列创建索引。 通常情况下,第一次创建数据库时创建了表和索引。要 删除表和索引,需要使用 execSQL() 方法调用 DROP INDEX 和 DROP TABLE 语句。 添加数据 有两种方法可以给表添加数据。 ①可以使用 execSQL() 方法执行 INSERT, UPDATE, DELETE 等语句来更新表的数据。execSQL() 方法适用于所有不返回结果的 SQL 语句。例如: String sql = "insert into user(username,password) values ('finch','123456');//插入操作的SQL语句 db.execSQL(sql);//执行SQL语句 ②使用 SQLiteDatabase 对象的 insert()。 ContentValues cv = new ContentValues(); cv.put("username","finch");//添加用户名 cv.put("password","123456"); //添加密码 db.insert("user",null,cv);//执行插入操作 更新数据(修改) ①使用SQLiteDatabase 对象的 update()方法。 ContentValues cv = new ContentValues(); cv.put("password","654321");//添加要更改的字段及内容 String whereClause = "username=?";//修改条件 String[] whereArgs = {"finch"};//修改条件的参数 db.update("user",cv,whereClause,whereArgs);//执行修改 该方法有四个参数: 表名; 列名和值的 ContentValues 对象; 可选的 WHERE 条件; 可选的填充 WHERE 语句的字符串,这些字符串会替换 WHERE 条件中的“?”标记,update() 根据条件,更新指定列的值. ②使用execSQL方式的实现 String sql = "update [user] set password = '654321' where username="finch";//修改的SQL语句 db.execSQL(sql);//执行修改 删除数据 ①使用SQLiteDatabase 对象的delete()方法。 String whereClause = "username=?";//删除的条件 String[] whereArgs = {"finch"};//删除的条件参数 db.delete("user",whereClause,whereArgs);//执行删除 ②使用execSQL方式的实现 String sql = "delete from user where username="finch";//删除操作的SQL语句 db.execSQL(sql);//执行删除操作 查询数据 ①使用 rawQuery() 直接调用 SELECT 语句 Cursor c = db.rawQuery("select * from user where username=?",new Stirng[]{"finch"}); if(cursor.moveToFirst()) { String password = c.getString(c.getColumnIndex("password")); } 返回值是一个 cursor 对象,这个对象的方法可以迭代查询结果。 如果查询是动态的,使用这个方法就会非常复杂。例如,当你需要查询的列在程序编译的时候不能确定,这时候使用 query() 方法会方便很多。 ②通过query实现查询 query() 方法用 SELECT 语句段构建查询。 SELECT 语句内容作为 query() 方法的参数,比如:要查询的表名,要获取的字段名,WHERE 条件,包含可选的位置参数,去替代 WHERE 条件中位置参数的值,GROUP BY 条件,HAVING 条件。 除了表名,其他参数可以是 null。所以代码可写成: Cursor c = db.query("user",null,null,null,null,null,null);//查询并获得游标 if(c.moveToFirst()){//判断游标是否为空 for(int i=0;i<c.getCount();i++){ c.move(i);//移动到指定记录 String username = c.getString(c.getColumnIndex("username"); String password = c.getString(c.getColumnIndex("password")); } } 使用游标 不管你如何执行查询,都会返回一个 Cursor,这是 Android 的 SQLite 数据库游标,使用游标,你可以: 通过使用 getCount() 方法得到结果集中有多少记录; 通过 moveToFirst(), moveToNext(), 和 isAfterLast() 方法遍历所有记录; 通过 getColumnNames() 得到字段名; 通过 getColumnIndex() 转换成字段号; 通过 getString(),getInt() 等方法得到给定字段当前记录的值; 通过 requery() 方法重新执行查询得到游标; 通过 close() 方法释放游标资源; 例如,下面代码遍历 user表: Cursor result=db.rawQuery("SELECT _id, username, password FROM user"); result.moveToFirst(); while (!result.isAfterLast()) { int id=result.getInt(0); String name=result.getString(1); String password =result.getString(2); // do something useful with these result.moveToNext(); } result.close(); 参考:http://www.ibm.com/developerworks/cn/opensource/os-cn-sqlite/ 版权声明:请尊重个人劳动成果,转载注明出处,谢谢! http://blog.csdn.net/amazing7/article/details/51375012
概述 希尔排序法(缩小增量法) 属于插入类排序,是将整个无序列分割成若干小的子序列分别进行插入排序的方法。 把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。 希尔排序是基于插入排序的以下两点性质而提出改进方法的: 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率。 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位。 实现过程 先取一个正整数d1小于n,把所有序号相隔d1的数组元素放一组,组内进行直接插入排序;然后取d2小于d1,重复上述分组和排序操作;直至di=1,即所有记录放进一个组中排序为止。 例如,假设有这样一组数[ 13 14 94 33 82 25 59 94 65 23 45 27 73 25 39 10 ],如果我们以步长为5开始进行排序,我们可以通过将这列表放在有5列的表中来更好地描述算法,这样他们就应该看起来是这样: 13 14 94 33 82 25 59 94 65 23 45 27 73 25 39 10 然后我们对每列进行排序: 10 14 73 25 23 13 27 94 33 39 25 59 94 65 82 45 将上述四行数字,依序接在一起时我们得到:[ 10 14 73 25 23 13 27 94 33 39 25 59 94 65 82 45 ].这时10已经移至正确位置了,然后再以3为步长进行排序: 10 14 73 25 23 13 27 94 33 39 25 59 94 65 82 45 排序之后变为: 10 14 13 25 23 33 27 25 59 39 65 73 45 94 82 94 最后以1步长进行排序(此时就是简单的插入排序了)。 实现效率 希尔排序是一个不稳定的排序,其时间复杂度受步长(增量)的影响。 空间复杂度: O(1) 时间复杂度: 平均 O(n^1.3) 最好 O(n) 最坏 O(n^2) Java实现 public static void shellSort(int[] a) { int gap = 1, i, j, len = a.length; int temp;//插入排序交换值的暂存 //确定初始步长 while (gap < len / 3){ gap = gap * 3 + 1; } for (; gap > 0; gap /= 3){//循环遍历步长,最后必为1 for (i = gap; i < len; i++) {//每一列依次向前做插入排序 temp = a[i]; //每一列中在a[i]上面且比a[i]大的元素依次向下移动 for (j = i - gap; j >= 0 && a[j] > temp; j -= gap){ a[j + gap] = a[j]; } //a[i]填补空白,完成一列中的依次插入排序 a[j + gap] = temp; } } } 版权声明:请尊重个人劳动成果,转载注明出处,谢谢! http://blog.csdn.net/amazing7/article/details/51386145
IntentService定义 IntentService继承与Service,用来处理异步请求。客户端可以通过startService(Intent)方法传递请求给IntentService。IntentService在onCreate()函数中通过HandlerThread单独开启一个线程来依次处理所有Intent请求对象所对应的任务。 这样以免事务处理阻塞主线程(ANR)。执行完所一个Intent请求对象所对应的工作之后,如果没有新的Intent请求达到,则**自动停止**Service;否则执行下一个Intent请求所对应的任务。 IntentService在处理事务时,还是采用的Handler方式,创建一个名叫ServiceHandler的内部Handler,并把它直接绑定到HandlerThread所对应的子线程。 ServiceHandler把处理一个intent所对应的事务都封装到叫做onHandleIntent的虚函数;因此我们直接实现虚函数onHandleIntent,再在里面根据Intent的不同进行不同的事务处理就可以了。 另外,IntentService默认实现了Onbind()方法,返回值为null。 使用IntentService需要实现的两个方法: 构造函数 IntentService的构造函数一定是参数为空的构造函数,然后再在其中调用super(“name”)这种形式的构造函数。因为Service的实例化是系统来完成的,而且系统是用参数为空的构造函数来实例化Service的 实现虚函数onHandleIntent 在里面根据Intent的不同进行不同的事务处理。 好处:处理异步请求的时候可以减少写代码的工作量,比较轻松地实现项目的需求。 IntentService与Service的区别 Service不是独立的进程,也不是独立的线程,它是依赖于应用程序的主线程的,不建议在Service中编写耗时的逻辑和操作,否则会引起ANR。 IntentService 它创建了一个独立的工作线程来处理所有的通过onStartCommand()传递给服务的intents(把intent插入到工作队列中)。通过工作队列把intent逐个发送给onHandleIntent()。 不需要主动调用stopSelft()来结束服务。因为,在所有的intent被处理完后,系统会自动关闭服务。 默认实现的onBind()返回null。 IntentService实例介绍 首先是myIntentService.Java public class myIntentService extends IntentService { //------------------必须实现----------------------------- public myIntentService() { super("myIntentService"); // 注意构造函数参数为空,这个字符串就是worker thread的名字 } @Override protected void onHandleIntent(Intent intent) { //根据Intent的不同进行不同的事务处理 String taskName = intent.getExtras().getString("taskName"); switch (taskName) { case "task1": Log.i("myIntentService", "do task1"); break; case "task2": Log.i("myIntentService", "do task2"); break; default: break; } } //--------------------用于打印生命周期-------------------- @Override public void onCreate() { Log.i("myIntentService", "onCreate"); super.onCreate(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.i("myIntentService", "onStartCommand"); return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { Log.i("myIntentService", "onDestroy"); super.onDestroy(); } } 然后记得在Manifest.xml中注册服务 <service android:name=".myIntentService"> <intent-filter > <action android:name="cn.scu.finch"/> </intent-filter> </service> 最后在Activity中开启服务 public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); //同一服务只会开启一个worker thread,在onHandleIntent函数里依次处理intent请求。 Intent i = new Intent("cn.scu.finch"); Bundle bundle = new Bundle(); bundle.putString("taskName", "task1"); i.putExtras(bundle); startService(i); Intent i2 = new Intent("cn.scu.finch"); Bundle bundle2 = new Bundle(); bundle2.putString("taskName", "task2"); i2.putExtras(bundle2); startService(i2); startService(i); //多次启动 } } 运行结果: IntentService在onCreate()函数中通过HandlerThread单独开启一个线程来依次处理所有Intent请求对象所对应的任务。 通过onStartCommand()传递给服务intent被依次插入到工作队列中。工作队列又把intent逐个发送给onHandleIntent()。 注意: 它只有一个工作线程,名字就是构造函数的那个字符串,也就是“myIntentService”,我们知道多次开启service,只会调用一次onCreate方法(创建一个工作线程),多次onStartCommand方法(用于传入intent通过工作队列再发给onHandleIntent函数做处理)。 版权声明:请尊重个人劳动成果,转载注明出处,谢谢! http://blog.csdn.net/amazing7/article/details/51394846
版权声明:请尊重个人劳动成果,转载注明出处,谢谢!http://blog.csdn.net/amazing7/article/details/51313851 目录(?)[+] ThreadLocal是什么? 线程局部变量,访问某个变量的每个线程都有自己的局部变量,它独立于变量的初始化副本。 它的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。从线程的角度看,就好像每一个线程都完全拥有该变量。 ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。每个线程都保持对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。 ThreadLocal的接口方法 protected T initialValue() :返回此线程局部变量的初始值 线程第一次使用 get() 方法访问变量时将调用此方法,但如果线程之前调用了 set(T) 方法,则不会对该线程再调用 initialValue 方法。通常,此方法对每个线程最多调用一次,但如果在调用 get() 后又调用了 remove(),则可能再次调用此方法。 该实现返回 null;如果程序员希望线程局部变量具有 null 以外的值,则必须为 ThreadLocal 创建子类,并重写此方法。通常将使用匿名内部类完成此操作。 public T get() :返回此线程局部变量的当前线程的值 如果变量没有用于当前线程的值,则先将其初始化为调用 initialValue() 方法返回的值。 public void set(T value) :将此线程局部变量的当前线程副本中的值设置为指定值 大部分子类不需要重写此方法,它们只依靠 initialValue() 方法来设置线程局部变量的值。 public void remove() :移除此线程局部变量当前线程的值 如果此线程局部变量随后被当前线程 读取,且这期间当前线程没有 设置其值,则将调用其 initialValue() 方法重新初始化其值。这将导致在当前线程多次调用 initialValue 方法。 如果希望线程局部变量初始化其它值,那么需要自己实现ThreadLocal的子类并重写该方法,通常使用一个内部类对ThreadLocal进行实例化。 比如下面的例子,SerialNum类为每一个类分配一个序号: public class SerialNum { // The next serial number to be assigned private static int nextSerialNum = 0; private static ThreadLocal serialNum = new ThreadLocal() { protected synchronized Object initialValue() { return new Integer(nextSerialNum++); } } public static int get() { return ((Integer) (serialNum.get())).intValue(); } } ThreadLocal的内部实现 先看看ThreadLocal类的部分源码: public class ThreadLocal<T> { //省略... 126 protected T initialValue() { 127 return null; 128 } 159 public T get() { 160 Thread t = Thread.currentThread(); //当前线程为key存入ThreadLocalMap 中 161 ThreadLocalMap map = getMap(t); 162 if (map != null) { 163 ThreadLocalMap.Entry e = map.getEntry(this); 164 if (e != null) { 165 @SuppressWarnings("unchecked") 166 T result = (T)e.value; 167 return result; 168 } 169 } 170 return setInitialValue(); 171 } 199 public void set(T value) { 200 Thread t = Thread.currentThread(); 201 ThreadLocalMap map = getMap(t); 202 if (map != null) 203 map.set(this, value); 204 else 205 createMap(t, value); 206 } 219 public void remove() { 220 ThreadLocalMap m = getMap(Thread.currentThread()); 221 if (m != null) 222 m.remove(this); 223 } } 我们可以很明显的看出ThreadLocal把线程和线程局部变量存在ThreadLocalMap中,而ThreadLocalMap是ThreadLocal的静态类部类,我们来看看ThreadLocalMap 的部分源码: 308 static class Entry extends WeakReference<ThreadLocal<?>> { 309 310 Object value; 311 312 Entry(ThreadLocal<?> k, Object v) { 313 super(k); 314 value = v; 315 } 316 } 这个Map的key是ThreadLocal对象的弱引用,当要抛弃掉ThreadLocal对象时,垃圾收集器会忽略这个key的引用而清理掉ThreadLocal对象 。 那么到底是ThreadLocal还是Thread持有ThreadLocalMap对象的引用呢? 我们在Thread源码中发现: 180 /* ThreadLocal values pertaining to this thread. This map is maintained 181 * by the ThreadLocal class. */ 182 ThreadLocal.ThreadLocalMap threadLocals = null; ThreadLocalMap变量属于Thread的内部属性,不同的Thread拥有完全不同的ThreadLocalMap变量. Thread中的ThreadLocalMap变量的值是在ThreadLocal对象进行set或者get操作时创建的. 在创建ThreadLocalMap之前,会首先检查当前Thread中的ThreadLocalMap变量是否已经存在,如果不存在则创建一个;如果已经存在,则使用当前Thread已创建的ThreadLocalMap. ThreadLocal如何做到线程安全 从上面的分析我们可以得出: 因为每个Thread在进行对象访问时,访问的都是各自线程自己的ThreadLocalMap,所以保证了Thread与Thread之间的数据访问隔离。 不同的ThreadLocal实例操作同一Thread时,ThreadLocalMap在存储时采用当前ThreadLocal的实例作为key来保证数据访问隔离(上面源码Entry处可以看出)。 举个例子说明: public class Test { static ThreadLocal<Integer> local=new ThreadLocal<Integer>(){ protected synchronized Integer initialValue(){ return 0; } }; public static void main( String args[]){ testRun t = new testRun(); new Thread(t).start(); new Thread(t).start(); } public static class testRun implements Runnable{ @Override public void run() { // TODO Auto-generated method stub for(int i=5 ;i>0;i--){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"::"+local.get()); int localValue=local.get(); local.set(++localValue); } } } } 输出结果为: Thread-0::0 Thread-1::0 Thread-0::1 Thread-1::1 Thread-1::2 Thread-0::2 Thread-1::3 Thread-0::3 Thread-0::4 Thread-1::4 TheadLocal模式与同步机制的区别 同步机制采用了“以时间换空间”的方式,提供一份变量,让不同的线程排队访问.而ThreadLocal采用了“以空间换时间”的方式,为每一个线程都提供一份变量的副本,从而实现同时访问而互不影响。 Java中的synchronized是一个保留字,它依靠JVM的锁机制来实现临界区的函数或者变量的访问中的原子性.在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量.此时,被用作“锁机制”的变量是多个线程共享的; 而ThreadLocal会为每一个线程维护一个和该线程绑定的变量的副本,从而隔离了多个线程的数据,每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。 同步机制是为了同步多个线程对相同资源的并发访问,是为了多个线程之间进行通信的有效方式。 而ThreadLocal是隔离多个线程的数据共享,从根本上就不在多个线程之间共享资源(变量),这样当然不需要对多个线程进行同步了。 所以,如果你需要进行多个线程之间进行通信,则使用同步机制。如果需要隔离多个线程之间的共享冲突,可以使用ThreadLocal。
版权声明:请尊重个人劳动成果,转载注明出处,谢谢!http://blog.csdn.net/amazing7/article/details/51303289 目录(?)[+] 在Android应用开发过程中,固定的一些控件和属性可能满足不了开发的需求,所以在一些特殊情况下,我们需要自定义控件与属性。 一、实现步骤 1. 继承View类或其子类 2. 复写view中的一些函数 3.为自定义View类增加属性(两种方式) 4.绘制控件(导入布局) 5.响应用户事件 6.定义回调函数(根据自己需求来选择) 二、哪些方法需要被重写 onDraw() view中onDraw()是个空函数,也就是说具体的视图都要覆写该函数来实现自己的绘制。对于ViewGroup则不需要实现该函数,因为作为容器是“没有内容“的(但必须实现dispatchDraw()函数,告诉子view绘制自己)。 onLayout() 主要是为viewGroup类型布局子视图用的,在View中这个函数为空函数。 onMeasure() 用于计算视图大小(即长和宽)的方式,并通过setMeasuredDimension(width, height)保存计算结果。 onTouchEvent 定义触屏事件来响应用户操作。 还有一些不常用的方法: onKeyDown 当按下某个键盘时 onKeyUp 当松开某个键盘时 onTrackballEvent 当发生轨迹球事件时 onSizeChange() 当该组件的大小被改变时 onFinishInflate() 回调方法,当应用从XML加载该组件并用它构建界面之后调用的方法 onWindowFocusChanged(boolean) 当该组件得到、失去焦点时 onAttachedToWindow() 当把该组件放入到某个窗口时 onDetachedFromWindow() 当把该组件从某个窗口上分离时触发的方法 onWindowVisibilityChanged(int): 当包含该组件的窗口的可见性发生改变时触发的方法 三.自定义控件的三种方式 1. 继承已有的控件 当要实现的控件和已有的控件在很多方面比较类似, 通过对已有控件的扩展来满足要求。 2. 继承一个布局文件 一般用于自定义组合控件,在构造函数中通过inflater和addView()方法加载自定义控件的布局文件形成图形界面(不需要onDraw方法)。 3.继承view 通过onDraw方法来绘制出组件界面。 四.自定义属性的两种方法 1.在布局文件中直接加入属性,在构造函数中去获得。 布局文件: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <com.example.demo.myView android:layout_width="wrap_content" android:layout_height="wrap_content" Text="@string/hello_world" /> </RelativeLayout> 获取属性值: public myView(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub int textId = attrs.getAttributeResourceValue(null, "Text", 0); String text = context.getResources().getText(textId).toString(); } 2.在res/values/ 下建立一个attrs.xml 来声明自定义view的属性。 可以定义的属性有: <declare-styleable name = "名称"> //参考某一资源ID (name可以随便命名) <attr name = "background" format = "reference" /> //颜色值 <attr name = "textColor" format = "color" /> //布尔值 <attr name = "focusable" format = "boolean" /> //尺寸值 <attr name = "layout_width" format = "dimension" /> //浮点值 <attr name = "fromAlpha" format = "float" /> //整型值 <attr name = "frameDuration" format="integer" /> //字符串 <attr name = "text" format = "string" /> //百分数 <attr name = "pivotX" format = "fraction" /> //枚举值 <attr name="orientation"> <enum name="horizontal" value="0" /> <enum name="vertical" value="1" /> </attr> //位或运算 <attr name="windowSoftInputMode"> <flag name = "stateUnspecified" value = "0" /> <flag name = "stateUnchanged" value = "1" /> </attr> //多类型 <attr name = "background" format = "reference|color" /> </declare-styleable> attrs.xml进行属性声明 <?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="myView"> <attr name="text" format="string"/> <attr name="textColor" format="color"/> </declare-styleable> </resources> 添加到布局文件 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:myview="http://schemas.android.com/apk/com.example.demo" > <com.example.demo.myView android:layout_width="wrap_content" android:layout_height="wrap_content" myview:text = "test" myview:textColor ="#ff0000" /> </RelativeLayout> 这里注意命名空间: xmlns:前缀=”http://schemas.android.com/apk/res/包名(或res-auto)”, 前缀:TextColor 使用属性。 在构造函数中获取属性值 public myView(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.myView); String text = a.getString(R.styleable.myView_text); int textColor = a.getColor(R.styleable.myView_textColor, Color.WHITE); a.recycle(); } 或者: public myView(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.myView); int n = a.getIndexCount(); for(int i=0;i<n;i++){ int attr = a.getIndex(i); switch (attr) { case R.styleable.myView_text: break; case R.styleable.myView_textColor: break; } } a.recycle(); } 五. 自定义随手指移动的小球(小例子) 实现上面的效果我们大致需要分成这几步 在res/values/ 下建立一个attrs.xml 来声明自定义view的属性 一个继承View并复写部分函数的自定义view的类 一个展示自定义view 的容器界面 1.自定义view命名为myView,它有一个属性值,格式为color、 <?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="myView"> <attr name="TextColor" format="color"/> </declare-styleable> </resources> 2.在构造函数获取获得view的属性配置和复写onDraw和onTouchEvent函数实现绘制界面和用户事件响应。 public class myView extends View{ //定义画笔和初始位置 Paint p = new Paint(); public float currentX = 50; public float currentY = 50; public int textColor; public myView(Context context, AttributeSet attrs) { super(context, attrs); //获取资源文件里面的属性,由于这里只有一个属性值,不用遍历数组,直接通过R文件拿出color值 //把属性放在资源文件里,方便设置和复用 TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.myView); textColor = array.getColor(R.styleable.myView_TextColor,Color.BLACK); array.recycle(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //画一个蓝色的圆形 p.setColor(Color.BLUE); canvas.drawCircle(currentX,currentY,30,p); //设置文字和颜色,这里的颜色是资源文件values里面的值 p.setColor(textColor); canvas.drawText("BY finch",currentX-30,currentY+50,p); } @Override public boolean onTouchEvent(MotionEvent event) { currentX = event.getX(); currentY = event.getY(); invalidate();//重新绘制图形 return true; } } 这里通过不断的更新当前位置坐标和重新绘制图形实现效果,要注意的是使用TypedArray后一定要记得recycle(). 否则会对下次调用产生影响。 3.把myView加入到activity_main.xml布局里面 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:myview="http://schemas.android.com/apk/res-auto" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="finch.scu.cn.myview.MainActivity"> <finch.scu.cn.myview.myView android:layout_width="match_parent" android:layout_height="match_parent" myview:TextColor="#ff0000" /> </RelativeLayout> 4.最后是MainActivity public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } } 具体的view要根据具体的需求来,比如我们要侧滑删除的listview我们可以继承listview,监听侧滑事件,显示删除按钮实现功能。
版权声明:请尊重个人劳动成果,转载注明出处,谢谢!http://blog.csdn.net/amazing7/article/details/51305911 目录(?)[+] 什么是服务? Service是一个应用程序组件,它能够在后台执行一些耗时较长的操作,并且不提供用户界面。服务能被其它应用程序的组件启动,即使用户切换到另外的应用时还能保持后台运行。此外,应用程序组件还能与服务绑定,并与服务进行交互,甚至能进行进程间通信(IPC)。 比如,服务可以处理网络传输、音乐播放、执行文件I/O、或者与content provider进行交互,所有这些都是后台进行的。 Service 与 Thread 的区别 服务仅仅是一个组件,即使用户不再与你的应用程序发生交互,它仍然能在后台运行。因此,应该只在需要时才创建一个服务。 如果你需要在主线程之外执行一些工作,但仅当用户与你的应用程序交互时才会用到,那你应该创建一个新的线程而不是创建服务。 比如,如果你需要播放一些音乐,但只是当你的activity在运行时才需要播放,你可以在onCreate()中创建一个线程,在onStart()中开始运行,然后在onStop()中终止运行。还可以考虑使用AsyncTask或HandlerThread来取代传统的Thread类。 由于无法在不同的 Activity 中对同一 Thread 进行控制,这个时候就要考虑用服务实现。如果你使用了服务,它默认就运行于应用程序的主线程中。因此,如果服务执行密集计算或者阻塞操作,你仍然应该在服务中创建一个新的线程来完成(避免ANR)。 服务的分类 按运行分类 前台服务 前台服务是指那些经常会被用户关注的服务,因此内存过低时它不会成为被杀的对象。 前台服务必须提供一个状态栏通知,并会置于“正在进行的”(“Ongoing”)组之下。这意味着只有在服务被终止或从前台移除之后,此通知才能被解除。 例如,用服务来播放音乐的播放器就应该运行在前台,因为用户会清楚地知晓它的运行情况。 状态栏通知可能会标明当前播放的歌曲,并允许用户启动一个activity来与播放器进行交互。 要把你的服务请求为前台运行,可以调用startForeground()方法。此方法有两个参数:唯一标识通知的整数值、状态栏通知Notification对象。例如: Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text),System.currentTimeMillis()); Intent notificationIntent = new Intent(this,ExampleActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0); notification.setLatestEventInfo(this, getText(R.string.notification_title), getText(R.string.notification_message), pendingIntent); startForeground(ONGOING_NOTIFICATION, notification); 要从前台移除服务,请调用stopForeground()方法,这个方法接受个布尔参数,表示是否同时移除状态栏通知。此方法不会终止服务。不过,如果服务在前台运行时被你终止了,那么通知也会同时被移除。 后台服务 按使用分类 本地服务 用于应用程序内部,实现一些耗时任务,并不占用应用程序比如Activity所属线程,而是单开线程后台执行。 调用Context.startService()启动,调用Context.stopService()结束。在内部可以调用Service.stopSelf() 或 Service.stopSelfResult()来自己停止。 远程服务 用于Android系统内部的应用程序之间,可被其他应用程序复用,比如天气预报服务,其他应用程序不需要再写这样的服务,调用已有的即可。可以定义接口并把接口暴露出来,以便其他应用进行操作。客户端建立到服务对象的连接,并通过那个连接来调用服务。调用Context.bindService()方法建立连接,并启动,以调用 Context.unbindService()关闭连接。多个客户端可以绑定至同一个服务。如果服务此时还没有加载,bindService()会先加载它。 Service生命周期 Service生命周期方法: public class ExampleService extends Service { int mStartMode; // 标识服务被杀死后的处理方式 IBinder mBinder; // 用于客户端绑定的接口 boolean mAllowRebind; // 标识是否使用onRebind @Override public void onCreate() { // 服务正被创建 } @Override public int onStartCommand(Intent intent, int flags, int startId) { // 服务正在启动,由startService()调用引发 return mStartMode; } @Override public IBinder onBind(Intent intent) { // 客户端用bindService()绑定服务 return mBinder; } @Override public boolean onUnbind(Intent intent) { // 所有的客户端都用unbindService()解除了绑定 return mAllowRebind; } @Override public void onRebind(Intent intent) { // 某客户端正用bindService()绑定到服务, // 而onUnbind()已经被调用过了 } @Override public void onDestroy() { // 服务用不上了,将被销毁 } } 请注意onStartCommand()方法必须返回一个整数。这个整数是描述系统在杀死服务之后应该如何继续运行。onStartCommand()的返回值必须是以下常量之一: START_NOT_STICKY 如果系统在onStartCommand()返回后杀死了服务,则不会重建服务了,除非还存在未发送的intent。 当服务不再是必需的,并且应用程序能够简单地重启那些未完成的工作时,这是避免服务运行的最安全的选项。 START_STICKY 如果系统在onStartCommand()返回后杀死了服务,则将重建服务并调用onStartCommand(),但不会再次送入上一个intent, 而是用null intent来调用onStartCommand() 。除非还有启动服务的intent未发送完,那么这些剩下的intent会继续发送。 这适用于媒体播放器(或类似服务),它们不执行命令,但需要一直运行并随时待命。 START_REDELIVER_INTENT 如果系统在onStartCommand()返回后杀死了服务,则将重建服务并用上一个已送过的intent调用onStartCommand()。任何未发送完的intent也都会依次送入。这适用于那些需要立即恢复工作的活跃服务,比如下载文件。 服务的生命周期与activity的非常类似。不过,更重要的是你需密切关注服务的创建和销毁环节,因为后台运行的服务是不会引起用户注意的。 服务的生命周期——从创建到销毁——可以有两种路径: 一个started服务 这类服务由其它组件调用startService()来创建。然后保持运行,且必须通过调用stopSelf()自行终止。其它组件也可通过调用stopService() 终止这类服务。服务终止后,系统会把它销毁。 如果一个Service被startService 方法多次启动,那么onCreate方法只会调用一次,onStart将会被调用多次(对应调用startService的次数),并且系统只会创建Service的一个实例(因此你应该知道只需要一次stopService调用)。该Service将会一直在后台运行,而不管对应程序的Activity是否在运行,直到被调用stopService,或自身的stopSelf方法。当然如果系统资源不足,android系统也可能结束服务。 一个bound服务 服务由其它组件(客户端)调用bindService()来创建。然后客户端通过一个IBinder接口与服务进行通信。客户端可以通过调用unbindService()来关闭联接。多个客户端可以绑定到同一个服务上,当所有的客户端都解除绑定后,系统会销毁服务。(服务不需要自行终止。) 如果一个Service被某个Activity 调用 Context.bindService 方法绑定启动,不管调用 bindService 调用几次,onCreate方法都只会调用一次,同时onStart方法始终不会被调用。当连接建立之后,Service将会一直运行,除非调用Context.unbindService 断开连接或者之前调用bindService 的 Context 不存在了(如Activity被finish的时候),系统将会自动停止Service,对应onDestroy将被调用。 这两条路径并不是完全隔离的。也就是说,你可以绑定到一个已经用startService()启动的服务上。例如,一个后台音乐服务可以通过调用startService()来启动,传入一个指明所需播放音乐的 Intent。 之后,用户也许需要用播放器进行一些控制,或者需要查看当前歌曲的信息,这时一个activity可以通过调用bindService()与此服务绑定。在类似这种情况下,stopService()或stopSelf()不会真的终止服务,除非所有的客户端都解除了绑定。 当在旋转手机屏幕的时候,当手机屏幕在“横”“竖”变换时,此时如果你的 Activity 如果会自动旋转的话,旋转其实是 Activity 的重新创建,因此旋转之前的使用 bindService 建立的连接便会断开(Context 不存在了)。 在manifest中声明服务 无论是什么类型的服务都必须在manifest中申明,格式如下: <manifest ... > ... <application ... > <service android:name=".ExampleService" /> ... </application> </manifest> Service 元素的属性有: android:name ————- 服务类名 android:label ————– 服务的名字,如果此项不设置,那么默认显示的服务名则为类名 android:icon ————– 服务的图标 android:permission ——- 申明此服务的权限,这意味着只有提供了该权限的应用才能控制或连接此服务 android:process ———- 表示该服务是否运行在另外一个进程,如果设置了此项,那么将会在包名后面加上这段字符串表示另一进程的名字 android:enabled ———- 如果此项设置为 true,那么 Service 将会默认被系统启动,不设置默认此项为 false android:exported ——— 表示该服务是否能够被其他应用程序所控制或连接,不设置默认此项为 false android:name是唯一必需的属性——它定义了服务的类名。与activity一样,服务可以定义intent过滤器,使得其它组件能用隐式intent来调用服务。如果你想让服务只能内部使用(其它应用程序无法调用),那么就不必(也不应该)提供任何intent过滤器。 此外,如果包含了android:exported属性并且设置为”false”, 就可以确保该服务是你应用程序的私有服务。即使服务提供了intent过滤器,本属性依然生效。 startService 启动服务 从activity或其它应用程序组件中可以启动一个服务,调用startService()并传入一个Intent(指定所需启动的服务)即可。 Intent intent = new Intent(this, MyService.class); startService(intent); 服务类: public class MyService extends Service { /** * onBind 是 Service 的虚方法,因此我们不得不实现它。 * 返回 null,表示客服端不能建立到此服务的连接。 */ @Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub return null; } @Override public void onCreate() { super.onCreate(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { //接受传递过来的intent的数据 return START_STICKY; }; @Override public void onDestroy() { super.onDestroy(); } } 一个started服务必须自行管理生命周期。也就是说,系统不会终止或销毁这类服务,除非必须恢复系统内存并且服务返回后一直维持运行。 因此,服务必须通过调用stopSelf()自行终止,或者其它组件可通过调用stopService()来终止它。 bindService 启动服务 当应用程序中的activity或其它组件需要与服务进行交互,或者应用程序的某些功能需要暴露给其它应用程序时,你应该创建一个bound服务,并通过进程间通信(IPC)来完成。 方法如下: Intent intent=new Intent(this,BindService.class); bindService(intent, ServiceConnection conn, int flags) 注意bindService是Context中的方法,当没有Context时传入即可。 在进行服务绑定的时,其flags有: Context.BIND_AUTO_CREATE 表示收到绑定请求的时候,如果服务尚未创建,则即刻创建,在系统内存不足需要先摧毁优先级组件来释放内存,且只有驻留该服务的进程成为被摧毁对象时,服务才被摧毁 Context.BIND_DEBUG_UNBIND 通常用于调试场景中判断绑定的服务是否正确,但容易引起内存泄漏,因此非调试目的的时候不建议使用 Context.BIND_NOT_FOREGROUND 表示系统将阻止驻留该服务的进程具有前台优先级,仅在后台运行。 服务类: public class BindService extends Service { // 实例化MyBinder得到mybinder对象; private final MyBinder binder = new MyBinder(); /** * 返回Binder对象。 */ @Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub return binder; } /** * 新建内部类MyBinder,继承自Binder(Binder实现IBinder接口), * MyBinder提供方法返回BindService实例。 */ public class MyBinder extends Binder{ public BindService getService(){ return BindService.this; } } @Override public boolean onUnbind(Intent intent) { // TODO Auto-generated method stub return super.onUnbind(intent); } } 启动服务的activity代码: public class MainActivity extends Activity { /** 是否绑定 */ boolean mIsBound = false; BindService mBoundService; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); doBindService(); } /** * 实例化ServiceConnection接口的实现类,用于监听服务的状态 */ private ServiceConnection conn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { BindService mBoundService = ((BindService.MyBinder) service).getService(); } @Override public void onServiceDisconnected(ComponentName name) { mBoundService = null; } }; /** 绑定服务 */ public void doBindService() { bindService(new Intent(MainActivity.this, BindService.class), conn,Context.BIND_AUTO_CREATE); mIsBound = true; } /** 解除绑定服务 */ public void doUnbindService() { if (mIsBound) { // Detach our existing connection. unbindService(conn); mIsBound = false; } } @Override protected void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); doUnbindService(); } } 注意在AndroidMainfest.xml中对Service进行显式声明 判断Service是否正在运行: private boolean isServiceRunning() { ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE); { if ("com.example.demo.BindService".equals(service.service.getClassName())) { return true; } } return false; }
版权声明:请尊重个人劳动成果,转载注明出处,谢谢!http://blog.csdn.net/amazing7/article/details/51283211 目录(?)[+] 1.概述 Hashmap继承于AbstractMap,实现了Map、Cloneable、Java.io.Serializable接口。它的key、value都可以为null,映射不是有序的。 Hashmap不是同步的,如果想要线程安全的HashMap,可以通过Collections类的静态方法synchronizedMap获得线程安全的HashMap。 Map map = Collections.synchronizedMap(new HashMap()); (除了不同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。) HashMap 中两个重要的参数:“初始容量” 和 “加载因子”。 容量 是哈希表中桶的数量,初始容量 只是哈希表在创建时的容量 加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度(默认0.75)。 当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构,桶数X2)。 加载因子越大,填满的元素越多,好处是,空间利用率高了,但:冲突的机会加大了.反之,加载因子越小,填满的元素越少, 好处是:冲突的机会减小了,但:空间浪费多了. 2.HashMap的数据结构 Hashmap本质是数组加链表。通过key的hashCode来计算hash值的,只要hashCode相同,计算出来的hash值就一样,然后再计算出数组下标,如果多个key对应到同一个下标,就用链表串起来,新插入的在前面。 结构图如下(通过key查找value): (图来源于网络) 先来看看HashMap中Entry类的代码: static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; // 指向下一个节点 Entry<K,V> next; final int hash; // 构造函数。 // 输入参数包括"哈希值(h)", "键(k)", "值(v)", "下一节点(n)" Entry(int h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; hash = h; } public final K getKey() { return key; } public final V getValue() { return value; } public final V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } // 判断两个Entry是否相等 // 若两个Entry的“key”和“value”都相等,则返回true。 // 否则,返回false public final boolean equals(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry e = (Map.Entry)o; Object k1 = getKey(); Object k2 = e.getKey(); if (k1 == k2 || (k1 != null && k1.equals(k2))) { Object v1 = getValue(); Object v2 = e.getValue(); if (v1 == v2 || (v1 != null && v1.equals(v2))) return true; } return false; } // 实现hashCode() public final int hashCode() { return (key==null ? 0 : key.hashCode()) ^ (value==null ? 0 : value.hashCode()); } public final String toString() { return getKey() + "=" + getValue(); } // 当向HashMap中添加元素时,绘调用recordAccess()。 // 这里不做任何处理 void recordAccess(HashMap<K,V> m) { } // 当从HashMap中删除元素时,绘调用recordRemoval()。 // 这里不做任何处理 void recordRemoval(HashMap<K,V> m) { } } 我们可以理解HashMap就是一个Entry数组,Entry对象中包含了键和值。 3.HashMap源码分析 HashMap共有4个构造函数,如下: HashMap() 构造一个具有默认初始容量 (16) 和默认加载因子 (0.75) 的空 HashMap。 HashMap(int initialCapacity) 构造一个带指定初始容量和默认加载因子 (0.75) 的空 HashMap。 HashMap(int initialCapacity, float loadFactor) 构造一个带指定初始容量和加载因子的空 HashMap。 HashMap(Map< extends K, extends V> m) 构造一个映射关系与指定 Map 相同的新 HashMap。 HashMap提供的API方法: void clear() 从此映射中移除所有映射关系。 Object clone() 返回此 HashMap 实例的浅表副本:并不复制键和值本身。 boolean containsKey(Object key) 如果此映射包含对于指定键的映射关系,则返回 true。 boolean containsValue(Object value) 如果此映射将一个或多个键映射到指定值,则返回 true。 Set entrySet() 返回此映射所包含的映射关系的 Set<Map.Entry> 视图。 V get(Object key) 返回指定键所映射的值;如果对于该键来说,此映射不包含任何映射关系,则返回 null。 boolean isEmpty() 如果此映射不包含键-值映射关系,则返回 true。 Set keySet() 返回此映射中所包含的键的 Set<K> 视图。 V put(K key, V value) 在此映射中关联指定值与指定键。 void putAll(Map< extends K, extends V> m) 将指定映射的所有映射关系复制到此映射中,这些映射关系将替换此映射目前针对指定映射中所有键的所有映射关系。 V remove(Object key) 从此映射中移除指定键的映射关系(如果存在)。 int size() 返回此映射中的键-值映射关系数。 Collection values() 返回此映射所包含的值的 Collection 视图。 HashMap源码: package java.util; import java.io.*; public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable { // 默认的初始容量(容量为HashMap中桶的数目)是16,且实际容量必须是2的整数次幂。 static final int DEFAULT_INITIAL_CAPACITY = 16; // 最大容量(必须是2的幂且小于2的30次方,传入容量过大将被这个值替换) static final int MAXIMUM_CAPACITY = 1 << 30; // 默认加载因子 static final float DEFAULT_LOAD_FACTOR = 0.75f; // 存储数据的Entry数组,长度是2的幂。 // HashMap是采用拉链法实现的,每一个Entry本质上是一个单向链表 transient Entry[] table; // HashMap的大小,它是HashMap保存的键值对的数量 transient int size; // HashMap的阈值,用于判断是否需要调整HashMap的容量(threshold = 容量*加载因子) int threshold; // 加载因子实际大小 final float loadFactor; // HashMap被改变的次数 transient volatile int modCount; // 指定“容量大小”和“加载因子”的构造函数 public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); // HashMap的最大容量只能是MAXIMUM_CAPACITY if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); // 找出“大于initialCapacity”的最小的2的幂 int capacity = 1; while (capacity < initialCapacity) capacity <<= 1; // 设置“加载因子” this.loadFactor = loadFactor; // 设置“HashMap阈值”,当HashMap中存储数据的数量达到threshold时,就需要将HashMap的容量加倍。 threshold = (int)(capacity * loadFactor); // 创建Entry数组,用来保存数据 table = new Entry[capacity]; init(); } // 指定“容量大小”的构造函数 public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } // 默认构造函数。 public HashMap() { // 设置“加载因子” this.loadFactor = DEFAULT_LOAD_FACTOR; // 设置“HashMap阈值”,当HashMap中存储数据的数量达到threshold时,就需要将HashMap的容量加倍。 threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR); // 创建Entry数组,用来保存数据 table = new Entry[DEFAULT_INITIAL_CAPACITY]; init(); } // 包含“子Map”的构造函数 public HashMap(Map<? extends K, ? extends V> m) { this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR); // 将m中的全部元素逐个添加到HashMap中 putAllForCreate(m); } static int hash(int h) { h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } // 返回索引值 // h & (length-1)保证返回值的小于length static int indexFor(int h, int length) { return h & (length-1); } public int size() { return size; } public boolean isEmpty() { return size == 0; } // 获取key对应的value public V get(Object key) { if (key == null) return getForNullKey(); // 获取key的hash值 int hash = hash(key.hashCode()); // 在“该hash值对应的链表”上查找“键值等于key”的元素 for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) return e.value; } return null; } // 获取“key为null”的元素的值 // HashMap将“key为null”的元素存储在table[0]位置! private V getForNullKey() { for (Entry<K,V> e = table[0]; e != null; e = e.next) { if (e.key == null) return e.value; } return null; } // HashMap是否包含key public boolean containsKey(Object key) { return getEntry(key) != null; } // 返回“键为key”的键值对 final Entry<K,V> getEntry(Object key) { // 获取哈希值 // HashMap将“key为null”的元素存储在table[0]位置,“key不为null”的则调用hash()计算哈希值 int hash = (key == null) ? 0 : hash(key.hashCode()); // 在“该hash值对应的链表”上查找“键值等于key”的元素 for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } return null; } // 将“key-value”添加到HashMap中 public V put(K key, V value) { // 若“key为null”,则将该键值对添加到table[0]中。 if (key == null) return putForNullKey(value); // 若“key不为null”,则计算该key的哈希值,然后将其添加到该哈希值对应的链表中。 int hash = hash(key.hashCode()); int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; // 若“该key”对应的键值对已经存在,则用新的value取代旧的value。然后退出! if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } // 若“该key”对应的键值对不存在,则将“key-value”添加到table中 modCount++; addEntry(hash, key, value, i); return null; } // putForNullKey()的作用是将“key为null”键值对添加到table[0]位置 private V putForNullKey(V value) { for (Entry<K,V> e = table[0]; e != null; e = e.next) { if (e.key == null) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } // 这里的完全不会被执行到! modCount++; addEntry(0, null, value, 0); return null; } // 创建HashMap对应的“添加方法”, // 它和put()不同。putForCreate()是内部方法,它被构造函数等调用,用来创建HashMap // 而put()是对外提供的往HashMap中添加元素的方法。 private void putForCreate(K key, V value) { int hash = (key == null) ? 0 : hash(key.hashCode()); int i = indexFor(hash, table.length); // 若该HashMap表中存在“键值等于key”的元素,则替换该元素的value值 for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { e.value = value; return; } } // 若该HashMap表中不存在“键值等于key”的元素,则将该key-value添加到HashMap中 createEntry(hash, key, value, i); } // 将“m”中的全部元素都添加到HashMap中。 // 该方法被内部的构造HashMap的方法所调用。 private void putAllForCreate(Map<? extends K, ? extends V> m) { // 利用迭代器将元素逐个添加到HashMap中 for (Iterator<? extends Map.Entry<? extends K, ? extends V>> i = m.entrySet().iterator(); i.hasNext(); ) { Map.Entry<? extends K, ? extends V> e = i.next(); putForCreate(e.getKey(), e.getValue()); } } // 重新调整HashMap的大小,newCapacity是调整后的单位 void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } // 新建一个HashMap,将“旧HashMap”的全部元素添加到“新HashMap”中, // 然后,将“新HashMap”赋值给“旧HashMap”。 Entry[] newTable = new Entry[newCapacity]; transfer(newTable); table = newTable; threshold = (int)(newCapacity * loadFactor); } // 将HashMap中的全部元素都添加到newTable中 void transfer(Entry[] newTable) { Entry[] src = table; int newCapacity = newTable.length; for (int j = 0; j < src.length; j++) { Entry<K,V> e = src[j]; if (e != null) { src[j] = null; do { Entry<K,V> next = e.next; int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e; e = next; } while (e != null); } } } // 将"m"的全部元素都添加到HashMap中 public void putAll(Map<? extends K, ? extends V> m) { // 有效性判断 int numKeysToBeAdded = m.size(); if (numKeysToBeAdded == 0) return; // 计算容量是否足够, // 若“当前实际容量 < 需要的容量”,则将容量x2。 if (numKeysToBeAdded > threshold) { int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1); if (targetCapacity > MAXIMUM_CAPACITY) targetCapacity = MAXIMUM_CAPACITY; int newCapacity = table.length; while (newCapacity < targetCapacity) newCapacity <<= 1; if (newCapacity > table.length) resize(newCapacity); } // 通过迭代器,将“m”中的元素逐个添加到HashMap中。 for (Iterator<? extends Map.Entry<? extends K, ? extends V>> i = m.entrySet().iterator(); i.hasNext(); ) { Map.Entry<? extends K, ? extends V> e = i.next(); put(e.getKey(), e.getValue()); } } // 删除“键为key”元素 public V remove(Object key) { Entry<K,V> e = removeEntryForKey(key); return (e == null ? null : e.value); } // 删除“键为key”的元素 final Entry<K,V> removeEntryForKey(Object key) { // 获取哈希值。若key为null,则哈希值为0;否则调用hash()进行计算 int hash = (key == null) ? 0 : hash(key.hashCode()); int i = indexFor(hash, table.length); Entry<K,V> prev = table[i]; Entry<K,V> e = prev; // 删除链表中“键为key”的元素 // 本质是“删除单向链表中的节点” while (e != null) { Entry<K,V> next = e.next; Object k; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { modCount++; size--; if (prev == e) table[i] = next; else prev.next = next; e.recordRemoval(this); return e; } prev = e; e = next; } return e; } // 删除“键值对” final Entry<K,V> removeMapping(Object o) { if (!(o instanceof Map.Entry)) return null; Map.Entry<K,V> entry = (Map.Entry<K,V>) o; Object key = entry.getKey(); int hash = (key == null) ? 0 : hash(key.hashCode()); int i = indexFor(hash, table.length); Entry<K,V> prev = table[i]; Entry<K,V> e = prev; // 删除链表中的“键值对e” // 本质是“删除单向链表中的节点” while (e != null) { Entry<K,V> next = e.next; if (e.hash == hash && e.equals(entry)) { modCount++; size--; if (prev == e) table[i] = next; else prev.next = next; e.recordRemoval(this); return e; } prev = e; e = next; } return e; } // 清空HashMap,将所有的元素设为null public void clear() { modCount++; Entry[] tab = table; for (int i = 0; i < tab.length; i++) tab[i] = null; size = 0; } // 是否包含“值为value”的元素 public boolean containsValue(Object value) { // 若“value为null”,则调用containsNullValue()查找 if (value == null) return containsNullValue(); // 若“value不为null”,则查找HashMap中是否有值为value的节点。 Entry[] tab = table; for (int i = 0; i < tab.length ; i++) for (Entry e = tab[i] ; e != null ; e = e.next) if (value.equals(e.value)) return true; return false; } // 是否包含null值 private boolean containsNullValue() { Entry[] tab = table; for (int i = 0; i < tab.length ; i++) for (Entry e = tab[i] ; e != null ; e = e.next) if (e.value == null) return true; return false; } // 克隆一个HashMap,并返回Object对象 public Object clone() { HashMap<K,V> result = null; try { result = (HashMap<K,V>)super.clone(); } catch (CloneNotSupportedException e) { // assert false; } result.table = new Entry[table.length]; result.entrySet = null; result.modCount = 0; result.size = 0; result.init(); // 调用putAllForCreate()将全部元素添加到HashMap中 result.putAllForCreate(this); return result; } // Entry是单向链表。 // 它是 “HashMap链式存储法”对应的链表。 // 它实现了Map.Entry 接口,即实现getKey(), getValue(), setValue(V value), equals(Object o), hashCode()这些函数 static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; // 指向下一个节点 Entry<K,V> next; final int hash; // 构造函数。 // 输入参数包括"哈希值(h)", "键(k)", "值(v)", "下一节点(n)" Entry(int h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; hash = h; } public final K getKey() { return key; } public final V getValue() { return value; } public final V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } // 判断两个Entry是否相等 // 若两个Entry的“key”和“value”都相等,则返回true。 // 否则,返回false public final boolean equals(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry e = (Map.Entry)o; Object k1 = getKey(); Object k2 = e.getKey(); if (k1 == k2 || (k1 != null && k1.equals(k2))) { Object v1 = getValue(); Object v2 = e.getValue(); if (v1 == v2 || (v1 != null && v1.equals(v2))) return true; } return false; } // 实现hashCode() public final int hashCode() { return (key==null ? 0 : key.hashCode()) ^ (value==null ? 0 : value.hashCode()); } public final String toString() { return getKey() + "=" + getValue(); } // 当向HashMap中添加元素时,绘调用recordAccess()。 // 这里不做任何处理 void recordAccess(HashMap<K,V> m) { } // 当从HashMap中删除元素时,绘调用recordRemoval()。 // 这里不做任何处理 void recordRemoval(HashMap<K,V> m) { } } // 新增Entry。将“key-value”插入指定位置,bucketIndex是位置索引。 void addEntry(int hash, K key, V value, int bucketIndex) { // 保存“bucketIndex”位置的值到“e”中 Entry<K,V> e = table[bucketIndex]; // 设置“bucketIndex”位置的元素为“新Entry”, // 设置“e”为“新Entry的下一个节点” table[bucketIndex] = new Entry<K,V>(hash, key, value, e); // 若HashMap的实际大小 不小于 “阈值”,则调整HashMap的大小 if (size++ >= threshold) resize(2 * table.length); } // 创建Entry。将“key-value”插入指定位置,bucketIndex是位置索引。 // 它和addEntry的区别是: // (01) addEntry()一般用在 新增Entry可能导致“HashMap的实际容量”超过“阈值”的情况下。 // 例如,我们新建一个HashMap,然后不断通过put()向HashMap中添加元素; // put()是通过addEntry()新增Entry的。 // 在这种情况下,我们不知道何时“HashMap的实际容量”会超过“阈值”; // 因此,需要调用addEntry() // (02) createEntry() 一般用在 新增Entry不会导致“HashMap的实际容量”超过“阈值”的情况下。 // 例如,我们调用HashMap“带有Map”的构造函数,它绘将Map的全部元素添加到HashMap中; // 但在添加之前,我们已经计算好“HashMap的容量和阈值”。也就是,可以确定“即使将Map中 // 的全部元素添加到HashMap中,都不会超过HashMap的阈值”。 // 此时,调用createEntry()即可。 void createEntry(int hash, K key, V value, int bucketIndex) { // 保存“bucketIndex”位置的值到“e”中 Entry<K,V> e = table[bucketIndex]; // 设置“bucketIndex”位置的元素为“新Entry”, // 设置“e”为“新Entry的下一个节点” table[bucketIndex] = new Entry<K,V>(hash, key, value, e); size++; } // HashIterator是HashMap迭代器的抽象出来的父类,实现了公共了函数。 // 它包含“key迭代器(KeyIterator)”、“Value迭代器(ValueIterator)”和“Entry迭代器(EntryIterator)”3个子类。 private abstract class HashIterator<E> implements Iterator<E> { // 下一个元素 Entry<K,V> next; // expectedModCount用于实现fast-fail机制。 int expectedModCount; // 当前索引 int index; // 当前元素 Entry<K,V> current; HashIterator() { expectedModCount = modCount; if (size > 0) { // advance to first entry Entry[] t = table; // 将next指向table中第一个不为null的元素。 // 这里利用了index的初始值为0,从0开始依次向后遍历,直到找到不为null的元素就退出循环。 while (index < t.length && (next = t[index++]) == null) } } public final boolean hasNext() { return next != null; } // 获取下一个元素 final Entry<K,V> nextEntry() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); Entry<K,V> e = next; if (e == null) throw new NoSuchElementException(); // 注意!!! // 一个Entry就是一个单向链表 // 若该Entry的下一个节点不为空,就将next指向下一个节点; // 否则,将next指向下一个链表(也是下一个Entry)的不为null的节点。 if ((next = e.next) == null) { Entry[] t = table; while (index < t.length && (next = t[index++]) == null) } current = e; return e; } // 删除当前元素 public void remove() { if (current == null) throw new IllegalStateException(); if (modCount != expectedModCount) throw new ConcurrentModificationException(); Object k = current.key; current = null; HashMap.this.removeEntryForKey(k); expectedModCount = modCount; } } // value的迭代器 private final class ValueIterator extends HashIterator<V> { public V next() { return nextEntry().value; } } // key的迭代器 private final class KeyIterator extends HashIterator<K> { public K next() { return nextEntry().getKey(); } } // Entry的迭代器 private final class EntryIterator extends HashIterator<Map.Entry<K,V>> { public Map.Entry<K,V> next() { return nextEntry(); } } // 返回一个“key迭代器” Iterator<K> newKeyIterator() { return new KeyIterator(); } // 返回一个“value迭代器” Iterator<V> newValueIterator() { return new ValueIterator(); } // 返回一个“entry迭代器” Iterator<Map.Entry<K,V>> newEntryIterator() { return new EntryIterator(); } // HashMap的Entry对应的集合 private transient Set<Map.Entry<K,V>> entrySet = null; // 返回“key的集合”,实际上返回一个“KeySet对象” public Set<K> keySet() { Set<K> ks = keySet; return (ks != null ? ks : (keySet = new KeySet())); } // Key对应的集合 // KeySet继承于AbstractSet,说明该集合中没有重复的Key。 private final class KeySet extends AbstractSet<K> { public Iterator<K> iterator() { return newKeyIterator(); } public int size() { return size; } public boolean contains(Object o) { return containsKey(o); } public boolean remove(Object o) { return HashMap.this.removeEntryForKey(o) != null; } public void clear() { HashMap.this.clear(); } } // 返回“value集合”,实际上返回的是一个Values对象 public Collection<V> values() { Collection<V> vs = values; return (vs != null ? vs : (values = new Values())); } // “value集合” // Values继承于AbstractCollection,不同于“KeySet继承于AbstractSet”, // Values中的元素能够重复。因为不同的key可以指向相同的value。 private final class Values extends AbstractCollection<V> { public Iterator<V> iterator() { return newValueIterator(); } public int size() { return size; } public boolean contains(Object o) { return containsValue(o); } public void clear() { HashMap.this.clear(); } } // 返回“HashMap的Entry集合” public Set<Map.Entry<K,V>> entrySet() { return entrySet0(); } // 返回“HashMap的Entry集合”,它实际是返回一个EntrySet对象 private Set<Map.Entry<K,V>> entrySet0() { Set<Map.Entry<K,V>> es = entrySet; return es != null ? es : (entrySet = new EntrySet()); } // EntrySet对应的集合 // EntrySet继承于AbstractSet,说明该集合中没有重复的EntrySet。 private final class EntrySet extends AbstractSet<Map.Entry<K,V>> { public Iterator<Map.Entry<K,V>> iterator() { return newEntryIterator(); } public boolean contains(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry<K,V> e = (Map.Entry<K,V>) o; Entry<K,V> candidate = getEntry(e.getKey()); return candidate != null && candidate.equals(e); } public boolean remove(Object o) { return removeMapping(o) != null; } public int size() { return size; } public void clear() { HashMap.this.clear(); } } // java.io.Serializable的写入函数 // 将HashMap的“总的容量,实际容量,所有的Entry”都写入到输出流中 private void writeObject(java.io.ObjectOutputStream s) throws IOException { Iterator<Map.Entry<K,V>> i = (size > 0) ? entrySet0().iterator() : null; // Write out the threshold, loadfactor, and any hidden stuff s.defaultWriteObject(); // Write out number of buckets s.writeInt(table.length); // Write out size (number of Mappings) s.writeInt(size); // Write out keys and values (alternating) if (i != null) { while (i.hasNext()) { Map.Entry<K,V> e = i.next(); s.writeObject(e.getKey()); s.writeObject(e.getValue()); } } } private static final long serialVersionUID = 362498820763181265L; // java.io.Serializable的读取函数:根据写入方式读出 // 将HashMap的“总的容量,实际容量,所有的Entry”依次读出 private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { // Read in the threshold, loadfactor, and any hidden stuff s.defaultReadObject(); // Read in number of buckets and allocate the bucket array; int numBuckets = s.readInt(); table = new Entry[numBuckets]; init(); // Give subclass a chance to do its thing. // Read in size (number of Mappings) int size = s.readInt(); // Read the keys and values, and put the mappings in the HashMap for (int i=0; i<size; i++) { K key = (K) s.readObject(); V value = (V) s.readObject(); putForCreate(key, value); } } // 返回“HashMap总的容量” int capacity() { return table.length; } // 返回“HashMap的加载因子” float loadFactor() { return loadFactor; } } 在public V get(Object key)方法中: 如果key不为null,则先求的key的hash值,根据hash值找到在table中的索引,在该索引对应的单链表中查找是否有键值对的key与目标key相等,有就返回对应的value,没有则返回null。 如果key为null,则直接从哈希表的第一个位置table[0]对应的链表上查找。记住,key为null的键值对永远都放在以table[0]为头结点的链表中,当然不一定是存放在头结点table[0]中。 public V put(K key, V value) 方法中: 如果key不为null,则同样先求出key的hash值,根据hash值得出在table中的索引,而后遍历对应的单链表,如果单链表中存在与目标key相等的键值对,则将新的value覆盖旧的value,并将旧的value返回,如果找不到与目标key相等的键值对,或者该单链表为空,则将该键值对插入到改单链表的头结点位置(每次新插入的节点都是放在头结点的位置),该操作是有addEntry方法实现的,它的源码如下: void addEntry(int hash, K key, V value, int bucketIndex) { Entry<K,V> e = table[bucketIndex]; //如果要加入的位置有值,将该位置原先的值设置为新entry的next,也就是新entry链表的下一个节点 table[bucketIndex] = new Entry<>(hash, key, value, e); if (size++ >= threshold) //如果大于临界值就扩容 resize(2 * table.length); //以2的倍数扩容 } 参数bucketIndex就是indexFor函数计算出来的索引值,第2行代码是取得数组中索引为bucketIndex的Entry对象,第3行就是用hash、key、value构建一个新的Entry对象放到索引为bucketIndex的位置,并且将该位置原先的对象设置为新对象的next构成链表。第4行和第5行就是判断put后size是否达到了临界值threshold,如果达到了临界值就要进行扩容,HashMap扩容是扩为原来的两倍。 如果key为null,则将其添加到table[0]对应的链表中,由putForNullKey()实现。 // putForNullKey()的作用是将“key为null”键值对添加到table[0]位置 private V putForNullKey(V value) { for (Entry<K,V> e = table[0]; e != null; e = e.next) { if (e.key == null) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } // 如果没有存在key为null的键值对,则直接题阿见到table[0]处! modCount++; addEntry(0, null, value, 0); return null; } resize扩容方法 void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } Entry[] newTable = new Entry[newCapacity]; transfer(newTable);//用来将原先table的元素全部移到newTable里面 table = newTable; //再将newTable赋值给table threshold = (int)(newCapacity * loadFactor);//重新计算临界值 } 它新建了一个HashMap的底层数组,而后调用transfer方法,将就HashMap的全部元素添加到新的HashMap中(要重新计算元素在新的数组中的索引位置)。 扩容是需要进行数组复制的,非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。 // 将HashMap中的全部元素都添加到newTable中 void transfer(Entry[] newTable) { Entry[] src = table; int newCapacity = newTable.length; for (int j = 0; j < src.length; j++) { Entry<K,V> e = src[j]; if (e != null) { src[j] = null; do { Entry<K,V> next = e.next; int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e; e = next; } while (e != null); } } } 计算哈希值的方法 static int hash(int h) { h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } 移位的操作使hash值的计算效率更高。 由hash值找到对应索引的方法 static int indexFor(int h, int length) { return h & (length-1); } HashMap中则通过h&(length-1)的方法来代替取模,同样实现了均匀的散列,但效率要高很多,这也是HashMap对Hashtable的一个改进。 length为2的整数次幂的话,h&(length-1)就相当于对length取模,这样便保证了散列的均匀,同时也提升了效率; length为2的整数次幂的话,为偶数,这样length-1为奇数,奇数的最后一位是1,这样便保证了h&(length-1)的最后一位可能为0,也可能为1(这取决于h的值),即与后的结果可能为偶数,也可能为奇数,这样便可以保证散列的均匀性,而如果length为奇数的话,很明显length-1为偶数,它的最后一位是0,这样h&(length-1)的最后一位肯定为0,即只能为偶数,这样任何hash值都只会被散列到数组的偶数下标位置上,这便浪费了近一半的空间。 4.HashMap和ConcurrentHashMap的区别 ConcurrentHashMap是在hashMap的基础上,将数据分为多个segment(类似hashtable),默认16个(concurrency level),然后在每一个分段上都用锁进行保护,从而让锁的粒度更精细一些,并发性能更好。
版权声明:请尊重个人劳动成果,转载注明出处,谢谢!http://blog.csdn.net/amazing7/article/details/51282082 目录(?)[+] 1.概述 Fragment是Activity中用户界面的一个行为或者是一部分。主要是支持在大屏幕上动态和更为灵活的去组合或是交换UI组件,通过将activity的布局分割成若干个fragment,可以在运行时编辑activity的呈现,并且那些变化会被保存在由activity管理的后台栈里面。 Fragment必须总是被嵌入到一个activity之中,并且fragment的生命周期直接受其宿主activity的生命周期的影响。你可以认为fragment是activity的一个模块零件,它有自己的生命周期,接收它自己的输入事件,并且可以在activity运行时添加或者删除。 应该将每一个fragment设计为模块化的和可复用化的activity组件。也就是说,你可以在多个activity中引用同一个fragment,因为fragment定义了它自己的布局,并且使用它本身生命周期回调的行为。 2.Fragment的生命周期 先看fragment生命周期图: fragment所生存的activity生命周期直接影响着fragment的生命周期,由此针对activity的每一个生命周期回调都会引发一个fragment类似的回调。例如,当activity接收到onPause()时,这个activity之中的每个fragment都会接收到onPause()。 这有Activity的详细说明 Fragment有一些额外的生命周期回调方法(创建和销毁fragment界面). onAttach() 当fragment被绑定到activity时调用(Activity会被传入)。 onCreateView() 将本身的布局构建到activity中去(fragment作为activity界面的一部分) onActivityCreated() 当activity的onCreate()函数返回时被调用。 onDestroyView() 当与fragment关联的视图体系正被移除时被调用。 onDetach() 当fragment正与activity解除关联时被调用。 当activity接收到它的onCreate()回调时,activity之中的fragment接收到onActivityCreated()回调。 一旦activity处于resumed状态,则可以在activity中自由的添加或者移除fragment。因此,只有当activity处于resumed状态时,fragment的生命周期才可以独立变化。 fragment会在 activity离开恢复状态时 再一次被activity推入它的生命周期中。 管理fragment生命周期与管理activity生命周期很相像。像activity一样,fragment也有三种状态: Resumed fragment在运行中的activity可见。 Paused 另一个activity处于前台且得到焦点,但是这个fragment所在的activity仍然可见(前台activity部分透明,或者没有覆盖全屏)。 Stopped fragment不可见。要么宿主activity已经停止,要么fragment已经从activity上移除,但已被添加到后台栈中。一个停止的fragment仍然活着(所有状态和成员信息仍然由系统保留着)。但是,它对用户来讲已经不再可见,并且如果activity被杀掉,它也将被杀掉。 如果activity的进程被杀掉了,在activity被重新创建时,你需要恢复fragment状态。可以执行fragment的onSaveInstanceState()来保存状态(注意在fragment是在onCreate(),onCreateView(),或onActvityCreate()中进行恢复)。 在生命周期方面,activity与fragment之间一个很重要的不同,就是在各自的后台栈中是如何存储的。 当activity停止时,默认情况下activity被安置在由系统管理的activity后台栈中; fragment仅当在一个事务被移除时,通过显式调用addToBackStack()请求保存的实例,该fragment才被置于由宿主activity管理的后台栈。 要创建一个fragment,必须创建一个fragment的子类。一般情况下,我们至少需要实现以下几个fragment生命周期方法: onCreate() 在创建fragment时系统会调用此方法。在实现代码中,你可以初始化想要在fragment中保持的那些必要组件,当fragment处于暂停或者停止状态之后可重新启用它们。 onCreateView() 在第一次为fragment绘制用户界面时系统会调用此方法。为fragment绘制用户界面,这个函数必须要返回所绘出的fragment的根View。如果fragment没有用户界面可以返回空。 @Override public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) { // Inflate the layout for this fragment return inflater.inflate(R.layout.example_fragment, container, false); } inflate()函数需要以下三个参数: ①要inflate的布局的资源ID。 ②被inflate的布局的父ViewGroup。 ③一个布尔值,表明在inflate期间被infalte的布局是否应该附上ViewGroup(第二个参数Container)。(在这个例子中传入的是false,因为系统已经将被inflate的布局插入到容器中(container)——传入true会在最终的布局里创建一个多余的ViewGroup。) onPause() 系统回调用该函数作为用户离开fragment的第一个预兆(尽管这并不总意味着fragment被销毁)。在当前用户会话结束之前,通常要在这里提交任何应该持久化的变化(因为用户可能不再返回)。 3.将fragment添加到activity之中 可以通过在activity布局文件中声明fragment,用fragment标签把fragment插入到activity的布局中,或者是用应用程序源码将它添加到一个存在的ViewGroup中。 但fragment并不是一个定要作为activity布局的一部分,fragment也可以为activity隐身工作。 3.1在activity的布局文件里声明fragment 可以像为view一样为fragment指定布局属性。例如: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment android:name="com.example.test.FragmentOne" android:id="@+id/fo" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout> fragment标签中的Android:name 属性指定了布局中实例化的Fragment类。 当系统创建activity布局时,它实例化了布局文件中指定的每一个fragment,并为它们调用onCreateView()函数,以获取每一个fragment的布局。系统直接在元素的位置插入fragment返回的View。 注意:每个fragment都需要一个唯一的标识,如果重启activity,系统可用来恢复fragment(并且可用来捕捉fragment的事务处理,例如移除)。为fragment提供ID有三种方法: 用android:id属性提供一个唯一的标识。 用android:tag属性提供一个唯一的字符串。 如果上述两个属性都没有,系统会使用其容器视图(view)的ID。 3.2通过编码将fragment添加到已存在的ViewGroup中 在activity运行的任何时候,你都可以将fragment添加到activity布局中。 要管理activity中的fragment,可以使用FragmentManager。可以通过在activity中调用getFragmentManager()获得。使用FragmentManager 可以做如下事情,包括: 使用findFragmentById()(用于在activity布局中提供有界面的fragment)或者findFragmentByTag()获取activity中存在的fragment(用于有界面或者没有界面的fragment)。 使用popBackStack()(模仿用户的BACK命令)从后台栈弹出fragment。 使用addOnBackStackChangedListener()注册一个监听后台栈变化的监听器。 在Android中,对Fragment的事务操作都是通过FragmentTransaction来执行。操作大致可以分为两类: 显示:add() replace() show() attach() 隐藏:remove() hide() detach() 说明: 调用show() & hide()方法时,Fragment的生命周期方法并不会被执行,仅仅是Fragment的View被显示或者隐藏。 执行replace()时(至少两个Fragment),会执行第二个Fragment的onAttach()方法、执行第一个Fragment的onPause()-onDetach()方法,同时containerView会detach第一个Fragment的View。 add()方法执行onAttach()-onResume()的生命周期,相对的remove()就是执行完成剩下的onPause()-onDetach()周期。 可以像下面这样从Activity中取得FragmentTransaction的实例: FragmentManager fragmentManager = getFragmentManager() FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); 可以用add()函数添加fragment,并指定要添加的fragment以及要将其插入到哪个视图(view)之中(注意commit事务): ExampleFragment fragment = new ExampleFragment(); fragmentTransaction.add(R.id.fragment_container, fragment); fragmentTransaction.commit(); 3.3添加没有界面的fragment 也可以使用fragment为activity提供后台动作,却不呈现多余的用户界面。 想要添加没有界面的fragment ,可以使用add(Fragment, String)(为fragment提供一个唯一的字符串“tag”,而不是视图(view)ID)。这样添加了fragment,但是,因为还没有关联到activity布局中的视图(view) ,收不到onCreateView()的调用。所以不需要实现这个方法。 对于无界面fragment,字符串标签是唯一识别它的方法。如果之后想从activity中取到fragment,需要使用findFragmentByTag()。 4.fragment事务后台栈 在调用commit()之前,可以将事务添加到fragment事务后台栈中(通过调用addToBackStatck())。这个后台栈由activity管理,并且允许用户通过按BACK键回退到前一个fragment状态。 下面的代码中一个fragment代替另一个fragment,并且将之前的fragment状态保留在后台栈中: Fragment newFragment = new ExampleFragment(); FragmentTransaction transaction = getFragmentManager().beginTransaction(); transaction.replace(R.id.fragment_container, newFragment); transaction.addToBackStack(null); transaction.commit(); 注意: 如果添加多个变更事务(例如另一个add()或者remove())并调用addToBackStack(),那么在调用commit()之前的所有应用的变更被作为一个单独的事务添加到后台栈中,并且BACK键可以将它们一起回退。 当移除一个fragment时,如果调用了addToBackStack(),那么之后fragment会被停止,如果用户回退,它将被恢复过来。 调用commit()并不立刻执行事务,相反,而是采取预约方式,一旦activity的界面线程(主线程)准备好便可运行起来。然而,如果有必要的话,你可以从界面线程调用executePendingTransations()立即执行由commit()提交的事务。 只能在activity保存状态(当用户离开activity时)之前用commit()提交事务。如果你尝试在那时之后提交,会抛出一个异常。这是因为如果activity需要被恢复,提交后的状态会被丢失。对于这类丢失提交的情况,可使用commitAllowingStateLoss() 5.与Activity交互 Activity中已经有了该Fragment的引用,直接通过该引用进行交互。 -如果没引用可以通过调用fragment的函数findFragmentById()或者findFragmentByTag(),从FragmentManager中获取Fragment的索引,例如: ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment); 1 1 在Fragment中可以通过getActivity得到当前绑定的Activity的实例。 创建activity事件回调函数,在fragment内部定义一个回调接口,宿主activity来实现它。 参考: fragments API Guides FragmentTransaction Reference
版权声明:请尊重个人劳动成果,转载注明出处,谢谢! 目录(?)[+] 在Android开发中会经常遇到滑动冲突(比如ScrollView或是SliddingMenu与ListView的嵌套)的问题,需要我们深入的了解android事件响应机制才能解决,事件响应机制已经是android开发者必不可少的知识。 1.涉及到事件响应的常用方法构成 用户在手指与屏幕接触过程中通过MotionEvent对象产生一系列事件,它有四种状态: MotionEvent.ACTION_DOWN :手指按下屏幕的瞬间(一切事件的开始) MotionEvent.ACTION_MOVE :手指在屏幕上移动 MotionEvent.ACTION_UP :手指离开屏幕瞬间 MotionEvent.ACTION_CANCEL :取消手势,一般由程序产生,不会由用户产生 Android中的事件onClick, onLongClick,onScroll, onFling等等,都是由许多个Touch事件构成的(一个ACTION_DOWN, n个ACTION_MOVE,1个ACTION_UP)。 android 事件响应机制是先 分发(先由外部的View接收,然后依次传递给其内层的最小View)再 处理 (从最小View单元(事件源)开始依次向外层传递。)的形式实现的。 复杂性表现在:可以控制每层事件是否继续传递(分发和拦截协同实现),以及事件的具体消费(事件分发也具有事件消费能力)。 2.android事件处理涉及到的三个重要函数 事件分发:public boolean dispatchTouchEvent(MotionEvent ev) 当有监听到事件时,首先由Activity进行捕获,进入事件分发处理流程。(因为activity没有事件拦截,View和ViewGroup有)会将事件传递给最外层View的dispatchTouchEvent(MotionEvent ev)方法,该方法对事件进行分发。 return true :表示该View内部消化掉了所有事件。 return false :事件在本层不再继续进行分发,并交由上层控件的onTouchEvent方法进行消费(如果本层控件已经是Activity,那么事件将被系统消费或处理)。 如果事件分发返回系统默认的 super.dispatchTouchEvent(ev),事件将分发给本层的事件拦截onInterceptTouchEvent 方法进行处理 事件拦截:public boolean onInterceptTouchEvent(MotionEvent ev) return true :表示将事件进行拦截,并将拦截到的事件交由本层控件 的 onTouchEvent 进行处理; return false :则表示不对事件进行拦截,事件得以成功分发到子View。并由子View的dispatchTouchEvent进行处理。 如果返回super.onInterceptTouchEvent(ev),默认表示拦截该事件,并将事件传递给当前View的onTouchEvent方法,和return true一样。 事件响应:public boolean onTouchEvent(MotionEvent ev) 在dispatchTouchEvent(事件分发)返回super.dispatchTouchEvent(ev)并且onInterceptTouchEvent(事件拦截返回true或super.onInterceptTouchEvent(ev)的情况下,那么事件会传递到onTouchEvent方法,该方法对事件进行响应。 如果return true,表示onTouchEvent处理完事件后消费了此次事件。此时事件终结; 如果return fasle,则表示不响应事件,那么该事件将会不断向上层View的onTouchEvent方法传递,直到某个View的onTouchEvent方法返回true,如果到了最顶层View还是返回false,那么认为该事件不消耗,则在同一个事件系列中,当前View无法再次接收到事件,该事件会交由Activity的onTouchEvent进行处理; 如果return super.dispatchTouchEvent(ev),则表示不响应事件,结果与return false一样。 从以上过程中可以看出,dispatchTouchEvent无论返回true还是false,事件都不再进行分发,只有当其返回super.dispatchTouchEvent(ev),才表明其具有向下层分发的愿望,但是是否能够分发成功,则需要经过事件拦截onInterceptTouchEvent的审核。事件是否向上传递处理是由onTouchEvent的返回值决定的。 (图来自网络) 3.View源码分析 Android中ImageView、textView、Button等继承于View但没有重写的dispatchTouchEvent方法,所以都用的View的该方法进行事件分发。 看View重要函数部分源码: public boolean dispatchTouchEvent(MotionEvent event) { //返回true,表示该View内部消化掉了所有事件。返回false,表示View内部只处理了ACTION_DOWN事件,事件继续传递,向上级View(ViewGroup)传递。 if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)) { //此处的onTouch方式就是回调的我们注册OnTouchListener时重写的onTouch()方法 return true; } return onTouchEvent(event); } 首先进行三个条件的判断: (1)查看是否给button设置了OnTouchListener()事件; (2)控件是否Enable;(控件默认都是enable的) (3)button里面实现的OnTouchListener监听里的onTouch()方法是否返回true; 如果条件都满足,则该事件被消耗掉,不再进入onTouchEvent中处理。否则将事件将交给onTouchEvent方法处理。 public boolean onTouchEvent(MotionEvent event) { ... /* 当前onTouch的组件必须是可点击的比如Button,ImageButton等等,此处CLICKABLE为true,才会进入if方法,最后返回true。 如果是ImageView、TexitView这些默认为不可点击的View,此处CLICKABLE为false,最后返回false。当然会有特殊情况,如果给这些View设置了onClick监听器,此处CLICKABLE也将为true */ if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { switch (event.getAction()) { case MotionEvent.ACTION_UP: ... if (!post(mPerformClick)) { performClick();// 实际就是回调了我们注册的OnClickListener中重新的onClick()方法 } ... break; case MotionEvent.ACTION_DOWN: ... break; case MotionEvent.ACTION_CANCEL: ... break; case MotionEvent.ACTION_MOVE: ... break; } return true; } return false; } public boolean performClick() { ... // if (li != null && li.mOnClickListener != null) { ... li.mOnClickListener.onClick(this); return true; } return false; } public void setOnClickListener(OnClickListener l) { if (!isClickable()) { setClickable(true); } getListenerInfo().mOnClickListener = l; } 只有我们注册OnTouchListener时重写的 onTouch()方法中 返回false —> 执行onTouchEvent方法 —> 导致onClick()回调方法执行 返回true —> onTouchEvent方法不执行 —> 导致onClick()回调方法不会执行 4.ViewGroup源码分析 Android中诸如LinearLayout等的五大布局控件,都是继承自ViewGroup,而ViewGroup本身是继承自View,所以ViewGroup的事件处理机制对这些控件都有效。 部分源码: public boolean dispatchTouchEvent(MotionEvent ev) { final int action = ev.getAction(); final float xf = ev.getX(); final float yf = ev.getY(); final float scrolledXFloat = xf + mScrollX; final float scrolledYFloat = yf + mScrollY; final Rect frame = mTempRect; //这个值默认是false, 然后我们可以通过requestDisallowInterceptTouchEvent(boolean disallowIntercept)方法 //来改变disallowIntercept的值 boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; //这里是ACTION_DOWN的处理逻辑 if (action == MotionEvent.ACTION_DOWN) { //清除mMotionTarget, 每次ACTION_DOWN都很设置mMotionTarget为null if (mMotionTarget != null) { mMotionTarget = null; } //disallowIntercept默认是false, 就看ViewGroup的onInterceptTouchEvent()方法 if (disallowIntercept || !onInterceptTouchEvent(ev)) { //第一点 ev.setAction(MotionEvent.ACTION_DOWN); final int scrolledXInt = (int) scrolledXFloat; final int scrolledYInt = (int) scrolledYFloat; final View[] children = mChildren; final int count = mChildrenCount; //遍历其子View for (int i = count - 1; i >= 0; i--) { //第二点 final View child = children[i]; //如果该子View是VISIBLE或者该子View正在执行动画, 表示该View才 //可以接受到Touch事件 if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { //获取子View的位置范围 child.getHitRect(frame); //如Touch到屏幕上的点在该子View上面 if (frame.contains(scrolledXInt, scrolledYInt)) { // offset the event to the view's coordinate system final float xc = scrolledXFloat - child.mLeft; final float yc = scrolledYFloat - child.mTop; ev.setLocation(xc, yc); child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; //调用该子View的dispatchTouchEvent()方法 if (child.dispatchTouchEvent(ev)) { // 如果child.dispatchTouchEvent(ev)返回true表示 //该事件被消费了,设置mMotionTarget为该子View mMotionTarget = child; //直接返回true return true; } // The event didn't get handled, try the next view. // Don't reset the event's location, it's not // necessary here. } } } } } //判断是否为ACTION_UP或者ACTION_CANCEL boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) || (action == MotionEvent.ACTION_CANCEL); if (isUpOrCancel) { //如果是ACTION_UP或者ACTION_CANCEL, 将disallowIntercept设置为默认的false //假如我们调用了requestDisallowInterceptTouchEvent()方法来设置disallowIntercept为true //当我们抬起手指或者取消Touch事件的时候要将disallowIntercept重置为false //所以说上面的disallowIntercept默认在我们每次ACTION_DOWN的时候都是false mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; } // The event wasn't an ACTION_DOWN, dispatch it to our target if // we have one. final View target = mMotionTarget; //mMotionTarget为null意味着没有找到消费Touch事件的View, 所以我们需要调用ViewGroup父类的 //dispatchTouchEvent()方法,也就是View的dispatchTouchEvent()方法 if (target == null) { // We don't have a target, this means we're handling the // event as a regular view. ev.setLocation(xf, yf); if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) { ev.setAction(MotionEvent.ACTION_CANCEL); mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; } return super.dispatchTouchEvent(ev); } //这个if里面的代码ACTION_DOWN不会执行,只有ACTION_MOVE //ACTION_UP才会走到这里, 假如在ACTION_MOVE或者ACTION_UP拦截的 //Touch事件, 将ACTION_CANCEL派发给target,然后直接返回true //表示消费了此Touch事件 if (!disallowIntercept && onInterceptTouchEvent(ev)) { final float xc = scrolledXFloat - (float) target.mLeft; final float yc = scrolledYFloat - (float) target.mTop; mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; ev.setAction(MotionEvent.ACTION_CANCEL); ev.setLocation(xc, yc); if (!target.dispatchTouchEvent(ev)) { } // clear the target mMotionTarget = null; // Don't dispatch this event to our own view, because we already // saw it when intercepting; we just want to give the following // event to the normal onTouchEvent(). return true; } if (isUpOrCancel) { mMotionTarget = null; } // finally offset the event to the target's coordinate system and // dispatch the event. final float xc = scrolledXFloat - (float) target.mLeft; final float yc = scrolledYFloat - (float) target.mTop; ev.setLocation(xc, yc); if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) { ev.setAction(MotionEvent.ACTION_CANCEL); target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; mMotionTarget = null; } //如果没有拦截ACTION_MOVE, ACTION_DOWN的话,直接将Touch事件派发给target return target.dispatchTouchEvent(ev); } 1、dispatchTouchEvent作用:决定事件是否由onInterceptTouchEvent来拦截处理。 返回super.dispatchTouchEvent时,由onInterceptTouchEvent来决定事件的流向 返回false时,会继续分发事件,自己内部只处理了ACTION_DOWN 返回true时,不会继续分发事件,自己内部处理了所有事件(ACTION_DOWN,ACTION_MOVE,ACTION_UP) 2、onInterceptTouchEvent作用:拦截事件,用来决定事件是否传向子View 返回true时,拦截后交给自己的onTouchEvent处理 返回false时,拦截后交给子View来处理 3、onTouchEvent作用:事件最终到达这个方法 返回true时,内部处理所有的事件,换句话说,后续事件将继续传递给该view的onTouchEvent()处理 返回false时,事件会向上传递,由onToucEvent来接受,如果最上面View中的onTouchEvent也返回false的话,那么事件就会消失 5.总结 如果ViewGroup找到了能够处理该事件的View,则直接交给子View处理,自己的onTouchEvent不会被触发; 可以通过复写onInterceptTouchEvent(ev)方法,拦截子View的事件(即return true),把事件交给自己处理,则会执行自己对应的onTouchEvent方法。 子View可以通过调用getParent().requestDisallowInterceptTouchEvent(true); 阻止ViewGroup对其MOVE或者UP事件进行拦截; 一个点击事件产生后,它的传递过程如下: Activity->Window->View。顶级View接收到事件之后,就会按相应规则去分发事件。如果一个View的onTouchEvent方法返回false,那么将会交给父容器的onTouchEvent方法进行处理,逐级往上,如果所有的View都不处理该事件,则交由Activity的onTouchEvent进行处理。 如果某一个View开始处理事件,如果他不消耗ACTION_DOWN事件(也就是onTouchEvent返回false),则同一事件序列比如接下来进行ACTION_MOVE,则不会再交给该View处理。 ViewGroup默认不拦截任何事件。 诸如TextView、ImageView这些不作为容器的View,一旦接受到事件,就调用onTouchEvent方法,它们本身没有onInterceptTouchEvent方法。正常情况下,它们都会消耗事件(返回true),除非它们是不可点击的(clickable和longClickable都为false),那么就会交由父容器的onTouchEvent处理。 点击事件分发过程如下 dispatchTouchEvent—->OnTouchListener的onTouch方法—->onTouchEvent–>OnClickListener的onClick方法。也就是说,我们平时调用的setOnClickListener,优先级是最低的,所以,onTouchEvent或OnTouchListener的onTouch方法如果返回true,则不响应onClick方法…
static 静态修饰符 在程序中任何变量或者代码都是在编译时由系统自动分配内存来存储的。static修饰符表示静态的,在类加载时Jvm会把它放到方法区,被本类以及本类的所有实例所共用。在编译后所分配的内存会一直存在,直到程序退出内存才会释放这个空间。如果一个被所有实例共用的方法被申明为static,那么就可以节省空间,不用每个实例初始化的时候都被分配到内存。 Java类被加载过程 类装载器把一个类装入Java虚拟机中,要经过三个步骤来完成: ①加载(以二进制形式来生成Class对象) ②链接(又分为验证、准备和解析) 校验:检查导入类或接口的二进制数据的正确性; 准备:给类的静态变量分配并初始化存储空间; 解析:将符号引用转成直接引用; ③初始化(激活类的静态变量和静态代码块、初始化Java代码) 静态变量 Static关键字修饰成员变量被称为静态变量(也叫作类变量,同时 局部变量也能被声明为static),静态变量在内存中只有一个拷贝(节省内存,方便对象之间共享值),JVM只为静态分配一次内存,在加载类的过程中完成静态变量的内存分配,可用类名直接访问(当然也可以通过对象来访问)。因为静态变量被类的所有实例共用,所以非线程安全的。 未被Static修饰的成员变量叫作实例变量,每创建一个实例,就会为实例变量分配一次内存,实例变量在内存中可以有多个拷贝(但互相不影响,更加灵活)。 //静态变量的申明 private static int count = 0; public static String str; 静态方法 静态方法可以直接通过类名调用,任何的实例也都可以调用。 只能访问所属类的静态成员变量和成员方法,静态方法中也不能用this和super关键字。 类似于静态变量,静态方法也属于类,不属于实例的。 //静态方法的申明 public static void s(int param) { ...... } 静态代码块 静态代码块就是在类加载器加载对象时,要执行的一组语句。静态块只会在类加载到内存中的时候执行一次,位置可以随便放,如果static代码块有多个,JVM将按照它们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次。 static{ //在类被加载的时候用于初始化资源,仅能访问静态变量和静态方法 System.out.println("StaticExample static block"); } 静态类 只能在内部类中定义静态类,静态内部类与外层类绑定,即使没有创建外层类的对象,它一样存在。 静态类的方法可以是静态的方法也可以是非静态的方法,静态的方法可以在外层通过静态类调用,而非静态的方法必须要创建类的对象之后才能调用。 只能引用外部类的static成员变量(也就是类变量)。 如果一个内部类不是被定义成静态内部类,那么在定义成员变量或者成员方法的时候,是不能够被定义成静态的。 public class OuterClass { public static class InnerClass{ InnerClass(){ System.out.println("静态内部类"); } } } 版权声明:请尊重个人劳动成果,转载注明出处,谢谢!http://blog.csdn.net/amazing7/article/details/51251870
Activity是什么? 我们都知道Android中有四大组件(Activity 活动,Service 服务,Content Provider 内容提供者,BroadcastReceiver 广播接收器),Activity是我们用的最多也是最基本的组件,因为应用的所有操作都与用户相关,Activity 提供窗口来和用户进行交互。 官方文档这么说: An activity is a single, focused thing that the user can do. Almost all activities interact with the user, so the Activity class takes care of creating a window for you in which you can place your UI with setContentView(View). 大概的意思(原谅我): activity是独立平等的,用来处理用户操作。几乎所有的activity都是用来和用户交互的,所以activity类会创建了一个窗口,开发者可以通过setContentView(View)的接口把UI放到给窗口上。 Android中的activity全都归属于task管理 。task 是多个 activity 的集合,这些 activity 按照启动顺序排队存入一个栈(即“back stack”)。android默认会为每个App维持一个task来存放该app的所有activity,task的默认name为该app的packagename。 当然我们也可以在AndroidMainfest.xml中申明activity的taskAffinity属性来自定义task,但不建议使用,如果其他app也申明相同的task,它就有可能启动到你的activity,带来各种安全问题(比如拿到你的Intent)。 Activity的内部调用过程 上面已经说了,系统通过堆栈来管理activity,当一个新的activity开始时,它被放置在堆栈的顶部和成为运行活动,以前的activity始终保持低于它在堆栈,而不会再次到达前台,直到新的活动退出。 还是上这张官网的activity_lifecycle图: 首先打开一个新的activity实例的时候,系统会依次调用 onCreate() -> onStart() -> onResume() 然后开始running running的时候被覆盖了(从它打开了新的activity或是被锁屏,但是它依然在前台运行, lost focus but is still visible),系统调用onPause(); 该方法执行activity暂停,通常用于提交未保存的更改到持久化数据,停止动画和其他的东西。但这个activity还是完全活着(它保持所有的状态和成员信息,并保持连接到窗口管理器) 接下来它有三条出路 ①用户返回到该activity就调用onResume()方法重新running ②用户回到桌面或是打开其他activity,就会调用onStop()进入停止状态(保留所有的状态和成员信息,对用户不可见) ③系统内存不足,拥有更高限权的应用需要内存,那么该activity的进程就可能会被系统回收。(回收onRause()和onStop()状态的activity进程)要想重新打开就必须重新创建一遍。 如果用户返回到onStop()状态的activity(又显示在前台了),系统会调用 onRestart() -> onStart() -> onResume() 然后重新running 在activity结束(调用finish ())或是被系统杀死之前会调用onDestroy()方法释放所有占用的资源。 activity生命周期中三个嵌套的循环 activity的完整生存期会在 onCreate() 调用和 onDestroy() 调用之间发生。 activity的可见生存期会在 onStart() 调用和 onStop() 调用之间发生。系统会在activity的整个生存期内多次调用 onStart() 和onStop(), 因为activity可能会在显示和隐藏之间不断地来回切换。 activity的前后台切换会在 onResume() 调用和 onPause() 之间发生。 因为这个状态可能会经常发生转换,为了避免切换迟缓引起的用户等待,这两个方法中的代码应该相当地轻量化。 activity被回收的状态和信息保存和恢复过程 public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { if(savedInstanceState!=null){ //判断是否有以前的保存状态信息 savedInstanceState.get("Key"); } super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override protected void onSaveInstanceState(Bundle outState) { // TODO Auto-generated method stub //可能被回收内存前保存状态和信息, Bundle data = new Bundle(); data.putString("key", "last words before be kill"); outState.putAll(data); super.onSaveInstanceState(outState); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { // TODO Auto-generated method stub if(savedInstanceState!=null){ //判断是否有以前的保存状态信息 savedInstanceState.get("Key"); } super.onRestoreInstanceState(savedInstanceState); } } onSaveInstanceState方法 在activity 可能被回收之前 调用,用来保存自己的状态和信息,以便回收后重建时恢复数据(在onCreate()或onRestoreInstanceState()中恢复)。旋转屏幕重建activity会调用该方法,但其他情况在onRause()和onStop()状态的activity不一定会调用 ,下面是该方法的文档说明。 One example of when onPause and onStop is called and not this method is when a user navigates back from activity B to activity A: there is no need to call onSaveInstanceState on B because that particular instance will never be restored, so the system avoids calling it. An example when onPause is called and not onSaveInstanceState is when activity B is launched in front of activity A: the system may avoid calling onSaveInstanceState on activity A if it isn't killed during the lifetime of B since the state of the user interface of A will stay intact. 也就是说,系统灵活的来决定调不调用该方法,但是如果要调用就一定发生在onStop方法之前,但并不保证发生在onPause的前面还是后面。 onRestoreInstanceState方法 这个方法在onStart 和 onPostCreate之间调用,在onCreate中也可以状态恢复,但有时候需要所有布局初始化完成后再恢复状态。 onPostCreate:一般不实现这个方法,当程序的代码开始运行时,它调用系统做最后的初始化工作。 启动模式 启动模式什么? 简单的说就是定义activity 实例与task 的关联方式。 为什么要定义启动模式? 为了实现一些默认启动(standard)模式之外的需求: 让某个 activity 启动一个新的 task (而不是被放入当前 task ) 让 activity 启动时只是调出已有的某个实例(而不是在 back stack 顶创建一个新的实例) 或者,你想在用户离开 task 时只保留根 activity,而 back stack 中的其它 activity 都要清空 怎样定义启动模式? 定义启动模式的方法有两种: 使用 manifest 文件 在 manifest 文件中activity声明时,利用 activity 元素的 launchMode 属性来设定 activity 与 task 的关系。 <activity ...... android:launchMode="standard" > ....... </activity> 注意: 你用 launchMode 属性为 activity 设置的模式可以被启动 activity 的 intent 标志所覆盖。 有哪些启动模式? “standard” (默认模式) 当通过这种模式来启动Activity时, Android总会为目标 Activity创建一个新的实例,并将该Activity添加到当前Task栈中。这种方式不会启动新的Task,只是将新的 Activity添加到原有的Task中。 “singleTop” 该模式和standard模式基本一致,但有一点不同:当将要被启动的Activity已经位于Task栈顶时,系统不会重新创建目标Activity实例,而是直接复用Task栈顶的Activity。 “singleTask” Activity在同一个Task内只有一个实例。 如果将要启动的Activity不存在,那么系统将会创建该实例,并将其加入Task栈顶; 如果将要启动的Activity已存在,且存在栈顶,直接复用Task栈顶的Activity。 如果Activity存在但是没有位于栈顶,那么此时系统会把位于该Activity上面的所有其他Activity全部移出Task,从而使得该目标Activity位于栈顶。 “singleInstance” 无论从哪个Task中启动目标Activity,只会创建一个目标Activity实例且会用一个全新的Task栈来装载该Activity实例(全局单例). 如果将要启动的Activity不存在,那么系统将会先创建一个全新的Task,再创建目标Activity实例并将该Activity实例放入此全新的Task中。 如果将要启动的Activity已存在,那么无论它位于哪个应用程序,哪个Task中;系统都会把该Activity所在的Task转到前台,从而使该Activity显示出来。 使用 Intent 标志 在要启动 activity 时,你可以在传给 startActivity() 的 intent 中包含相应标志,以修改 activity 与 task 的默认关系。 Intent i = new Intent(this,NewActivity.class); i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(i); 可以通过标志修改的默认模式有哪些? FLAG_ACTIVITY_NEW_TASK 与”singleTask”模式相同,在新的 task 中启动 activity。如果要启动的 activity 已经运行于某 task 中,则那个 task 将调入前台。 FLAG_ACTIVITY_SINGLE_TOP 与 “singleTop”模式相同,如果要启动的 activity位于back stack 顶,系统不会重新创建目标Activity实例,而是直接复用Task栈顶的Activity。 FLAG_ACTIVITY_CLEAR_TOP 此种模式在launchMode中没有对应的属性值。如果要启动的 activity 已经在当前 task 中运行,则不再启动一个新的实例,且所有在其上面的 activity 将被销毁。 关于启动模式的一些建议 一般不要改变 activity 和 task 默认的工作方式。 如果你确定有必要修改默认方式,请保持谨慎,并确保 activity 在启动和从其它 activity 返回时的可用性,多做测试和安全方面的工作。 Intent Filter android的3个核心组件——Activity、services、广播接收器——是通过intent传递消息的。intent消息用于在运行时绑定不同的组件。 在 Android 的 AndroidManifest.xml 配置文件中可以通过 intent-filter 节点为一个 Activity 指定其 Intent Filter,以便告诉系统该 Activity 可以响应什么类型的 Intent。 intent-filter 的三大属性 Action 一个 Intent Filter 可以包含多个 Action,Action 列表用于标示 Activity 所能接受的“动作”,它是一个用户自定义的字符串。 <intent-filter > <action android:name="android.intent.action.MAIN" /> <action android:name="com.scu.amazing7Action" /> …… </intent-filter> 在代码中使用以下语句便可以启动该Intent 对象: Intent i=new Intent(); i.setAction("com.scu.amazing7Action"); Action 列表中包含了“com.scu.amazing7Action”的 Activity 都将会匹配成功 URL 在 intent-filter 节点中,通过 data节点匹配外部数据,也就是通过 URI 携带外部数据给目标组件。 <data android:mimeType="mimeType" android:scheme="scheme" android:host="host" android:port="port" android:path="path"/> 注意:只有data的所有的属性都匹配成功时 URI 数据匹配才会成功 Category 为组件定义一个 类别列表,当 Intent 中包含这个类别列表的所有项目时才会匹配成功。 <intent-filter . . . > <action android:name="code android.intent.action.MAIN" /> <category android:name="code android.intent.category.LAUNCHER" /> </intent-filter> Activity 种 Intent Filter 的匹配过程 ①加载所有的Intent Filter列表 ②去掉action匹配失败的Intent Filter ③去掉url匹配失败的Intent Filter ④去掉Category匹配失败的Intent Filter ⑤判断剩下的Intent Filter数目是否为0。如果为0查找失败返回异常;如果大于0,就按优先级排序,返回最高优先级的Intent Filter 开发中Activity的一些问题 一般设置Activity为非公开的 <activity ...... android:exported="false" /> 注意:非公开的Activity不能设置intent-filter,以免被其他activity唤醒(如果拥有相同的intent-filter)。 不要指定activity的taskAffinity属性 不要设置activity的LaunchMode(保持默认) 注意Activity的intent最好也不要设定为FLAG_ACTIVITY_NEW_TASK 在匿名内部类中使用this时加上activity类名(类名.this,不一定是当前activity) 设置activity全屏 在其 onCreate()方法中加入: // 设置全屏模式 getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); // 去除标题栏 requestWindowFeature(Window.FEATURE_NO_TITLE); 版权声明:请尊重个人劳动成果,转载注明出处,谢谢! http://my.csdn.net/Amazing7
版权声明:请尊重个人劳动成果,转载注明出处,谢谢! 目录(?)[+] 首先的明白Java中锁的机制 synchronized 在修饰代码块的时候需要一个reference对象作为锁的对象. 在修饰方法的时候默认是当前对象作为锁的对象. 在修饰类时候默认是当前类的Class对象作为锁的对象. 线程同步的方法:sychronized、lock、reentrantLock分析 方法锁(synchronized修饰方法时) 通过在方法声明中加入 synchronized关键字来声明 synchronized 方法。 synchronized 方法控制对类成员变量的访问: 每个类实例对应一把锁,每个 synchronized 方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个类实例,其所有声明为 synchronized 的成员函数中至多只有一个处于可执行状态,从而有效避免了类成员变量的访问冲突。 对象锁(synchronized修饰方法或代码块) 当一个对象中有synchronized method或synchronized block的时候调用此对象的同步方法或进入其同步区域时,就必须先获得对象锁。如果此对象的对象锁已被其他调用者占用,则需要等待此锁被释放。(方法锁也是对象锁) java的所有对象都含有1个互斥锁,这个锁由JVM自动获取和释放。线程进入synchronized方法的时候获取该对象的锁,当然如果已经有线程获取了这个对象的锁,那么当前线程会等待;synchronized方法正常返回或者抛异常而终止,JVM会自动释放对象锁。这里也体现了用synchronized来加锁的1个好处,方法抛异常的时候,锁仍然可以由JVM来自动释放。 对象锁的两种形式: public class Test { // 对象锁:形式1(方法锁) public synchronized void Method1() { System.out.println("我是对象锁也是方法锁"); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } // 对象锁:形式2(代码块形式) public void Method2() { synchronized (this) { System.out.println("我是对象锁"); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } } 类锁(synchronized 修饰静态的方法或代码块) 由于一个class不论被实例化多少次,其中的静态方法和静态变量在内存中都只有一份。所以,一旦一个静态的方法被申明为synchronized。此类所有的实例化对象在调用此方法,共用同一把锁,我们称之为类锁。 对象锁是用来控制实例方法之间的同步,类锁是用来控制静态方法(或静态变量互斥体)之间的同步。 类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定实例方法和静态方法的区别的。 java类可能会有很多个对象,但是只有1个Class对象,也就是说类的不同实例之间共享该类的Class对象。Class对象其实也仅仅是1个java对象,只不过有点特殊而已。由于每个java对象都有1个互斥锁,而类的静态方法是需要Class对象。所以所谓的类锁,不过是Class对象的锁而已。获取类的Class对象有好几种,最简单的就是[类名.class]的方式。 public class Test { // 类锁:形式1 public static synchronized void Method1() { System.out.println("我是类锁一号"); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } // 类锁:形式2 public void Method2() { synchronized (Test.class) { System.out.println("我是类锁二号"); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } }
版权声明:请尊重个人劳动成果,转载注明出处,谢谢! 目录(?)[+] 什么是host? Hosts是一个没有扩展名的系统文件,,记录了一些网站的IP地址和域名的对应关系.可以用记事本等工具打开,其作用就是将一些常用的网址域名与其对应的IP地址建立一个关联“数据库”,当用户在浏览器中输入一个需要登录的网址时,系统会首先自动从Hosts文件中寻找对应的IP地址,如果在hosts文件中存在这个对应关系的时候,域名解析优先生效,域名解析不再请求域名服务器,系统会立即打开对应网页;否则,系统将网址提交DNS域名解析服务器进行IP地址的解析。 效果图 如何做? ①先找到你对应系统的host文件 hosts所在文件夹: Windows 系统hosts位于 C:\Windows\System32\drivers\etc\hosts Android(安卓 须root)系统hosts位于 /system/etc/hosts Mac(苹果电脑)系统hosts跟Linux一样位于 /etc/hosts iPhone(iOS 需越狱)系统hosts跟Linux Mac一样位于 /etc/hosts Linux系统hosts位于 /etc/hosts ② 做备份(万一那啥了是吧) Windows host 备份: # Copyright (c) 1993-2009 Microsoft Corp. # # This is a sample HOSTS file used by Microsoft TCP/IP for Windows. # # This file contains the mappings of IP addresses to host names. Each # entry should be kept on an individual line. The IP address should # be placed in the first column followed by the corresponding host name. # The IP address and the host name should be separated by at least one # space. # # Additionally, comments (such as these) may be inserted on individual # lines or following the machine name denoted by a '#' symbol. # # For example: # # 102.54.94.97 rhino.acme.com # source server # 38.25.63.10 x.acme.com # x client host # localhost name resolution is handled within DNS itself. # 127.0.0.1 localhost # ::1 localhost ③ 百度(google hosts 最新)下载,因为有些ip会失效(我还没有失效过)。 我在(http://htcui.com/4938.html)下载(下载在最下面) ④直接替换系统的host文件(修改时应当注意权限问题,建议关闭360等安全软件) Windows : ⑤刷新 DNS 缓存(其实我没刷新也可以用) Windows 开始 -> 运行 -> 输入cmd -> 在CMD窗口输入 ipconfig /flushdns Linux 终端输入 sudo rcnscd restart Linux(systemd发行版) sudo systemctl restart NetworkManager Mac OS X 终端输入 sudo killall -HUP mDNSResponder Android 和 IOS 重启 ⑥ 建议使用chrome浏览器(IE也可以访问比较慢 Chrome 浏览器各版本下载),并在地址栏里输入chrome://flags/,然后查找QUIC(把chrome设置成英文的),启用该协议,同时建议启用SPDY/4,能让访问更流畅。 其实很多网站都可以访问,你可以试试,比如: wikipedia、ccFox.info、ProjectH、Battle.NET 、WordPress、Microsoft Live、GitHub、Amazon、Box.com、SoundCloud、inoreader、Feedly、FlipBoard、Twitter、Facebook、Flickr、imgur、DuckDuckGo、Ixquick、Google Services、Google apis、Android、Youtube、Google Drive、UpLoad、Appspot、Googl eusercontent、Gstatic、Google other、Google Play 接下来就享受谷歌吧!
版权声明:请尊重个人劳动成果,转载注明出处,谢谢! 目录(?)[+] 接口(Interface) 是抽象方法的集合,接口通常以interface来声明。 一个类通过继承接口的方式,从而来继承接口的抽象方法。 接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。 类描述对象的属性和方法。接口则包含类要实现的方法。 除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。 接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法(所有方法都是抽象的方法),否则就必须声明为抽象类。 接口没有构造方法,支持多重继承,不能包含成员变量,除了static和final变量。 接口的申明方法 [可见度] interface 接口名称 [extends 其他的类名] { //任何类型 final, static 字段 public [返回类型] [函数名] (); // 抽象方法(所有都是 public) } 接口是隐式抽象的,当声明一个接口和接口中方法的时候,不必使用abstract关键字。接口中的方法都是公有的。 类使用implements关键字实现接口。在类声明中,Implements关键字放在class声明后面。 ... implements 接口名称[, 其他接口, 其他接口..., ...] ... 重写接口中的方法时,需注意: 类在实现接口的方法时,不能抛出强制性异常,只能在接口中,或者继承接口的抽象类 中抛出该强制性异常。 类在重写方法时要保持一致的方法名,并且应该保持相同或者相兼容的返回值类型。 一个类只能继承一个类,但是能实现多个接口,同时一个接口能继承另一个接口。 一个接口能继承另一个接口,和类之间的继承方式比较相似。接口的继承使用 extends 关键字,子接口继承父接口的方法。 抽象类 (Abstract) 在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。 抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。抽象类必须被继承,才能被使用。(它没有足够信息描绘一个对象,只有等子类来继承完善后才能使用) 在Java语言中使用abstract class来定义抽象类。 public abstract class Employee { //成员变量 //成员方法,可以是抽象的或实现的(可以没有抽象方法) [访问权限修饰符] abstract [返回类型] [函数名](); } 声明抽象方法会造成以下两个结果: 如果一个类包含抽象方法,那么该类必须是抽象类。但是一个抽象类可以没有抽象方法,只是把类申明为抽象的。 任何子类必须重写父类的抽象方法(一句话抽象方法都要重写,只不过接口的方法全部都是抽象的而已),或者声明自身为抽象类。 区别 接口不是类,抽象类是一个功能不齐全的类,都不能实例化对象。 一个类可以实现(implements)多个接口。一个类只能继承(extends)一个抽象类。 接口没有构造函数,所有方法都是 public abstract的,一般不定义成员变量。(所有的成员变量都是 static final ,而且必须显示初始化)。 抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。 一个实现接口的类,必须实现接口内所描述的所有方法(所有方法都是抽象的方法),否则就必须声明为抽象类。 如果一个类包含抽象方法,那么该类必须是抽象类。任何子类必须重写父类的抽象方法,或者声明自身为抽象类。
版权声明:请尊重个人劳动成果,转载注明出处,谢谢! 多态 同一个类的不同表现形态,不同的形态是通过其不同的子类体现 Java通过将子类对象引用赋值给超类对象变量, 来实现动态方法调用。 面向对象的三个特征与含义 下面看例子: public class A{ public String name = "父类name"; public void move(){ System.out.println("父类move"); } } public class B extends A{ public String name = "子类name"; @Override public void move() { // TODO Auto-generated method stub System.out.println("子类move"); } } public class Test { public static void main(String[] args) { A a = new B(); a.move(); } } 类B是类A的子类, A a = new B() 编译时变量和运行时变量不一样,所以多态发生了。 ① A a 作为一个引用类型数据,存储在JVM栈的本地变量表中。 ② new B()作为实例对象数据存储在堆中 B的对象实例数据(接口、方法、field、对象类型等)的地址也存储在堆中 B的对象的类型数据(对象实例数据的地址所执行的数据)存储在方法区中,方法区中 对象类型数据 中有一个指向该类方法的方法表。 ③Java虚拟机规范中并未对引用类型访问具体对象的方式做规定,目前主流的实现方式主要有两种: 1. 通过句柄访问 在这种方式中,JVM堆中会专门有一块区域用来作为句柄池,存储相关句柄所执行的实例数据地址(包括在堆中地址和在方法区中的地址)。这种实现方法由于用句柄表示地址,因此十分稳定。 2.通过直接指针访问 通过直接指针访问的方式中,reference中存储的就是对象在堆中的实际地址,在堆中存储的对象信息中包含了在方法区中的相应类型数据。这种方法最大的优势是速度快,在HotSpot虚拟机中用的就是这种方式。 ④实现过程 首先虚拟机通过reference类型(A的引用)查询java栈中的 本地变量表,得到堆中的 对象类型数据的地址,从而找到方法区中的 对象类型数据(B的对象类型数据) ,然后查询方法表定位到实际类(B类)的方法运行。
版权声明:请尊重个人劳动成果,转载注明出处,谢谢!http://blog.csdn.net/amazing7/article/details/51225610 重写(Override) 重写是子类对父类的允许访问的方法的实现过程进行重新编写! 返回值和形参都不能改变。即外壳不变,重写内在实现! 重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。 在面向对象原则里,重写意味着可以重写任何现有方法。实例如下: class Animal{ public void move(){ System.out.println("Animal can move"); } } class Pig extends Animal{ public void move(){ System.out.println("Pig can Climb tree"); } } public class Test{ public static void main(String args[]){ Animal a = new Animal(); Animal b = new Pig (); a.move();// 执行 Animal 类的方法 b.move();//执行 Pig 类的方法 } } 以上实例编译运行结果如下: Animal can move Pig can Climb tree 从上面的例子中可以看出,b 申明的是Animal类 的引用,但运行的是Pig类的move方法。 这是因为由于在编译阶段,只是检查参数的引用类型。 然而在运行时,Java虚拟机(JVM)指定对象的类型并且运行该对象的方法。 在上面的例子中,之所以能编译成功,是因为Animal类中存在move方法,然而运行时,运行的是特定对象(Pig 对象)的方法。 看下面的例子: class Animal{ public void move(){ System.out.println("Animal can move"); } } class Pig extends Animal{ public void move(){ System.out.println("Pig can Climb tree"); } public void bark(){ System.out.println("Pig can bray"); } } public class Test{ public static void main(String args[]){ Animal a = new Animal(); Animal b = new Pig(); a.move();// 执行 Animal 类的方法 b.move();//执行 Pig 类的方法 b.bark(); } } 以上实例编译运行结果会报如下错误: Test.java:30: cannot find symbol symbol : method bark() location: class Animal b.bark(); ^ 这是一个编译错误,因为b的引用类型Animal 类中没有bark方法。 方法的重写规则 参数列表必须完全与被重写方法的相同; 返回类型必须完全与被重写方法的返回类型相同; 访问权限不能比父类中被重写的方法的访问权限更高。例如:如果父类的一个方法被声明为public,那么在子类中重写该方法就不能声明为protected 或 private。 父类的成员方法只能被它的子类重写。 声明为final的方法不能被重写。 声明为static的方法不能被重写,但是能够被再次声明。 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为private和final的方法。 子类和父类不在同一个包中,那么子类只能够重写父类的声明为public和protected的非final方法。 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。 但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。 构造方法不能被重写。 如果不能继承一个方法,则不能重写这个方法( 父类的private方法)。 重载(Overload) 重载(overloading) 是在同一个类里面,方法名字相同,而参数不同,返回类型可以相同也可以不同的多个方法。 每个重载的方法(只能重载构造函数)都必须有一个独一无二的参数类型列表。 重载规则: 被重载的方法必须改变参数列表; 被重载的方法可以改变返回类型; 被重载的方法可以改变访问修饰符; 被重载的方法可以声明新的或更广的检查异常; 方法能够在同一个类中或者在一个子类中被重载。 public class Test{ public int test(){ System.out.println("Overload1"); return 1; } public void test(int a){ System.out.println("Overload2"); } //以下两个参数类型顺序不同 public String test(int a,String s){ return "Overload3" + s; } public String test(String s,int a){ return "Overload4"+s; } public static void main(String[] args){ Test t = new Test(); System.out.println(t.test()); t.test(1); System.out.println(t.test(1,"test3")); System.out.println(t.test("test4",1)); } } 重写与重载之间的区别 重载方法: 参数列表 必须修改 返回类型 可以修改 异常 可以修改 访问修饰符 可以修改 重写方法: 参数列表 不能改 返回类型 不能改 异常 可以减少或删除,不能抛出新的或更广的异常 访问修饰符 只能降低限制,不能变成高限制 限制由低到高 : public 、protected 、private
版权声明:请尊重个人劳动成果,转载注明出处,谢谢! 目录(?)[+] 生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。 生产者消费者模式的优点 - 解耦 - 支持并发 - 支持忙闲不均 解决方法可分为两类: (1)用信号量和锁机制实现生产者和消费者之间的同步; - wait() / notify()方法 - await() / signal()方法 - BlockingQueue阻塞队列方法 - Semaphore方法 (2)在生产者和消费者之间建立一个管道。(一般不使用,缓冲区不易控制、数据不易封装和传输) - PipedInputStream / PipedOutputStream 1 wait() / notify()方法实现 wait() / nofity()方法是Object里面的两个方法(Object有哪些公用方法?),所有Object的子类都可以使用这两个方法。 wait()方法: 在其他线程调用此对象的 notify() 方法前,导致当前线程等待。当前线程必须拥有此对象监视器。该线程发布对此监视器的所有权并等待,直到其他线程通过调用 notify 方法,或 notifyAll 方法通知在此对象的监视器上等待的线程醒来。然后该线程将等到重新获得对监视器的所有权后才能继续执行。 此方法只应由作为此对象监视器的所有者的线程来调用。 notify()方法: 唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。 直到当前线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。 此方法只应由作为此对象监视器的所有者的线程来调用。通过以下三种方法之一,线程可以成为此对象监视器的所有者: -通过执行此对象的同步实例方法。 -通过执行在此对象上进行同步的 synchronized 语句的正文。 -对于 Class 类型的对象,可以通过执行该类的同步静态方法。 一次只能有一个线程拥有对象的监视器。 代码示例: public class Test { private static Integer count = 0; private final Integer FULL = 5; private static String lock = "lock"; public static void main(String[] args) { Test t = new Test(); new Thread(t.new Producer()).start(); new Thread(t.new Consumer()).start(); new Thread(t.new Producer()).start(); new Thread(t.new Consumer()).start(); } class Producer implements Runnable { @Override public void run() { for (int i = 0; i < 5; i++) { try { Thread.sleep(1000); } catch (InterruptedException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } synchronized (lock) { while (count == FULL) { try { lock.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } count++; System.out.println(Thread.currentThread().getName() + "produce:: " + count); lock.notifyAll(); } } } } class Consumer implements Runnable { @Override public void run() { for (int i = 0; i < 5; i++) { try { Thread.sleep(1000); } catch (InterruptedException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } synchronized (lock) { while (count == 0) { try { lock.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } count--; System.out.println(Thread.currentThread().getName()+ "consume:: " + count); lock.notifyAll(); } } } } } 对象的监视器对锁对象的锁定(也就是代码中的lock对象),注意是调用锁对象的wait() / nofity()方法。 运行结果: 2 await() / signal()方法 await()/signal()是对wait()/notify()的改进,功能更加强大,更适用于高级用户,synchronized是托管给JVM执行的,而lock是Java写的控制锁的代码。 ReentrantLock(实现lock接口)相对于synchronized多了三个高级功能: ①等待可中断 在持有锁的线程长时间不释放锁的时候,等待的线程可以选择放弃等待. tryLock(long timeout, TimeUnit unit) ②公平锁 按照申请锁的顺序来一次获得锁称为公平锁.synchronized的是非公平锁,ReentrantLock可以通过构造函数实现公平锁. new RenentrantLock(boolean fair) 公平锁和非公平锁。这2种机制的意思从字面上也能了解个大概:即对于多线程来说,公平锁会依赖线程进来的顺序,后进来的线程后获得锁。而非公平锁的意思就是后进来的锁也可以和前边等待锁的线程同时竞争锁资源。对于效率来讲,当然是非公平锁效率更高,因为公平锁还要判断是不是线程队列的第一个才会让线程获得锁。 ③绑定多个Condition 通过多次newCondition可以获得多个Condition对象,可以简单的实现比较复杂的线程同步的功能.通过await(),signal(); 一般情况下都是用synchronized原语实现同步,除非下列情况使用ReentrantLock ①某个线程在等待一个锁的控制权的这段时间需要中断 ②需要分开处理一些wait-notify,ReentrantLock里面的Condition应用,能够控制notify哪个线程 ③具有公平锁功能,每个到来的线程都将排队等候 更多了解:线程同步的方法:sychronized、lock、reentrantLock分析 下面是使用ReentrantLock来实现生产者消费者问题 代码示例: public class Test { private static Integer count = 0;//缓冲区 private final Integer FULL = 5; final Lock lock = new ReentrantLock(); //获得可重入锁 final Condition put = lock.newCondition(); final Condition get = lock.newCondition(); public static void main(String[] args) { Test t = new Test(); new Thread(t.new Producer()).start(); new Thread(t.new Consumer()).start(); new Thread(t.new Consumer()).start(); new Thread(t.new Producer()).start(); } class Producer implements Runnable { @Override public void run() { for (int i = 0; i < 5; i++) { try { Thread.sleep(1000); } catch (InterruptedException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } lock.lock(); try { while (count == FULL) { try { put.await(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } count++; System.out.println(Thread.currentThread().getName() + "produce:: " + count); get.signal(); } finally { lock.unlock(); } } } } class Consumer implements Runnable { @Override public void run() { for (int i = 0; i < 5; i++) { try { Thread.sleep(1000); } catch (InterruptedException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } lock.lock(); try { while (count == 0) { try { get.await(); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } } count--; System.out.println(Thread.currentThread().getName()+ "consume:: " + count); put.signal(); } finally { lock.unlock(); } } } } } 运行结果: 3 BlockingQueue阻塞队列方法 BlockingQueue 实现主要用于生产者-使用者队列,但它另外还支持 Collection 接口。是线程安全的,所有排队方法都可以使用内部锁或其他形式的并发控制来自动达到它们的目的。 BlockingQueue 方法以四种形式出现,对于不能立即满足但可能在将来某一时刻可以满足的操作,这四种形式的处理方式不同:第一种是抛出一个异常,第二种是返回一个特殊值(null 或 false,具体取决于操作),第三种是在操作可以成功前,无限期地阻塞当前线程,第四种是在放弃前只在给定的最大时间限制内阻塞。下表中总结了这些方法: 主要说说用于阻塞的那个方法 put()方法:将指定元素插入此队列中,将等待可用的空间(如果有必要)。 take()方法:获取并移除此队列的头部,在指定的等待时间前等待可用的元素(如果有必要)。 代码示例: public class Test { private static Integer count = 0; final BlockingQueue<Integer> bq = new ArrayBlockingQueue<Integer>(5);//容量为5的阻塞队列 public static void main(String[] args) { Test t = new Test(); new Thread(t.new Producer()).start(); new Thread(t.new Consumer()).start(); new Thread(t.new Consumer()).start(); new Thread(t.new Producer()).start(); } class Producer implements Runnable { @Override public void run() { for (int i = 0; i < 5; i++) { try { Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } try { bq.put(1); count++; System.out.println(Thread.currentThread().getName() + "produce:: " + count); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } class Consumer implements Runnable { @Override public void run() { for (int i = 0; i < 5; i++) { try { Thread.sleep(1000); } catch (InterruptedException e1) { e1.printStackTrace(); } try { bq.take(); count--; System.out.println(Thread.currentThread().getName()+ "consume:: " + count); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } } } } } 运行结果: 4 Semaphore方法实现同步 信号量(Semaphore)维护了一个许可集。在许可可用前会阻塞每一个 acquire(),然后再获取该许可。每个 release() 添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采取相应的行动。 Semaphore 通常用于限制可以访问某些资源(物理或逻辑的)的线程数目。 注意,调用 acquire() 时无法保持同步锁,因为这会阻止将项返回到池中。信号量封装所需的同步,以限制对池的访问,这同维持该池本身一致性所需的同步是分开的。 代码示例: public class Test { int count = 0; final Semaphore put = new Semaphore(5);//初始令牌个数 final Semaphore get = new Semaphore(0); final Semaphore mutex = new Semaphore(1); public static void main(String[] args) { Test t = new Test(); new Thread(t.new Producer()).start(); new Thread(t.new Consumer()).start(); new Thread(t.new Consumer()).start(); new Thread(t.new Producer()).start(); } class Producer implements Runnable { @Override public void run() { for (int i = 0; i < 5; i++) { try { Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } try { put.acquire();//注意顺序 mutex.acquire(); count++; System.out.println(Thread.currentThread().getName() + "produce:: " + count); } catch (Exception e) { e.printStackTrace(); } finally { mutex.release(); get.release(); } } } } class Consumer implements Runnable { @Override public void run() { for (int i = 0; i < 5; i++) { try { Thread.sleep(1000); } catch (InterruptedException e1) { e1.printStackTrace(); } try { get.acquire();//注意顺序 mutex.acquire(); count--; System.out.println(Thread.currentThread().getName()+ "consume:: " + count); } catch (Exception e) { e.printStackTrace(); } finally { mutex.release(); put.release(); } } } } } 运行结果: 注意同步令牌(notFull.acquire())必须在互斥令牌(mutex.acquire())前面获得,如果先得到互斥锁再发生等待,会造成死锁。 5 PipedInputStream / PipedOutputStream 这个类位于java.io包中,是解决同步问题的最简单的办法,一个线程将数据写入管道,另一个线程从管道读取数据,这样便构成了一种生产者/消费者的缓冲区编程模式。PipedInputStream/PipedOutputStream只能用于多线程模式,用于单线程下可能会引发死锁。 代码示例: public class Test { final PipedInputStream pis = new PipedInputStream(); final PipedOutputStream pos = new PipedOutputStream(); public static void main(String[] args) { Test t = new Test(); new Thread(t.new Producer()).start(); new Thread(t.new Consumer()).start(); } class Producer implements Runnable{ @Override public void run() { try { pis.connect(pos); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } try { while(true){ //不断的产生数据 int n = (int)(Math.random()*255); System.out.println(Thread.currentThread().getName()+"produce::"+n); pos.write(n); pos.flush(); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally { try { pis.close(); pos.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } class Consumer implements Runnable{ @Override public void run() { int n; try { while(true){ n = pis.read(); System.out.println(Thread.currentThread().getName()+"consume::"+n); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ try { pis.close(); pos.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } } 运行结果: 从结果上看出也可以实现同步,但一般不使用,因为缓冲区不易控制、数据不易封装和传输。
版权声明:请尊重个人劳动成果,转载注明出处,谢谢! Java提供了两种创建线程方法: 通过实现Runable接口; 通过继承Thread类本身。 1 .声明实现 Runnable 接口的类,该类然后实现 run 方法。然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动。例如,计算大于某一规定值的质数的线程可以写成: class PrimeRun implements Runnable { long minPrime; PrimeRun(long minPrime) { this.minPrime = minPrime; } public void run() { // compute primes larger than minPrime . . . } } 然后,下列代码会创建并启动一个线程: PrimeRun p = new PrimeRun(143); new Thread(p).start(); 2.将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。接下来可以分配并启动该子类的实例。 class PrimeThread extends Thread { long minPrime; PrimeThread(long minPrime) { this.minPrime = minPrime; } public void run() { // compute primes larger than minPrime . . . } } 然后,下列代码会创建并启动一个线程: PrimeThread p = new PrimeThread(143); p.start(); 当 Java 虚拟机启动时,通常都会有单个非守护线程(它通常会调用某个指定类的 main 方法)。Java 虚拟机会继续执行线程,直到下列任一情况出现时为止: 调用了 Runtime 类的 exit 方法,并且安全管理器允许退出操作发生。 非守护线程的所有线程都已停止运行,无论是通过从对 run 方法的调用中返回,还是通过抛出一个传播到 run 方法之外的异常。 3.使用和区别 Runable源码: public interface Runnable { /** * When an object implementing interface <code>Runnable</code> is used * to create a thread, starting the thread causes the object's * <code>run</code> method to be called in that separately executing * thread. * <p> * The general contract of the method <code>run</code> is that it may * take any action whatsoever. * * @see java.lang.Thread#run() */ public abstract void run(); } Thread 类实现了 Runnable。激活的意思是说某个线程已启动并且尚未停止。此外,Runnable 为非 Thread 子类的类提供了一种激活方式。通过实例化某个 Thread 实例并将自身作为运行目标,就可以运行实现 Runnable 的类而无需创建 Thread 的子类。大多数情况下,如果只想重写 run() 方法,而不重写其他 Thread 方法,那么应使用 Runnable 接口。这很重要,因为除非程序员打算修改或增强类的基本行为,否则不应为该类创建子类。 继承Thread类实现多线程,要求放入多线程中的类不能继承其他类(Java的单继承特性),如果需要请用 Runnable 实现(接口可以多实现并不影响继承其他类)。 一个实现Runnable接口的类可以放在多个线程中执行,多个线程可以去执行同一资源;而继承Thread只能实现多个线程分别去处理自己的资源。(通过Runnable创建的多个线程可以由编程人员传入同一个Runnable对象,即执行同一个run方法,而通过Thread创建的多线程它们运行的都是自己的run方法)。
版权声明:请尊重个人劳动成果,转载注明出处,谢谢! 对象 是类的一个实例(对象不是找个女朋友),有状态和行为。例如,一条狗是一个对象,它的状态有:颜色、名字、品种;行为有:摇尾巴、叫、吃等。 类 是一个模板,它描述一类对象的行为和状态。 1 . 封装性 将对象的状态信息尽可能的隐藏在对象内部,只保留有限的接口和方法与外界进行交互,从而避免了外界对对象内部属性的破坏。 Java中使用访问控制符来保护对类、变量、方法和构造方法的访问。 Java支持4种不同的访问权限。 默认的,也称为default,在同一包内可见,不使用任何修饰符。 私有的,以private修饰符指定,在同一类内可见。 共有的,以public修饰符指定,对所有类可见。 受保护的,以protected修饰符指定,对同一包内的类和所有子类可见。 2. 继承 java通过继承创建分等级层次的类,可以理解为一个对象从另一个对象获取属性的过程。 类的继承是单一继承,也就是说,一个子类只能拥有一个父类 下面的做法是不合法的: public class extends Animal, Mammal{} 但是我们可以用多继承接口来实现, 如: public class Apple extends Fruit implements Fruit1, Fruit2{} 继承中最常使用的两个关键字是extends(用于基本类和抽象类)和implements(用于接口)。 注意:子类拥有父类所有的成员变量,但对于父类private的成员变量却没有访问权限,这保障了父类的封装性。 下面是使用关键字extends实现继承。 public class Animal{ } public class Mammal extends Animal{ } public class Reptile extends Animal{ } public class Dog extends Mammal{ } 通过使用关键字extends,子类可以继承父类所有的方法和属性,但是无法使用 private(私有) 的方法和属性。 我们通过使用instanceof 操作符能够确定一个对象是另一个对象的一个分类。 public class Dog extends Mammal{ public static void main(String args[]){ Animal a = new Animal(); Mammal m = new Mammal(); Dog d = new Dog(); System.out.println(m instanceof Animal); System.out.println(d instanceof Mammal); System.out.println(d instanceof Animal); } } 结果如下: true true true Implements关键字使用在类继承接口的情况下, 这种情况不能使用关键字extends。 public interface Animal {} public class Mammal implements Animal{ } public class Dog extends Mammal{ } 可以使用 instanceof 运算符来检验Mammal和dog对象是否是Animal类的一个实例。 interface Animal{} class Mammal implements Animal{} public class Dog extends Mammal{ public static void main(String args[]){ Mammal m = new Mammal(); Dog d = new Dog(); System.out.println(m instanceof Animal); System.out.println(d instanceof Mammal); System.out.println(d instanceof Animal); } } 运行结果如下: true true true 3.多态 多态是同一个行为具有多个不同表现形式或形态的能力。 多态性是对象多种表现形式的体现 比如:我到宠物店说”请给我一只宠物”,服务员给我小猫、小狗或者蜥蜴都可以,我们就说”宠物”这个对象就具备多态性。 例子 public interface Vegetarian{} public class Animal{} public class Deer extends Animal implements Vegetarian{} 因为Deer类具有多重继承,所以它具有多态性。 访问一个对象的唯一方法就是通过引用型变量 (编译时变量)。 引用型变量只能有一种类型,一旦被声明,引用型变量的类型就不能被改变了。 引用型变量不仅能够被重置为其他对象,前提是这些对象没有被声明为final。还可以引用和它类型相同的或者相兼容的对象。它可以声明为类类型或者接口类型。 Deer d = new Deer(); Animal a = d; Vegetarian v = d; Object o = d; 所有的引用型变量d,a,v,o都指向堆中相同的Deer对象。 我们来看下面这个例子: public class Animal { public String name = "父类name"; public void move(){ System.out.println("父类move"); } public void content(){ System.out.println("父类content"); } } public class Bird extends Animal{ public String name = "子类name"; @Override public void move() { // TODO Auto-generated method stub System.out.println("子类move"); } public void content(){ System.out.println("子类content"); } } public class Test { public static void main(String[] args) { Animal a = new Animal(); System.out.println(a.name); a.move(); a.content(); System.out.println("----------------------"); Animal b = new Bird(); //向上转型由系统自动完成 //编译时变量 运行时变量 System.out.println(b.name); b.move(); b.content(); System.out.println("----------------------"); Bird c = new Bird(); System.out.println(c.name); c.move(); c.content(); } } 运行结果: 父类name 父类move 父类content ---------------------- 父类name 子类move 子类content ---------------------- 子类name 子类move 子类content 说明:Bird类继承Animal并重写了其方法。 因为Animal b = new Bird(),编译时变量和运行时变量不一样,所以多态发生了。可以从最后的运行结果中看出,调用了父类的成员变量name和子类重写后的两个方法。 上面继承说了,子类可以调用父类所有非private的方法和属性。因为name是一个String的对象,与方法不同,对象的域不具有多态性。通过引用变量来访问其包含的实例变量时,系统总是视图访问它编译时类型所定义的变量,而不是他运行时类型所定义的变量。 那么问题来了,如果我们把Animal的成员变量换成private,那会不会去调用Bird类的成员变量name来打印输出呢? 也就是说 系统访问的始终是去访问编译时类型所定义的变量。 重写定义:子类对父类的允许访问的方法的实现过程进行重新编写!返回值和形参都不能改变。即外壳不变,核心重写!
版权声明:请尊重个人劳动成果,转载注明出处,谢谢! Java 异常类继承关系图 (一)Throwable Throwable 类是 Java 语言中所有错误或异常的超类。只有当对象是此类或其子类之一的实例时,才能通过 Java 虚拟机或者 Java throw 语句抛出,才可以是 catch 子句中的参数类型。 Throwable 类及其子类有两个构造方法,一个不带参数,另一个带有 String 参数,此参数可用于生成详细消息。 Throwable 包含了其线程创建时线程执行堆栈的快照。它还包含了给出有关错误更多信息的消息字符串。 Java将可抛出(Throwable)的结构分为三种类型: 错误(Error) 运行时异常(RuntimeException) 被检查的异常(Checked Exception) 1.Error Error 是 Throwable 的子类,用于指示合理的应用程序不应该试图捕获的严重问题。大多数这样的错误都是异常条件。 和RuntimeException一样, 编译器也不会检查Error。 当资源不足、约束失败、或是其它程序无法继续运行的条件发生时,就产生错误,程序本身无法修复这些错误的。 2.Exception Exception 类及其子类是 Throwable 的一种形式,它指出了合理的应用程序想要捕获的条件。 对于可以恢复的条件使用被检查异常(Exception的子类中除了RuntimeException之外的其它子类),对于程序错误使用运行时异常。 ① ClassNotFoundException 当应用程序试图使用以下方法通过字符串名加载类时: Class 类中的 forName 方法。 ClassLoader 类中的 findSystemClass 方法。 ClassLoader 类中的 loadClass 方法。 但是没有找到具有指定名称的类的定义,抛出该异常。 ② CloneNotSupportedException 当调用 Object 类中的 clone 方法复制对象,但该对象的类无法实现 Cloneable 接口时,抛出该异常。重写 clone 方法的应用程序也可能抛出此异常,指示不能或不应复制一个对象。 ③ IOException 当发生某种 I/O 异常时,抛出此异常。此类是失败或中断的 I/O 操作生成的异常的通用类。 -EOFException 当输入过程中意外到达文件或流的末尾时,抛出此异常。 此异常主要被数据输入流用来表明到达流的末尾。 注意:其他许多输入操作返回一个特殊值表示到达流的末尾,而不是抛出异常。 -FileNotFoundException 当试图打开指定路径名表示的文件失败时,抛出此异常。 在不存在具有指定路径名的文件时,此异常将由 FileInputStream、FileOutputStream 和 RandomAccessFile 构造方法抛出。如果该文件存在,但是由于某些原因不可访问,比如试图打开一个只读文件进行写入,则此时这些构造方法仍然会抛出该异常。 -MalformedURLException 抛出这一异常指示出现了错误的 URL。或者在规范字符串中找不到任何合法协议,或者无法解析字符串。 -UnknownHostException 指示主机 IP 地址无法确定而抛出的异常。 ④ RuntimeException 是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。可能在执行方法期间抛出但未被捕获的 RuntimeException 的任何子类都无需在 throws 子句中进行声明。 Java编译器不会检查它。当程序中可能出现这类异常时,还是会编译通过。 虽然Java编译器不会检查运行时异常,但是我们也可以通过throws进行声明抛出,也可以通过try-catch对它进行捕获处理。 -ArithmeticException 当出现异常的运算条件时,抛出此异常。例如,一个整数“除以零”时,抛出此类的一个实例。 -ClassCastException 当试图将对象强制转换为不是实例的子类时,抛出该异常。 例如:Object x = new Integer(0); -LllegalArgumentException 抛出的异常表明向方法传递了一个不合法或不正确的参数。 -IllegalStateException 在非法或不适当的时间调用方法时产生的信号。换句话说,即 Java 环境或 Java 应用程序没有处于请求操作所要求的适当状态下。 -IndexOutOfBoundsException 指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。 应用程序可以为这个类创建子类,以指示类似的异常。 -NoSuchElementException 由 Enumeration 的 nextElement 方法抛出,表明枚举中没有更多的元素。 -NullPointerException 当应用程序试图在需要对象的地方使用 null 时,抛出该异常。这种情况包括: 调用 null 对象的实例方法。 访问或修改 null 对象的字段。 将 null 作为一个数组,获得其长度。 将 null 作为一个数组,访问或修改其时间片。 将 null 作为 Throwable 值抛出。 应用程序应该抛出该类的实例,指示其他对 null 对象的非法使用。 (二) SOF (堆栈溢出 StackOverflow) StackOverflowError 的定义: 当应用程序递归太深而发生堆栈溢出时,抛出该错误。 因为栈一般默认为1-2m,一旦出现死循环或者是大量的递归调用,在不断的压栈过程中,造成栈容量超过1m而导致溢出。 栈溢出的原因: 递归调用 大量循环或死循环 全局变量是否过多 数组、List、map数据过大 (三)Android的OOM(Out Of Memory) 当内存占有量超过了虚拟机的分配的最大值时就会产生内存溢出(VM里面分配不出更多的page)。 一般出现情况:加载的图片太多或图片过大时、分配特大的数组、内存相应资源过多没有来不及释放。 解决方法: ①在内存引用上做处理 软引用是主要用于内存敏感的高速缓存。在jvm报告内存不足之前会清除所有的软引用,这样以来gc就有可能收集软可及的对象,可能解决内存吃紧问题,避免内存溢出。什么时候会被收集取决于gc的算法和gc运行时可用内存的大小。 ②对图片做边界压缩,配合软引用使用 ③显示的调用GC来回收内存 if(bitmapObject.isRecycled()==false) //如果没有回收 bitmapObject.recycle(); ④优化Dalvik虚拟机的堆内存分配 1.增强程序堆内存的处理效率 //在程序onCreate时就可以调用 即可 private final static floatTARGET_HEAP_UTILIZATION = 0.75f; VMRuntime.getRuntime().setTargetHeapUtilization(TARGET_HEAP_UTILIZATION); 2 .设置堆内存的大小 private final static int CWJ_HEAP_SIZE = 6* 1024* 1024 ; //设置最小heap内存为6MB大小 VMRuntime.getRuntime().setMinimumHeapSize(CWJ_HEAP_SIZE); ⑤ 用LruCache 和 AsyncTask<>解决 从cache中去取Bitmap,如果取到Bitmap,就直接把这个Bitmap设置到ImageView上面。 如果缓存中不存在,那么启动一个task去加载(可能从文件来,也可能从网络)。
版权声明:请尊重个人劳动成果,转载注明出处,谢谢! Condition 1: 如果try中没有异常且try中有return (执行顺序) try ---- finally --- return Condition 2: 如果try中有异常并且try中有return try----catch---finally--- return 总之 finally 永远执行! Condition 3: try中有异常,try-catch-finally里都没有return ,finally 之后有个return try----catch---finally try中有异常以后,根据Java的异常机制先执行catch后执行finally,此时错误异常已经抛出,程序因异常而终止,所以你的return是不会执行的 Condition 4: 当 try和finally中都有return时,finally中的return会覆盖掉其它位置的return(多个return会报unreachable code,编译不会通过)。 Condition 5: 当finally中不存在return,而catch中存在return,但finally中要修改catch中return 的变量值时 int ret = 0; try{ throw new Exception(); } catch(Exception e) { ret = 1; return ret; } finally{ ret = 2; } 最后返回值是1,因为return的值在执行finally之前已经确定下来了
版权声明:请尊重个人劳动成果,转载注明出处,谢谢! Map家族的继承关系 1 . TreeMap TreeMap实现SortMap接口,能够把它保存的记录根据键排序, 默认是按键值的升序排序(自然顺序),也可以指定排序的比较器( Comparator ),当用Iterator 遍历TreeMap时,得到的记录是排过序的。 注意,此实现不是同步的。如果多个线程同时访问一个映射,则其必须 外部同步。这一般是通过对自然封装该映射的对象执行同步操作来完成的。如果不存在这样的对象,则应该使用 Collections.synchronizedSortedMap 方法来“包装”该映射。最好在创建时完成这一操作,以防止对映射进行意外的不同步访问,如下所示: SortedMap m = Collections.synchronizedSortedMap(new TreeMap(...)); 2 .HashMap 键和值都可以是空对象 不保证映射的顺序,多次访问,映射元素的顺序可能不同 非线程安全 3 .LinkedHashMap 内部维持了一个双向链表,可以保持顺序 此实现不是同步的。如果多个线程同时访问链接的哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须 保持外部同步。这一般通过对自然封装该映射的对象进行同步操作来完成。如果不存在这样的对象,则应该使用 Collections.synchronizedMap 方法来“包装”该映射。最好在创建时完成这一操作,以防止对映射的意外的非同步访问: Map m = Collections.synchronizedMap(new LinkedHashMap(...)); LinkedHashMap保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的.也可以在构造时用带参数,按照应用次数排序。在遍历的时候会比HashMap慢,不过有种情况例外,当HashMap容量很大,实际数据较少时,遍历起来可能会比LinkedHashMap慢,因为LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而HashMap的遍历速度和他的容量有关。 4 .使用场景 一般情况下,我们用的最多的是HashMap HashMap里面存入的键值对在取出的时候是随机的,它根据键的HashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度。 在Map 中插入、删除和定位元素,HashMap 是最好的选择。 TreeMap取出来的是排序后的键值对。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。 LinkedHashMap 是HashMap的一个子类,如果需要输出的顺序和输入的相同,那么用LinkedHashMap可以实现,它还可以按读取顺序来排列,像连接池中可以应用。
版权声明:请尊重个人劳动成果,转载注明出处,谢谢! HashTable Hashtable继承于Dictionary字典,实现Map接口 键、值都不能是空对象 多次访问,映射元素的顺序相同 线程安全 hash算法 ,Hashtable则直接利用key本身的hash码来做验证 数据遍历的方式 Iterator (支持fast-fail)和 Enumeration (不支持fast-fail) 缺省初始长度为11,内部都为抽象方法,需要 它的实现类一一作自己的实现 备注:程序在对 collection 进行迭代时,某个线程对该 collection 在结构上对其做了修改,这时迭代器就会抛出 ConcurrentModificationException 异常信息,从而产生 fail-fast。 HashMap HashMap继承于AbstractMap抽象类 键和值都可以是空对象 多次访问,映射元素的顺序可能不同 非线程安全 HashMap可以通过下面的语句进行同步: Map m = Collections.synchronizeMap(hashMap); 检测是否含有key时,HashMap内部需要将key的hash码重新计算一边再检测 数据遍历的方式 Iterator (支持fast-fail) 缺省初始长度为16,其内部已经实现了Map所需 要做的大部分工作, 它的子类只需要实现它的少量方法
版权声明:请尊重个人劳动成果,转载注明出处,谢谢! Map 键映射到值的对象。一个映射不能包含重复的键;每个键最多只能映射到一个值。 某些映射实现可明确保证其顺序,如 TreeMap 类;另一些映射实现则不保证顺序,如 HashMap 类。 Map中元素,可以将key序列、value序列单独抽取出来。 使用keySet()抽取key序列,将map中的所有keys生成一个Set。 使用values()抽取value序列,将map中的所有values生成一个Collection。 为什么一个生成Set,一个生成Collection?那是因为,key总是独一无二的,value允许重复。 Set 一个不包含重复元素的 collection。更确切地讲,set 不包含满足 e1.equals(e2) 的元素对 e1 和 e2,并且最多包含一个 null 元素。 不可随机访问包含的元素 只能用Iterator实现单向遍历 Set 没有同步方法 List 可随机访问包含的元素 元素是有序的 可在任意位置增、删元素 不管访问多少次,元素位置不变 允许重复元素 用Iterator实现单向遍历,也可用ListIterator实现双向遍历 Queue 先进先出 Queue使用时要尽量避免Collection的add()和remove()方法,而是要使用offer()来加入元素,使用poll()来获取并移出元素。它们的优点是通过返回值可以判断成功与否,add()和remove()方法在失败的时候会抛出异常。 如果要使用前端而不移出该元素,使用element()或者peek()方法。 值得注意的是LinkedList类实现了Queue接口,因此我们可以把LinkedList当成Queue来用。 Queue 实现通常不允许插入 null 元素,尽管某些实现(如 LinkedList)并不禁止插入 null。即使在允许 null 的实现中,也不应该将 null 插入到 Queue 中,因为 null 也用作 poll 方法的一个特殊返回值,表明队列不包含元素。 Stack 后进先出 Stack继承自Vector(可增长的对象数组),也是同步的 它通过五个操作对类 Vector 进行了扩展 ,允许将向量视为堆栈。它提供了通常的 push 和 pop 操作,以及取堆栈顶点的 peek 方法、测试堆栈是否为空的 empty 方法、在堆栈中查找项并确定到堆栈顶距离的 search 方法。 用法 如果涉及到堆栈、队列等操作,应该考虑用List; 对于需要快速插入,删除元素,应该使用LinkedList; 如果需要快速随机访问元素,应该使用ArrayList。 如果程序在单线程环境中,或者访问仅仅在一个线程中进行,考虑非同步的类,其效率较高
版权声明:请尊重个人劳动成果,转载注明出处,谢谢! String public final class String extends Object implements Serializable, Comparable, CharSequence String 类代表字符串。Java 程序中的所有字符串字面值(如 “abc” )都作为此类的实例实现。 字符串是常量;它们的值在创建之后不能更改。字符串缓冲区支持可变的字符串。因为 String 对象是不可变的,所以可以共享。例如: String str = "abc" 等效于: char data[] = {'a', 'b', 'c'}; String str = new String(data); 下面给出了一些如何使用字符串的更多示例: System.out.println("abc"); String cde = "cde"; System.out.println("abc" + cde); String c = "abc".substring(2,3); String d = cde.substring(1, 2); String 类包括的方法可用于检查序列的单个字符、比较字符串、搜索字符串、提取子字符串、创建字符串副本并将所有字符全部转换为大写或小写。大小写映射基于 Character 类指定的 Unicode 标准版。 Java 语言提供对字符串串联符号(”+”)以及将其他对象转换为字符串的特殊支持。字符串串联是通过 StringBuilder(或 StringBuffer)类及其 append 方法实现的。字符串转换是通过 toString 方法实现的,该方法由 Object 类定义,并可被 Java 中的所有类继承。有关字符串串联和转换的更多信息,请参阅 Gosling、Joy 和 Steele 合著的 The Java Language Specification。 除非另行说明,否则将 null 参数传递给此类中的构造方法或方法将抛出 NullPointerException。 String 表示一个 UTF-16 格式的字符串,其中的增补字符 由代理项对 表示(有关详细信息,请参阅 Character 类中的 Unicode 字符表示形式)。索引值是指 char 代码单元,因此增补字符在 String 中占用两个位置。 String 类提供处理 Unicode 代码点(即字符)和 Unicode 代码单元(即 char 值)的方法。 StringBuffer public final class StringBuffer extends Object implements Serializable, CharSequence 线程安全的可变字符序列。一个类似于 String 的字符串缓冲区,但不能修改。虽然在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。 可将字符串缓冲区安全地用于多个线程。可以在必要时对这些方法进行同步,因此任意特定实例上的所有操作就好像是以串行顺序发生的,该顺序与所涉及的每个线程进行的方法调用顺序一致。 StringBuffer 上的主要操作是 append 和 insert 方法,可重载这些方法,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串缓冲区中。append 方法始终将这些字符添加到缓冲区的末端;而 insert 方法则在指定的点添加字符。 例如,如果 z 引用一个当前内容为 “start” 的字符串缓冲区对象,则此方法调用 z.append(“le”) 会使字符串缓冲区包含 “startle”,而 z.insert(4, “le”) 将更改字符串缓冲区,使之包含 “starlet”。 通常,如果 sb 引用 StringBuilder 的一个实例,则 sb.append(x) 和 sb.insert(sb.length(), x) 具有相同的效果。 当发生与源序列有关的操作(如源序列中的追加或插入操作)时,该类只在执行此操作的字符串缓冲区上而不是在源上实现同步。 每个字符串缓冲区都有一定的容量。只要字符串缓冲区所包含的字符序列的长度没有超出此容量,就无需分配新的内部缓冲区数组。如果内部缓冲区溢出,则此容量自动增大。从 JDK 5 开始,为该类补充了一个单个线程使用的等价类,即 StringBuilder。与该类相比,通常应该优先使用 StringBuilder 类,因为它支持所有相同的操作,但由于它不执行同步,所以速度更快。 StringBuilder public final class StringBuilder extends Object implements Serializable, CharSequence 一个可变的字符序列。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer 要快。 在 StringBuilder 上的主要操作是 append 和 insert 方法,可重载这些方法,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串生成器中。append 方法始终将这些字符添加到生成器的末端;而 insert 方法则在指定的点添加字符。 将 StringBuilder 的实例用于多个线程是不安全的。如果需要这样的同步,则建议使用 StringBuffer。 1 .上String、StringBuffer与StringBuilder的原代码中的部分代码 //String public final class String implements java.io.Serializable, Comparable<String>, CharSequence { private final char value[]; /** * Allocates a new {@code String} so that it represents the sequence of * characters currently contained in the character array argument. The * contents of the character array are copied; subsequent modification of * the character array does not affect the newly created string. * * @param value * The initial value of the string */ public String(String original) { this.value = Arrays.copyOf(value, value.length); // 把传入构造函数original字符串切分成字符数组并赋给value[]; } } //StringBuffer public final class StringBuffer extends AbstractStringBuilder implements java.io.Serializable, CharSequence { /** * Constructs a string buffer initialized to the contents of the * specified string. The initial capacity of the string buffer is * <code>16</code> plus the length of the string argument. * * @param str the initial contents of the buffer. * @exception NullPointerException if <code>str</code> is <code>null</code> */ public StringBuffer(String str) { super(str.length() + 16); //继承父类的构造器,并创建一个大小为str.length()+16的value[]数组 append(str); //将str切分成字符序列并加入到value[]中 } public synchronized StringBuffer append(String str) { super.append(str); return this; } } //StringBuilder public final class StringBuilder extends AbstractStringBuilder implements java.io.Serializable, CharSequence { /** * Constructs a string builder initialized to the contents of the * specified string. The initial capacity of the string builder is * <code>16</code> plus the length of the string argument. * * @param str the initial contents of the buffer. * @throws NullPointerException if <code>str</code> is <code>null</code> */ public StringBuilder(String str) { super(str.length() + 16); append(str); } public StringBuilder append(String str) { super.append(str); return this; } } //AbstractStringBuilder abstract class AbstractStringBuilder implements Appendable, CharSequence { /** * The value is used for character storage. */ char[] value; /** * Creates an AbstractStringBuilder of the specified capacity. */ AbstractStringBuilder(int capacity) { value = new char[capacity]; } public AbstractStringBuilder append(String str) { if (str == null) str = "null"; int len = str.length(); ensureCapacityInternal(count + len); str.getChars(0, len, value, count); count += len; return this; } } 2 .我们可以看出在String中构造器传入的字符串对象被切分成字符序列,存放在其私有final的类变量value[]中。 new String(“java”)使得value[]={‘j’,’a’,’v’,’a’},之后这个String对象中的value[]再也不能改变了,只能读,所以是线程安全的。 StringBuffer和StringBuilder都继承了AbstractStringBuilder 这个抽象类,在他们的构造函数中传入字符串,会调用父类AbstractStringBuilder中对应的构造函数和append函数,父类构造函数新建一个长度为(字符串长度+末尾附加16个空元素)的数组然后通过父类append函数把字符串转换成字符数组value[]中(这里的char[] value 没有final,StringBuffer和StringBuilder中的字符串是可变的)。以后通过append()方法将新字符串加入value[]末尾。这样也就改变了value[]的内容和大小了。 StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。 而StringBuilder不是线程安全的。 ① String s = "ja" +"va" ② String s1="ja"; StringBuffer sb=new StringBuffer("va"); sb.append(s1); ③ String s1="ja"; String s2 = "va"; String s = s1 +s2 在编译阶段就能够确定的字符串常量,完全没有必要创建String或StringBuffer对象。直接使用字符串常量的”+”连接操作效率最高。 时间上 ①<③ StringBuffer对象的append效率要高于两个String对象的”+”连接操作。 时间上 ②<③ 一般来说 执行时间上从快到慢 StringBuilder、StringBuffer、String 非多线程操作字符串缓冲区建议使用 StringBuilder。 抽象类和接口 抽象类中可以定义一些子类的公共方法,子类只需要增加新的功能,不需要重复写已经存在的方法; 而接口中只是对方法的申明和常量的定义。
版权声明:请尊重个人劳动成果,转载注明出处,谢谢! 首先我们来看一下继承关系: 我们可以看出ArrayList、LinkedList、Vector都实现了List的接口。 接下来分别看一下三个数据结构的说明。 public class ArrayList extends AbstractList implements List, RandomAccess, Cloneable, Serializable List 接口的大小可变数组的实现。实现了所有可选列表操作,并允许包括 null 在内的所有元素。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。(此类大致上等同于 Vector 类,除了此类是不同步的。) 每个 ArrayList 实例都有一个容量。该容量是指用来存储列表元素的数组的大小。它总是至少等于列表的大小。随着向 ArrayList 中不断添加元素,其容量也自动增长。并未指定增长策略的细节,因为这不只是添加元素会带来分摊固定时间开销那样简单。 在添加大量元素前,应用程序可以使用 ensureCapacity 操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量。 List list = Collections.synchronizedList(new ArrayList(...)); 此类的 iterator 和 listIterator 方法返回的迭代器是快速失败的:在创建迭代器之后,除非通过迭代器自身的 remove 或 add 方法从结构上对列表进行修改,否则在任何时间以任何方式对列表进行修改,迭代器都会抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。 注意,迭代器的快速失败行为无法得到保证,因为一般来说,不可能对是否出现不同步并发修改做出任何硬性保证。快速失败迭代器会尽最大努力抛出 ConcurrentModificationException。因此,为提高这类迭代器的正确性而编写一个依赖于此异常的程序是错误的做法:迭代器的快速失败行为应该仅用于检测 bug。 然后是LinkedList public class LinkedList extends AbstractSequentialList implements List, Deque, Cloneable, Serializable List 接口的链接列表实现。实现所有可选的列表操作,并且允许所有元素(包括 null)。除了实现 List 接口外,LinkedList 类还为在列表的开头及结尾 get、remove 和 insert 元素提供了统一的命名方法。这些操作允许将链接列表用作堆栈、队列或双端队列。 此类实现 Deque 接口,为 add、poll 提供先进先出队列操作,以及其他堆栈和双端队列操作。 所有操作都是按照双重链接列表的需要执行的。在列表中编索引的操作将从开头或结尾遍历列表(从靠近指定索引的一端)。 注意,此实现不是同步的。如果多个线程同时访问一个链接列表,而其中至少一个线程从结构上修改了该列表,则它必须 保持外部同步。(结构修改指添加或删除一个或多个元素的任何操作;仅设置元素的值不是结构修改。)这一般通过对自然封装该列表的对象进行同步操作来完成。如果不存在这样的对象,则应该使用 Collections.synchronizedList 方法来“包装”该列表。最好在创建时完成这一操作,以防止对列表进行意外的不同步访问,如下所示: List list = Collections.synchronizedList(new LinkedList(…)); 此类的 iterator 和 listIterator 方法返回的迭代器是快速失败 的:在迭代器创建之后,如果从结构上对列表进行修改,除非通过迭代器自身的 remove 或 add 方法,其他任何时间任何方式的修改,迭代器都将抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就会完全失败,而不冒将来不确定的时间任意发生不确定行为的风险。 注意,迭代器的快速失败行为不能得到保证,一般来说,存在不同步的并发修改时,不可能作出任何硬性保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException。因此,编写依赖于此异常的程序的方式是错误的,正确做法是:迭代器的快速失败行为应该仅用于检测程序错误。 最后是Vector public class Vector extends AbstractList implements List, RandomAccess, Cloneable, Serializable Vector 类可以实现可增长的对象数组。与数组一样,它包含可以使用整数索引进行访问的组件。但是,Vector 的大小可以根据需要增大或缩小,以适应创建 Vector 后进行添加或移除项的操作。 每个向量会试图通过维护 capacity 和 capacityIncrement 来优化存储管理。capacity 始终至少应与向量的大小相等;这个值通常比后者大些,因为随着将组件添加到向量中,其存储将按 capacityIncrement 的大小增加存储块。应用程序可以在插入大量组件前增加向量的容量;这样就减少了增加的重分配的量。 由 Vector 的 iterator 和 listIterator 方法所返回的迭代器是快速失败的:如果在迭代器创建后的任意时间从结构上修改了向量(通过迭代器自身的 remove 或 add 方法之外的任何其他方式),则迭代器将抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就完全失败,而不是冒着在将来不确定的时间任意发生不确定行为的风险。Vector 的 elements 方法返回的 Enumeration 不是 快速失败的。 注意,迭代器的快速失败行为不能得到保证,一般来说,存在不同步的并发修改时,不可能作出任何坚决的保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException。因此,编写依赖于此异常的程序的方式是错误的,正确做法是:迭代器的快速失败行为应该仅用于检测 bug。 从 Java 2 平台 v1.2 开始,此类改进为可以实现 List 接口,使它成为 Java Collections Framework 的成员。与新 collection 实现不同,Vector 是同步的。 区别 ArrayList 本质上是一个可改变大小的数组.当元素加入时,其大小将会动态地增长.内部的元素可以直接通过get与set方法进行访问.元素顺序存储 ,随机访问很快,删除非头尾元素慢,新增元素慢而且费资源 ,较适用于无频繁增删的情况 ,比数组效率低,如果不是需要可变数组,可考虑使用数组 ,非线程安全. LinkedList 是一个双链表,在添加和删除元素时具有比ArrayList更好的性能.但在get与set方面弱于ArrayList. 适用于 :没有大规模的随机读取,大量的增加/删除操作.随机访问很慢,增删操作很快,不耗费多余资源 ,允许null元素,非线程安全. Vector (类似于ArrayList)但其是同步的,开销就比ArrayList要大。如果你的程序本身是线程安全的,那么使用ArrayList是更好的选择。 Vector和ArrayList在更多元素添加进来时会请求更大的空间。Vector每次请求其大小的双倍空间,而ArrayList每次对size增长50%.
版权声明:请尊重个人劳动成果,转载注明出处,谢谢! 官方文档 http://docs.oracle.com/javase/8/docs/api/ protected Object clone() 创建并返回此对象的一个副本。 boolean equals(Object obj) 指示其他某个对象是否与此对象“相等”。 protected void finalize() 当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。 Class getClass() 返回此 Object 的运行时类。 int hashCode() 返回该对象的哈希码值。 void notify() 唤醒在此对象监视器上等待的单个线程。 void notifyAll() 唤醒在此对象监视器上等待的所有线程。 String toString() 返回该对象的字符串表示。 void wait() 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。 void wait(long timeout) 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量前,导致当前线程等待。 void wait(long timeout, int nanos) 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量前,导致当前线程等待。
版权声明:请尊重个人劳动成果,转载注明出处,谢谢! 从JDK1.2版本开始,把对象的引用分为四种级别,从而使程序能更加灵活的控制对象的生命周期。这四种级别由高到低依次为:强引用、软引用、弱引用和虚引用。 1 . 强引用(StrongReference) 强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。如代码String s=”abc”中变量s就是字符串对象”abc”的一个强引用。只要你给强引用对象s赋空值null,该对象就可以被垃圾回收器回收。因为该对象此时不再含有其他强引用。 2 . 弱引用(WeakReference) 弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象(s),不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象,弱引用非常适合存储元数据。另一个使用弱引用的例子是WeakHashMap,它是除HashMap和TreeMap之外,Map接口的另一种实现。WeakHashMap有一个特点:map中的键值(keys)都被封装成弱引用,也就是说一旦强引用被删除,WeakHashMap内部的弱引用就无法阻止该对象被垃圾回收器回收。 如下代码创建弱引用: Counter counter = new Counter(); // strong reference - line 1 WeakReference<Counter> weakCounter = new WeakReference<Counter>(counter); //weak reference counter = null; // now Counter object is eligible for garbage collection 3 . 软引用(SoftReference) 如果一个对象(如 s)只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。 可以使用如下代码创建软引用: Counter prime = new Counter(); // prime holds a strong reference SoftReference soft = new SoftReference(prime) ; //soft reference variable has SoftReference to Counter Object created at line 2 prime = null; // now Counter object is eligible for garbage collection but only be collected when JVM absolutely needs memory 4 . 虚引用(PhantomReference) “虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。 虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用 必须 和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。 如下代码创建虚引用: DigitalCounter digit = new DigitalCounter(); // digit reference variable has strong reference – line 3 PhantomReference phantom = new PhantomReference(digit); // phantom reference to object created at line 3 digit = null; 弱引用、软引用可以和一个引用队列(ReferenceQueue)联合使用,如果其所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。 在创建任何弱引用、软引用和虚引用的过程中你可以通过如下代码提供引用队列ReferenceQueue。 ReferenceQueue refQueue = new ReferenceQueue(); //reference will be stored in this queue for cleanup DigitalCounter digit = new DigitalCounter(); PhantomReference<DigitalCounter> phantom = new PhantomReference<DigitalCounter>(digit, refQueue);
版权声明:请尊重个人劳动成果,转载注明出处,谢谢! == 是一个运算符。 equals则是string对象的方法。 Java中 值类型 是存储在内存中的栈中。 而引用类型在栈中仅仅是存储引用类型变量的地址,而其本身则存储在堆中。所以字符串的内容相同,引用地址不一定相同,有可能创建了多个对象。 ==操作比较的是两个变量的值是否相等,对于引用型变量表示的是两个变量在堆中存储的地址是否相同,即栈中的内容是否相同。 equals将此字符串与指定的对象比较。当且仅当该参数不为 null,并且是与此对象表示相同字符序列的 String 对象时,结果才为 true。即堆中的内容是否相同。==比较的是2个对象的地址(栈中),而equals比较的是2个对象的内容(堆中)。所以当equals为true时,==不一定为true。 下面是String类equals方法源码,它复写了类 Object 中的 equals方法。 public boolean equals(Object anObject) { 965 if (this == anObject) { 966 return true; 967 } 968 if (anObject instanceof String) { 969 String anotherString = (String)anObject; 970 int n = value.length; 971 if (n == anotherString.value.length) { 972 char v1[] = value; 973 char v2[] = anotherString.value; 974 int i = 0; 975 while (n-- != 0) { 976 if (v1[i] != v2[i]) 977 return false; 978 i++; 979 } 980 return true; 981 } 982 } 983 return false; 984 } 上面已经说到equals是比较两个对象的内容,我们可以看到方法中,先是比较两个String对象是否为同一对象,如果是就直接返回true(两个对象为同一对象那他们的内容必然相等)。 如果不是同一对象,先确定传入的对象是否是String类型,如果是,则比较两对象的字符序列(String类内部存储是用char[]实现的,可以查看源码了解),遍历过程中只要有一个字符不相同,就返回false,否则返回true。这里注意比较次数为第一个String对象的长度n,而不是传入的String对象参数的长度。
版权声明:请尊重个人劳动成果,转载注明出处,谢谢! 1 . 在jdk 7 之前,switch 只能支持 byte、short、char、int 这几个基本数据类型和其对应的封装类型。switch后面的括号里面只能放int类型的值,但由于byte,short,char类型,它们会 自动 转换为int类型(精精度小的向大的转化),所以它们也支持。 对于精度比int大的类型,long、float、double,不会自动转换成int。要想使用就得加强转如(int)long。 另外boolean类型不参与转换,任何类型不能转换为boolean型. 2 .我们也可以用枚举类型实现switch可传入string参数 public enum En{ a,b,c } public static void main(String[] args) { En t = En.a; function(t); } public static void function(En type){ switch (type) { case a: System.err.println("a"); break; case b: System.err.println("b"); break; case c: System.err.println("c"); break; default: break; } } 运行结果: 3 . jdk7之后Java加入了switch对string的支持,就不用枚举来实现啦! public static void main(String[] args) { String s = "a"; switch (s) { case "a": System.err.println("a"); break; case "b": System.err.println("b"); break; case "c": System.err.println("c"); break; default: break; } } 运行结果:
版权声明:请尊重个人劳动成果,转载注明出处,谢谢! 下面异常是属于Runtime Exception 的是(abcd)(多选) A、ArithmeticException B、IllegalArgumentException C、NullPointerException D、BufferUnderflowException 解析: Java提供了两类主要的异常:runtime exception和checked exception。checked 异常也就是我们经常遇到的IO异常,以及SQL异常都是这种异常。对于这种异常,JAVA编译器强制要求我们必需对出现的这些异常进行catch。所以,面对这种异常不管我们是否愿意,只能自己去写一大堆catch块去处理可能的异常。 出现运行时异常后,系统会把异常一直往上层抛,一直遇到处理代码。如果没有处理块,到最上层,如果是多线程就由Thread.run()抛出,如果是单线程就被main()抛出。抛出之后,如果是线程,这个线程也就退出了。如果是主程序抛出的异常,那么这整个程序也就退出了。运行时异常是Exception的子类,也有一般异常的特点,是可以被Catch块处理的。只不过往往我们不对他处理罢了。也就是说,你如果不对运行时异常进行处理,那么出现运行时异常之后,要么是线程中止,要么是主程序终止。 编译时被检查的异常和运行时异常的区别: 编译被检查的异常在函数内被抛出,函数必须要声明,否编译失败。 声明的原因:是需要调用者对该异常进行处理。 运行时异常如果在函数内被抛出,在函数上不需要声明。 Math.round(11.5)等于多少(). Math.round(-11.5)等于多少(c) A、11 ,-11 B、11 ,-12 C、12 ,-11 D、12 ,-12 解析: Math.ceil()用作向上取整。 Math.floor()用作向下取整。 Math.round() 我们数学中常用到的四舍五入取整。 对一些资源以及状态的操作保存,最好是保存在生命周期的哪个函数中进行(d) A、onPause() B、onCreate() C、 onResume() D、onStart() 解析: 系统杀死程序会调用onSaveInstanceState(Bundle)进行数据保存,这里保存的数据会出现在在程序下一次onStart(Bundle)这个Bundle中,onStart时可以将Bundle中数据取出。 Intent传递数据时,下列的数据类型哪些可以被传递(abcd)(多选) A、Serializable B、charsequence C、Parcelable D、Bundle 解析: Serializable :将 Java 对象序列化为二进制文件的 Java 序列化技术是 Java系列技术中一个较为重要的技术点,在大部分情况下,开发人员只需要了解被序列化的类需要实现 Serializable 接口,使用ObjectInputStream 和 ObjectOutputStream 进行对象的读写。 charsequence : 实现了这个接口的类有:CharBuffer、String、StringBuffer、StringBuilder这个四个类。 CharBuffer为nio里面用的一个类,String实现这个接口理所当然,StringBuffer也是一个CharSequence,StringBuilder是Java抄袭C#的一个类,基本和StringBuffer类一样,效率高,但是不保证线程安全,在不需要多线程的环境下可以考虑。 提供这么一个接口,有些处理String或者StringBuffer的类就不用重载了。但是这个接口提供的方法有限,只有下面几个:charat、length、subSequence、toString这几个方法,感觉如果有必要,还是重载的比较好,避免用instaneof这个操作符。 Parcelable : Android提供了一种新的类型:Parcel。本类被用作封装数据的容器,封装后的数据可以通过Intent或IPC传递。 除了基本类型以 外,只有实现了Parcelable接口的类才能被放入Parcel中。 是GOOGLE在安卓中实现的另一种序列化,功能和Serializable相似,主要是序列化的方式不同 Bundle是将数据传递到另一个上下文中或保存或回复你自己状态的数据存储方式。它的数据不是持久化状态。 下列属于SAX解析xml文件的优点的是(b) A、将整个文档树在内存中,便于操作,支持删除,修改,重新排列等多种功能 B、不用事先调入整个文档,占用资源少 C、整个文档调入内存,浪费时间和空间 D、不是长久驻留在内存,数据不是持久的,事件过后,若没有保存数据,数据就会消失 解析: 在Android中提供了三种解析XML的方式:SAX(Simple API XML),DOM(Document Objrect Model),以及Android推荐的Pull解析方式。 SAX: 是事件驱动型XML解析的一个标准接口,简单地说就是对文档进行顺序扫描,当扫描到文档(document)开始与结束、元素(element)开始与结束、文档(document)结束等地方时通知事件处理函数,由事件处理函数做相应动作,然后继续同样的扫描,直至文档结束。 DOM:即对象文档模型,它是将整个XML文档载入内存(所以效率较低,不推荐使用),使用DOM API遍历XML树、检索所需的数据,每一个节点当做一个对象。 Pull:运行方式与 SAX 解析器相似。它提供了类似的事件,SAX解析器的工作方式是自动将事件推入事件处理器进行处理,因此你不能控制事件的处理主动结束;而Pull解析器的工作方式为允许你的应用程序代码主动从解析器中获取事件,正因为是主动获取事件,因此可以在满足了需要的条件后不再获取事件,结束解析。pull是一个while循环,随时可以跳出,而sax是只要解析了,就必须解析完成。 在android中使用Menu时可能需要重写的方法有(ac)。(多选) A、onCreateOptionsMenu() B、onCreateMenu() C、onOptionsItemSelected() D、onItemSelected() 解析: android中有三种菜单 1.选项菜单Options menus :一个Activity只能有一个选项菜单,在按下Menu键时,显示在屏幕下方。 重写 onCreateContextMenu 用以创建上下文菜单 重写 onContextItemSelected 用以响应上下文菜单 2.上下文菜单Context menus :为Activity中的任何一个视图注册一个上下文菜单,“长按”出现。 重写 onCreateOptionsMenu 用以创建选项菜单 重写 onOptionsItemSelected 用以响应选项菜单 3.弹出式菜单Popup menus :依赖于Activity中的某个一个视图 android 关于service生命周期的onCreate()和onStart()说法正确的是(ad)(多选题) A、当第一次启动的时候先后调用onCreate()和onStart()方法 B、当第一次启动的时候只会调用onCreate()方法 C、如果service已经启动,将先后调用onCreate()和onStart()方法 D、如果service已经启动,只会执行onStart()方法,不在执行onCreate()方法 解析: 1). 被启动的服务的生命周期:如果一个Service被某个Activity 调用 Context.startService 方法启动,那么不管是否有Activity使用bindService绑定或unbindService解除绑定到该Service,该Service都在后台运行。如果一个Service被startService 方法多次启动,那么onCreate方法只会调用一次,onStart将会被调用多次(对应调用startService的次数),并且系统只会创建Service的一个实例(因此你应该知道只需要一次stopService调用)。该Service将会一直在后台运行,而不管对应程序的Activity是否在运行,直到被调用stopService,或自身的stopSelf方法。当然如果系统资源不足,android系统也可能结束服务。 2). 被绑定的服务的生命周期:如果一个Service被某个Activity 调用 Context.bindService 方法绑定启动,不管调用 bindService 调用几次,onCreate方法都只会调用一次,同时onStart方法始终不会被调用。当连接建立之后,Service将会一直运行,除非调用Context.unbindService 断开连接或者之前调用bindService 的 Context 不存在了(如Activity被finish的时候),系统将会自动停止Service,对应onDestroy将被调用。 3). 被启动又被绑定的服务的生命周期:如果一个Service又被启动又被绑定,则该Service将会一直在后台运行。并且不管如何调用,onCreate始终只会调用一次,对应startService调用多少次,Service的onStart便会调用多少次。调用unbindService将不会停止Service,而必须调用 stopService 或 Service的 stopSelf 来停止服务。 4). 当服务被停止时清除服务:当一个Service被终止(1、调用stopService;2、调用stopSelf;3、不再有绑定的连接(没有被启动))时,onDestroy方法将会被调用,在这里你应当做一些清除工作,如停止在Service中创建并运行的线程。 特别注意: 1、你应当知道在调用 bindService 绑定到Service的时候,你就应当保证在某处调用 unbindService 解除绑定(尽管 Activity 被 finish 的时候绑定会自动解除,并且Service会自动停止); 2、你应当注意 使用 startService 启动服务之后,一定要使用 stopService停止服务,不管你是否使用bindService; 3、同时使用 startService 与 bindService 要注意到,Service 的终止,需要unbindService与stopService同时调用,才能终止 Service,不管 startService 与 bindService 的调用顺序,如果先调用 unbindService 此时服务不会自动终止,再调用 stopService 之后服务才会停止,如果先调用 stopService 此时服务也不会终止,而再调用 unbindService 或者 之前调用 bindService 的 Context 不存在了(如Activity 被 finish 的时候)之后服务才会自动停止; 4、当在旋转手机屏幕的时候,当手机屏幕在“横”“竖”变换时,此时如果你的 Activity 如果会自动旋转的话,旋转其实是 Activity 的重新创建,因此旋转之前的使用 bindService 建立的连接便会断开(Context 不存在了),对应服务的生命周期与上述相同。 5、在 sdk 2.0 及其以后的版本中,对应的 onStart 已经被否决变为了 onStartCommand,不过之前的 onStart 任然有效。这意味着,如果你开发的应用程序用的 sdk 为 2.0 及其以后的版本,那么你应当使用 onStartCommand 而不是 onStart。 下面是属于GLSurFaceView特性的是(abc)(多选) A、管理一个surface,这个surface就是一块特殊的内存,能直接排版到android的视图view上。 B、管理一个EGL display,它能让opengl把内容渲染到上述的surface上。 C、让渲染器在独立的线程里运作,和UI线程分离。 D、可以直接从内存或者DMA等硬件接口取得图像数据 解析: Android游戏当中主要的除了控制类外就是显示类View。SurfaceView是从View基类中派生出来的显示类。android游戏开发中常用的三种视图是:view、SurfaceView和GLSurfaceView。 View:显示视图,内置画布,提供图形绘制函数、触屏事件、按键事件函数等;必须在UI主线程内更新画面,速度较慢。 SurfaceView:基于view视图进行拓展的视图类,更适合2D游戏的开发;是view的子类,类似使用双缓机制,在新的线程中更新画面所以刷新界面速度比view快。 GLSurfaceView:基于SurfaceView视图再次进行拓展的视图类,专用于3D游戏开发的视图;是SurfaceView的子类,openGL专用。 GLSurfaceView提供了下列特性: 1.管理一个surface,这个surface就是一块特殊的内存,能直接排版到android的视图view上。 2.管理一个EGL display,它能让opengl把内容渲染到上述的surface上。 3.用户自定义渲染器(render)。 4 . 让渲染器在独立的线程里运作,和UI线程分离。 5.支持按需渲染(on-demand)和连续渲染(continuous)。 6.一些可选工具,如调试。 关于ContenValues类说法正确的是(a) A、他和Hashtable比较类似,也是负责存储一些名值对,但是他存储的名值对当中的名是String类型,而值都是基本类型 B、他和Hashtable比较类似,也是负责存储一些名值对,但是他存储的名值对当中的名是任意类型,而值都是基本类型 C、他和Hashtable比较类似,也是负责存储一些名值对,但是他存储的名值对当中的名,可以为空,而值都是String类型 D、他和Hashtable比较类似,也是负责存储一些名值对,但是他存储的名值对当中的名是String类型,而值也是String类型 解析: ContentValues 和HashTable类似都是一种存储的机制 但是两者最大的区别就在于,contenvalues Key只能是String类型,values只能存储基本类型的数据,像string,int之类的,不能存储对象这种东西。ContentValues 常用在数据库中的操作。 HashMap是Hashtable的轻量级实现(非线程安全的实现),他们都完成了Map接口,主要区别在于HashMap允许空(null)键值(key),由于非线程安全,效率上可能高于Hashtable。HashMap允许将null作为一个entry的key或者value,而Hashtable不允许。 下面退出Activity错误的方法是(c) A、finish() B、抛异常强制退出 C、System.exit() D、onStop() 解析: finish():在你的activity动作完成的时候,或者Activity需要关闭的时候,调用此方法。当你调用此方法的时候,系统只是将最上面的Activity移出了栈,并没有及时的调用onDestory()方法,其占用的资源也没有被及时释放。因为移出了栈,所以当你点击手机上面的“back”按键的时候,也不会再找到这个Activity。finish函数仅仅把当前Activity退出了,但是并没有释放他的资源。安卓系统自己决定何时从内存中释放应用程序。当系统没有可用内存到时候,会按照优先级,释放部分应用。 onDestory():系统销毁了这个Activity的实例在内存中占据的空间。 在Activity的生命周期中,onDestory()方法是他生命的最后一步,资源空间等就被回收了。当重新进入此Activity的时候,必须重新创建,执行onCreate()方法。 System.exit(0):退出整个应用程序(不仅仅是当前activity)。将整个进程直接Kill掉。 关于res/raw目录说法正确的是(a) A、 这里的文件是原封不动的存储到设备上不会转换为二进制的格式 B、这里的文件是原封不动的存储到设备上会转换为二进制的格式 C、 这里的文件最终以二进制的格式存储到指定的包中 D、这里的文件最终不会以二进制的格式存储到指定的包中 解析: res/raw和assets的相同点: 两者目录下的文件在打包后会原封不动的保存在apk包中,不会被编译成二进制。 res/raw和assets的不同点: 1.res/raw中的文件会被映射到R.java文件中,访问的时候直接使用资源ID即R.id.filename;assets文件夹下的文件不会被映射到R.java中,访问的时候需要AssetManager类。 android中常用的四个布局是framlayout,linenarlayout,relativelayout和tablelayout。 android 的四大组件是activiey,service,broadcast和contentprovide。 activity一般会重载7个方法用来维护其生命周期,除了onCreate(),onStart(),onDestory() 外还有onpause,onresume,onstop,onrestart。 android的数据存储的方式sharedpreference,文件,SQlite,contentprovider,网络。 程序运行的结果是:good and gbc public classExample{ String str=new String("good"); char[]ch={'a','b','c'}; public static void main(String args[]){ Example ex=new Example(); ex.change(ex.str,ex.ch); System.out.print(ex.str+" and "); Sytem.out.print(ex.ch); } public void change(String str,char ch[]){ str="test ok"; ch[0]='g'; } } 解析: public void change(String str,char ch[]) str是按值传递,所以在函数中对它的操作只生效于它的副本,与原字符串无关。 ch是按址传递,在函数中根据地址,可以直接对字符串进行操作。 在android中,请简述jni的调用过程。 1)安装和下载Cygwin,下载 Android NDK 2)在ndk项目中JNI接口的设计 3)使用C/C++实现本地方法 4)JNI生成动态链接库.so文件 5)将动态链接库复制到java工程,在java工程中调用,运行java工程即可 Android应用程序结构: Linux Kernel(Linux内核)、Libraries(系统运行库或者是c/c++核心库)、Application Framework(开发框架包)、Applications (核心应用程序) 请继承SQLiteOpenHelper实现创建一个版本为1的“diaryOpenHelper.db”的数据库,同时创建一个 “diary” 表(包含一个_id主键并自增长,topic字符型100长度, content字符型1000长度),在数据库版本变化时请删除diary表,并重新创建出diary表。 public class DBHelper extends SQLiteOpenHelper{ public final static String DATABASENAME ="diaryOpenHelper.db"; public final static int DATABASEVERSION =1; //创建数据库 public DBHelper(Context context,Stringname,CursorFactory factory,int version) { super(context, name, factory,version); } //创建表等机构性文件 public void onCreate(SQLiteDatabase db) { String sql ="create tablediary"+ "("+ "_idinteger primary key autoincrement,"+ "topicvarchar(100),"+ "contentvarchar(1000)"+ ")"; db.execSQL(sql); } //若数据库版本有更新,则调用此方法 public void onUpgrade(SQLiteDatabasedb,int oldVersion,int newVersion) { String sql = "drop table ifexists diary"; db.execSQL(sql); this.onCreate(db); } } 页面上现有ProgressBar控件progressBar,请用书写线程以10秒的的时间完成其进度显示工作。 public class ProgressBarStu extends Activity { private ProgressBar progressBar = null; protected void onCreate(BundlesavedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.progressbar); //从这到下是关键 progressBar = (ProgressBar)findViewById(R.id.progressBar); Thread thread = new Thread(newRunnable() { @Override public void run() { int progressBarMax =progressBar.getMax(); try { while(progressBarMax!=progressBar.getProgress()) { intstepProgress = progressBarMax/10; intcurrentprogress = progressBar.getProgress(); progressBar.setProgress(currentprogress+stepProgress); Thread.sleep(1000); } } catch(InterruptedException e) { // TODO Auto-generatedcatch block e.printStackTrace(); } } }); thread.start(); //关键结束 } } onFreeze() renamed to onSaveInstanceState(),以便恢复在onCreate(Bundle)里面设置的状态。 如果后台的Activity由于某原因被系统回收了,onSaveInstanceState()在被系统回收之前(onPause()前面)保存当前状态。 当你的程序中某一个Activity A在运行时,主动或被动地运行另一个新的Activity B,这个时候A会执行onSaveInstanceState()。B完成以后又会来找A,这个时候就有两种情况:一是A被回收,二是A没有被回收,被回收的A就要重新调用onCreate()方法,不同于直接启动的是这回onCreate()里是带上了参数savedInstanceState;而没被收回的就直接执行onResume(),跳过onCreate()了。 ContentProvider: 提供了我们在应用程序之前共享数据的一种机制,而我们知道每一个应用程序都是运行在不同的应用程序的,数据和文件在不同应用程序之间达到数据的共享不是没有可能,而是显得比较复杂,而正好Android中的ContentProvider则达到了这一需求,比如有时候我们需要操作手机里的联系人,手机里的多媒体等一些信息,我们都可以用到这个ContentProvider来达到我们所需。 1)、ContentProvider为存储和获取数据提供了统一的接口。ContentProvide对数据进行封装,不用关心数据存储的细节。使用表的形式来组织数据。 2)、使用ContentProvider可以在不同的应用程序之间共享数据。 3)、Android为常见的一些数据提供了默认的ContentProvider(包括音频、视频、图片和通讯录等)。 总的来说使用ContentProvider对外共享数据的好处是统一了数据的访问方式。 Uri为系统的每一个资源给其一个名字,比方说通话记录。每一个ContentProvider都拥有一个公共的URI,这个URI用于表示这个ContentProvider所提供的数据。 请解释下Android程序运行时权限与文件系统权限的区别。 运行时权限Dalvik( android授权) 文件系统 linux 内核授权 什么是ANR 如何避免它? 在Android里,应用程序的响应性是由Activity Manager和Window Manager系统服务监视的。当它监测到以下情况中的一个时,Android就会针对特定的应用程序显示ANR: 在5秒内没有响应输入的事件(例如,按键按下,屏幕触摸) BroadcastReceiver在10秒内没有执行完毕。 Android应用程序通常是运行在一个单独的线程(例如,main)里。这意味着你的应用程序所做的事情如果在主线程里占用了太长的时间的话,就会引发ANR对话框,因为你的应用程序并没有给自己机会来处理输入事件或者Intent广播。 在主线程里尽量的少做事情,比如高耗时的计算和网络、数据库等潜在的耗时操作都应该放在子线程来完成。
版权声明:尊重博主原创文章,转载请注明出处哦~http://blog.csdn.net/eson_15/article/details/51454428 目录(?)[+] 1. 数据库连接池 JDBC部分的前两个总结主要总结了一下JDBC的基本操作,而且有个共同点,就是应用程序都是直接获取数据库连接的。这会有个弊端:用户每次请求都需要向数据库获得连接,而数据库创建连接通常需要消耗相对较大的资源,创建时间也较长。假设一个网站每天有10万次访问量,那么数据库服务器就需要创建10万次连接,这极大的浪费数据库的资源,并且极易造成数据库服务器内存溢出、脱机。 为了解决上述问题,数据库连接池的技术便得到了广泛的使用。为了优化性能,应用程序一启动,就向数据库要一批连接放到池(也就是一个集合而已)中,当用户箱操作数据库的时候,直接找连接池要,当完成对数据库的操作后,再把连接还给连接池供其他用户使用。 有了上面的思路,我们可以自己写一个数据库的连接池,主要使用动态代理技术。 2. 自己编写的数据库连接池 2.1 连接池JdbcPool类的实现 编写连接池需要实现Java.sql.DateSource接口。DateSource接口中定义了两个重载的getConnection方法:Connection getConnection(); 方法和 Connection getConnection(String username, String password); 方法,实现连接池功能的步骤如下: 在DateSource构造函数或者静态代码块中批量创建与数据库的连接,并把创建的连接加入LinkedList对象中; 实现getConnection方法,让getConnection方法每次调用时,从LinkedList中取一个Connection返回给用户; 当用户使用完Connection,调用Connection.close()方法时,Connection对象应保证将自己返回到LinkedList中(编程的难点,动态代理实现),而不要把conn还给数据库。 public class JdbcPool implements DataSource { private static LinkedList<Connection> list = new LinkedList<Connection>(); static{ try{ InputStream in = JdbcPool.class.getClassLoader().getResourceAsStream("db.properties"); Properties prop = new Properties(); prop.load(in);//加载配置文件 String driver = prop.getProperty("driver"); String url = prop.getProperty("url"); String username = prop.getProperty("username"); String password = prop.getProperty("password"); Class.forName(driver); //加载驱动 //连接池中创建10个Connection for(int i = 0; i < 10; i++){ Connection conn = DriverManager.getConnection(url, username, password); System.out.println("获取到了连接" + conn); list.add(conn); } } catch(Exception e){ e.printStackTrace(); throw new ExceptionInInitializerError(e); } } /* * 用动态代理,返回一个代理对象出去,拦截close方法的调用,对close进行增强 */ @Override public synchronized Connection getConnection() throws SQLException { if(list.size() > 0){ final Connection conn = list.removeFirst();//删掉并返回给conn // return conn;//这里不能直接return,因为用户使用完了后,调用conn.close()会操作数据库,并没有把这个conn返回给连接池中 System.out.println("池大小是" + list.size()); //下面用动态代理技术来写: //动态代理技术:使用的是拦截技术 return (Connection) Proxy.newProxyInstance(JdbcPool.class.getClassLoader(), new Class[]{Connection.class}, new InvocationHandler() { //这里的第二个参数原来为conn.getClass.getInterface(),不过会出错,最终改成了new Class[]{Connection.class} //原因见帖子:http://blog.csdn.net/njchenyi/article/details/3091092 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if(!method.getName().equals("close")){//如果判断不是调用close方法,不管就是了 return method.invoke(conn, args); } else{//如果是调用close方法,将conn放到连接池里 list.add(conn); System.out.println(conn + "被还到池中"); System.out.println("池大小为" + list.size()); return null; } } }); } else{ throw new RuntimeException("对不起,数据库忙"); } } //下面是其他需要实现的方法,默认生成即可,不用写代码 @Override ...... } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 2.2 Jdbc工具类JdbcUtils的实现 有了上面的连接池,我们在工具类中获取Collection就不用像原来那样使用DriverManager来获取Connection了,我们可以直接使用自己的连接池了,如下: public class JdbcUtils { private static JdbcPool pool = new JdbcPool();//定义一个连接池 public static Connection getConnection() throws SQLException { return pool.getConnection();//直接从连接池中获取一个Connection } public static void release(Connection conn, Statement st, ResultSet rs) { if(rs != null){ try{ rs.close(); }catch(Exception e) { e.printStackTrace(); } rs = null; } if(st != null){ try{ st.close(); }catch(Exception e) { e.printStackTrace(); } st = null; } if(conn != null){ try{ conn.close(); }catch(Exception e) { e.printStackTrace(); } conn = null; } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 2.3 写一个测试用例 我们写一个模拟转账的程序来测试一下自己写的连接池能不能正常使用: public class Demo1 { //模拟转账 @Test public void changeAccount () { Connection conn = null; PreparedStatement st = null; ResultSet rs = null; Savepoint sp = null; try{ conn = JdbcUtils.getConnection(); conn.setAutoCommit(false);//相当于开启事务start transaction String sql1 = "update account set money=money-100 where name=?"; st = conn.prepareStatement(sql1); st.setString(1, "aaa"); st.executeUpdate(); // sp = conn.setSavepoint(); // int x = 1 / 0; //故意让程序抛异常,用来抓取然后手动回滚,测回滚用的 String sql2 = "update account set money=money+100 where name=?"; st = conn.prepareStatement(sql2); st.setString(1, "bbb"); st.executeUpdate(); conn.commit(); }catch(Exception e){ try { conn.rollback(sp);//回滚到指定位置,该位置之前的sql都被有效处理 conn.commit();//回滚了要记得提交 } catch (SQLException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } e.printStackTrace(); }finally{ JdbcUtils.release(conn, st, rs); } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 3. 开源数据库连接池 现在很多web服务器(Weblogic,WebSphere, Tomcat)都提供了DataSource的实现,即连接池的实现。通常我们把DataSource的实现,按其英文含义称之为数据源,数据源中都包含了数据库连接池的实现。也有一些开源组织提供了数据源的独立实现:如DBCP数据库连接池和C3P0数据库连接池(spring中用的就是这个)。使用这些连接池时不需要编写连接数据库的代码,直接从数据源获得数据库的连接,程序员编程时也尽量使用这些数据源的实现,以提升程序的数据库访问性能。 下面我们来学习一下这些开源数据库的连接池。 3.1 DPCP连接池 DBCP是Apache软件基金组织下的开源连接池实现,使用DBCP数据源需要在程序中加入下面两个jar包(dbcp的jar包下载地址:http://download.csdn.net/detail/eson_15/9525736): commons-dbcp.jar:连接池的实现; commons-pool.jar:连接池实现的依赖库 Tomcat的连接池正是采用DBCP连接池来实现的,该数据库连接池既可以与应用服务器整合使用,也可以独立使用,下面我们使用DBCP来改写上面的JdbcUtils工具类: public class JdbcUtils_DBCP { private static DataSource ds = null;//定义数据源 static{ try{ InputStream in = JdbcUtils_DBCP.class.getClassLoader().getResourceAsStream("dbcp.properties"); Properties prop = new Properties(); prop.load(in); BasicDataSourceFactory factory = new BasicDataSourceFactory();//数据源工厂 ds = factory.createDataSource(prop);//工厂产生数据源 System.out.println(ds);//打印出来瞧瞧是何方神圣~~ }catch(Exception e){ throw new ExceptionInInitializerError(e); } } public static synchronized Connection getConnection() throws SQLException { Connection conn = ds.getConnection();//从数据源中拿一个Connection来用~ return conn; } public static void release(Connection conn, Statement st, ResultSet rs) { if(rs != null){ try{ rs.close(); }catch(Exception e) { e.printStackTrace(); } rs = null; } if(st != null){ try{ st.close(); }catch(Exception e) { e.printStackTrace(); } st = null; } if(conn != null){ try{ conn.close(); }catch(Exception e) { e.printStackTrace(); } conn = null; } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 从上面代码中可以看出,获得了数据源dataSource后,就可以直接通过这个数据源拿到Connection,说明拿之前连接池中已经放好了一些Connection了,这些都已经被DBCP封装好了,我们不用去管,我们需要做的就是在配置文件中做一些配置,DBCP会根据配置文件中的配置去初始化数据库连接池的。我们看一下都需要配置啥: #连接设置 driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/databasename username=root password=root #初始化连接:10个 initialSize=10 #最大连接数量 maxActive=50 #最大空闲连接 maxIdle=20 #最小空闲连接 minIdle=5 #超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 maxWait=60000 #JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;] #注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。 connectionProperties=useUnicode=true;characterEncoding=gbk #指定由连接池所创建的连接的自动提交(auto-commit)状态。 defaultAutoCommit=true #driver default 指定由连接池所创建的连接的只读(read-only)状态。 #如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix) defaultReadOnly= #driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。 #可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE defaultTransactionIsolation=READ_UNCOMMITTED 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 上面就是DBCP连接池的基本配置,我们只要配置好了,它就会自己根据配置文件中的配置进行初始化。 3.2 C3P0连接池 用C3P0数据库连接池,需要导入下面两个jar包(c3p0的jar包下载地址:http://download.csdn.net/detail/eson_15/9525734): c3p0-0.9.5.1.jar mchange-commons-java-0.2.10.jar 这样就可以使用C3P0来改写JdbcUtils工具类了: public class JdbcUtils_C3P0 { private static ComboPooledDataSource ds = null; static { try { //配置文件可以用properties文件,也可以用xml,这里使用xml配置,下面给出配置好的xml(要放在类路径下) ds = new ComboPooledDataSource(); //使用默认配置 //ds = new ComboPooledDataSource("mysql"); //指定配置 } catch (Exception e) { throw new ExceptionInInitializerError(e); } } public static synchronized Connection getConnection() throws SQLException { Connection conn = ds.getConnection(); return conn; } public static void release(Connection conn, Statement st, ResultSet rs) {....} } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 下面看一下C3P0的配置文件c3p0-config.xml: <?xml version="1.0" encoding="UTF-8"?> <c3p0-config> <default-config> <!--默认配置--> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/databasename</property> <property name="user">root</property> <property name="password">root</property> <property name="acquireIncrement">5</property> <property name="initialPoolSize">10</property> <property name="minPoolSize">5</property> <property name="maxPoolSize">20</property> <!-- intergalactoApp adopts a different approach to configuring statement caching --> <property name="maxStatements">0</property> <property name="maxStatementsPerConnection">5</property> <!-- he's important, but there's only one of him --> </default-config> <named-config name="mysql"> <!--mysql的配置,在new ComboPooledDataSource()时候括号中指定,所以还可以再对oracle进行配置--> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/databasename</property> <property name="user">root</property> <property name="password">root</property> <property name="acquireIncrement">5</property> <property name="initialPoolSize">10</property> <property name="minPoolSize">5</property> <property name="maxPoolSize">20</property> <!-- intergalactoApp adopts a different approach to configuring statement caching --> <property name="maxStatements">0</property> <property name="maxStatementsPerConnection">5</property> <!-- he's important, but there's only one of him --> </named-config> </c3p0-config> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 我们可以看出,C3P0的配置文件中可以指定哪个数据库,也可以指定默认的数据库,这就很方便了,我们在获得数据源的时候就可以直接用自己配置的参数指定即可。 4. 配置Tomcat数据源 这种方式在开发中也用的比较多。Tomcat服务器在启动时可以帮我们创建一个池,这样我们可以直接利用这个连接池,但是需要进行配置。在META-INF目录下新建一个context.xml文档(也可以在WEB-INF目录下的web.xml中进行配置),然后在里面进行如下配置: <Context> <Resource name="jdbc/EmployeeDB" auth="Container" type="javax.sql.DataSource" username="root" password="root" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/day16" maxTotal="8" maxIdle="4"/> </Context> 1 2 3 4 5 6 7 8 9 10 11 12 1 2 3 4 5 6 7 8 9 10 11 12 其他参数可以参照dbcp的配置文件进行设置,因为tomcat连接池内部就是dbcp。然后新建一个servlet,在servlet中编写如下代码(Tomcat连接池的模板代码): try { Context initCtx = new InitialContext();// 初始化jndi Context envCtx = (Context) initCtx.lookup("java:comp/env");// 得到jndi容器 DataSource ds = (DataSource) envCtx.lookup("jdbc/EmployeeDB");// 从容器中检索连接池 Connection conn = ds.getConnection(); System.out.println(conn); } catch (Exception e) { e.printStackTrace(); } 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 启动服务器,运行该servlet即可在控制台打印出连接信息。 注:如果程序抛出异常,可能是eclipse版本问题,低版本需要将工程中MySQL-connector-java-5.1.26-bin.jar包拷贝到Tomcat服务器的lib文件夹下。 好了,关于JDBC的内容就介绍这么多吧~如有错误之处,欢迎留言指正~ 相关阅读:http://blog.csdn.net/column/details/servletandjsp.html —–乐于分享,共同进步! —–更多文章请看:http://blog.csdn.net/eson_15
版权声明:尊重博主原创文章,转载请注明出处哦~http://blog.csdn.net/eson_15/article/details/51425010 目录(?)[+] 上一节我们做完了购物车的基本操作,但是有个问题是:当用户点击结算时,我们应该做一个登录的判断,判断用户有没有登录,没有登录的话,得首先让用户登录。这就用到了过滤器的技术了,过滤器是专门拦截页面请求的,它与拦截器的原理差不多,拦截器是专门拦截Action请求的,所以各有所用,如果直接是页面的跳转,不经过Action的话,我们只要写一个拦截器即可,如果需要跳转到一个Action处理,那么我们就得写一个拦截器。 1. 登录跳转的原理 先说一下实现原理:写一个过滤器,在web.xml中配置一下需要拦截的url,这样的话,当用户的请求url中满足配置的话,就会执行我们自己写的过滤器,在过滤器中,我们首先检查session中有没有登录过的user,如果没有说明没有登录,然后拿到用户想要访问的页面url和参数,重新拼接成url放到session中,然后重定向到登陆页面,登录后跳转到Action处理,处理完后跳转到session中保存的url,即原来用户想去的地方。这样就完成了登陆的跳转了。 2. 登录跳转的实现 当现实购物车页面后,我们点击结账,它会自动跳转到订单确认的页面,如下: 但是此时如果用户没登录,我们肯定不能直接跳到订单确认页面,所以我们要用过滤器拦下来判断一下,下面写过滤器: 2.1 过滤器的实现 过滤器的实现要实现Filter接口,并覆写三个方法即可,其实我们主要要覆写其中一个方法即可。如下: [java] view plain copy public class UserFilter implements Filter { @Override public void destroy() { // TODO Auto-generated method stub } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse res = (HttpServletResponse) response; // 判断当前session是否有用户信息 if(req.getSession().getAttribute("user") == null) { //保存当前客户想要去的url地址 String goURL = req.getServletPath();//获得用户想要去的地址 String param = req.getQueryString(); //获得地址中携带的参数 if(param != null) { goURL = goURL + "?" + param; //重新拼好请求地址+参数 } //把当前客户想要访问的地址,存储到session中 req.getSession().setAttribute("goURL", goURL); //非法请求,跳转到登陆页面 req.getSession().setAttribute("error", "非法请求,请登录!"); res.sendRedirect(req.getContextPath() + "/ulogin.jsp"); } else { //如果有下一个过滤器则跳转,否则直接到目标页面 chain.doFilter(request, response); } } @Override public void init(FilterConfig config) throws ServletException { // TODO Auto-generated method stub } } 从实现的代码来看,主要腹泻了doFilter方法,在方法里,首先判断当前session中是否有用户的信息,如果没有,说明没有登录,那么要先将用户想要去的url地址和地址中的参数保存下来,拼成新的url存到session中,然后重定向到登陆页面,让用户登陆。如果session中有用户信息,说明已经登录过了,直接放行到用户想去的页面。 写好了Filter,别忘了在web.xml中配置要过滤的url,配置如下: 所以会过滤上面的${shop}/user/confirm.jsp。接下来我们看看登陆页面,其实就是两个框框,用户名和密码,主要看它跳到哪个Action去: 我们看到,它跳转到了userAction中的login方法去执行逻辑。下面我们实现userAction: 2.2 Action的实现 在userAction中,我们首先进行登陆的判断,即在数据库中查找有没有该用户名和密码的用户,如果成功,则将user存到session中,然后返回一个结果,交给struts2处理,代码如下: [java] view plain copy @Controller("userAction") @Scope("prototype") public class UserAction extends BaseAction<User> { public String login() { //进行登陆的判断 model = userService.login(model); if(model == null) { session.put("error", "登陆失败"); return "login"; } else { //登录成功,先将用户存储到session中 session.put("user", model); //根据session中goURL是否有值而决定页面的跳转 if(session.get("goURL") == null) { return "index"; //跳到首页 } else { return "goURL"; } } } } 我们看看struts.xml中的配置: 因为我们把goURL存在session中了,但是在struts.xml中我们不能像在Java代码里去拿session,然后拿参数,但是我们可以从值栈中取,上面是从值栈中取数据的方法。 2.3 Service层的登陆判断 Service层主要就是上面Action中用到的login方法,实现比较简单,如下: [java] view plain copy //userService接口 public interface UserService extends BaseService<User> { //用户登陆,成功返回该User public User login(User user); } //userServiceImpl实现类 @Service("userService") public class UserServiceImpl extends BaseServiceImpl<User> implements UserService { @Override public User login(User user) { String hql = "from User u where u.login=:login and u.pass=:pass"; return (User) getSession().createQuery(hql) // .setString("login", user.getLogin()) // .setString("pass", user.getPass()) // .uniqueResult(); } } 好了,这样我们用过滤器实现了用户登录的判断与跳转,登陆过后,就能跳转到订单确认页面了,效果如下: 整个流程测试完毕,功能正常。其实这里还可以再完善一点,我们其实应该在加入购物车之前就应该要进行登录判断,也就是说,购物车页面的时候已经是登录状态了,这里是订单确认页面判断登录的。不过在购物车页面前进行判断的话,我们就不好用过滤器了,我们得用拦截器,因为跳转到购物车页面请求的是Action,不是普通页面,请求Action的时候我们得用拦截器拦截来判断,后期再来完善这里吧,现在先把这里的功能基本实现了再说~好了,登录判断与跳转就做好了。 (注:到最后我会提供整个项目的源码下载!欢迎大家收藏或关注) 相关阅读:http://blog.csdn.net/column/details/str2hiberspring.html_____________________________________________________________________________________________________________________________________________________ -----乐于分享,共同进步! -----更多文章请看:http://blog.csdn.net/eson_15
版权声明:尊重博主原创文章,转载请注明出处哦~http://blog.csdn.net/eson_15/article/details/51423300 最近在写网上商城项目的时候学习了一个关于session的序列化问题,过来总结一下。 众所周知,session是服务器端的一种会话技术,只要session没有关闭,一个会话就会保持。这里先引出一个问题:如果我在访问某个页面后,服务器重启了一下,但是网页还没关,那么原来的session还在么?答案是很明显的,你都把服务器关掉了,session肯定不是原来的session了,原来的像登录信息等一些跟session相关的信息肯定就没了。但是如果我们想要服务器重启后,还是原来的session,那跟如何做呢? 这就涉及到了一个叫序列化(Serializable)的技术。当对象存储到硬盘的时候,就需要实现序列化接口,序列化的功能就是添加了一个唯一的ID(类主键),这样在反序列化(从硬盘加载到内存)的时候就可以成功找到相应的对象。另外,还要弄清楚一件事情:一般大家都觉得容器关闭后,session就销毁了,其实不是这样的,容器的关闭并不会导致session的销毁。过程是这样子的,一旦容器关闭后,session就会被持久化到硬盘,并没有真正销毁,为了说明这个问题,来做个试验:打开tomcat的工作目录下正在运行的工程目录:我的是E:\web\apache-tomcat-8.0.26\work\Catalina\localhost\E_shop,里面只有一个org的文件夹,其他什么也没有,现在我们重启tomcat服务器,注意观察这里面的变化,当服务器停掉后,这个该目录下多了个SESSION.ser文件,服务器重启成功后,该文件又消失了。如下: 所以,如果项目中的POJO实现了Serializable接口,当反序列化的时候就能找到刚刚序列化时候的POJO,原来session中的内容就能成功反序列化,session还是原来的session,这样原来页面的东西还在,刷新后还是继续上次的操作。如果POJO没有被实例化,那么在session发序列化的时候当然就没有了这些POJO了。下面看一下我的项目中的部分POJO,如下: 最后总结一下: 1. 容器关闭后session并没有消失,而是被持久化到了硬盘里; 2. 如果项目中的POJO实现了Serializable接口,那么会跟着session一起被持久化到硬盘,在反序列化的时候会成功还原; 3. 要想服务器重启后,还是原来的session,还继续紧接着原来的页面操作的话,就需要实例化项目中的POJO。 _____________________________________________________________________________________________________________________________________________________ -----乐于分享,共同进步! -----更多文章请看:http://blog.csdn.net/eson_15
版权声明:尊重博主原创文章,转载请注明出处哦~http://blog.csdn.net/eson_15/article/details/51387378 目录(?)[+] 上一节我们做完了首页UI界面,但是有个问题:如果我在后台添加了一个商品,那么我必须重启一下服务器才能重新同步后台数据,然后刷新首页才能同步数据。这明显不是我们想要的效果,一般这种网上商城首页肯定不是人为手动同步数据的,那么如何解决呢?我们需要用到线程和定时器来定时自动同步首页数据。 1. Timer和TimerTask 我们需要用到Timer和TimerTask两个类。先来介绍下这两个类。 Timer是一种工具类,在Java.util包中,线程用其安排以后在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行。它有个构造函数: [java] view plain copy Timer(boolean isDaemon) //创建一个新计时器,可以指定其相关的线程作为守护程序运行。 守护线程即主线程结束后,该线程也结束,非守护线程即主线程结束后,该线程仍然继续执行。isDaemon为true时为守护线程。Timer类有个schedule方法可以创建一个任务,如下: [java] view plain copy void schedule(TimerTask task, Date firstTime, long period) //安排指定的任务在指定的时间开始进行重复的固定延迟执行。 //第一个参数是指定任务,即TimerTask对象;第二个参数为第一次开启任务时间;第三个参数为时间间隔,即每隔多长时间执行一次 我们再来看看TimerTask,TimerTask是用来创建一个新的线程任务的,它实现了Runnable接口,如果我们要创建一个新的线程任务,只需要继承TimerTask,并重写run方法即可。 2. 创建一个新的线程任务 下面我们来创建一个新的线程任务,用来更新后台数据: [java] view plain copy @Component //把该对象交给Spring管理 public class ProductTimerTask extends TimerTask { @Resource private ProductService productService = null; //注入productService @Resource private CategoryService categoryService = null; //注入categoryService private ServletContext application = null; //定义一个ServletContext对象,因为我们更新了后台数据后,需要存入application域里面 public void setApplication(ServletContext application) { this.application = application; //通过监听器将这个application对象set进来,因为这里是无法拿application对象的 } @Override //和监听器在项目启动的时候数据初始化的逻辑一样 public void run() { System.out.println("----run----"); List<List<Product>> bigList = new ArrayList<List<Product>>(); //bigList中存放一个装有Category类的list // 1. 查询出热点类别 for(Category category : categoryService.queryByHot(true)) { //根据热点类别id获取推荐商品信息 List<Product> lst = productService.querByCategoryId(category.getId()); bigList.add(lst); //将装有category的list放到bigList中 } // 2. 把查询的bigList交给application内置对象 application.setAttribute("bigList", bigList); //假设我们已经拿到了application对象 } } 接下来,我们修改项目启动时监听器里面的内容,原本上面的这个查询操作是放在监听器中,当项目启动时,监听器开始执行,获取后台数据,存到application域中,然后前台通过jstl标签从application域中拿到数据。现在我们把这些事情交给我们定义的ProductTimerTask去做,那么监听器中只要设置一下定时器,让ProductTimerTask定时去更新一下后台数据即可。看看监听器中修改后的代码: 3. 在监听器中启动定时器 [java] view plain copy //@Component //监听器是web层的组件,它是tomcat实例化的,不是Spring实例化的。不能放到Spring中 public class InitDataListener implements ServletContextListener { private ProductTimerTask productTimerTask = null; //定义一个ProductTimerTask对象 private ApplicationContext context = null; @Override public void contextDestroyed(ServletContextEvent event) { // TODO Auto-generated method stub } @Override public void contextInitialized(ServletContextEvent event) { context = WebApplicationContextUtils.getWebApplicationContext(event.getServletContext()); productTimerTask = (ProductTimerTask) context.getBean("productTimerTask");//从配置文件中获取ProductTimerTask对象 //把内置对象交给productTimerTask,因为productTimerTask里面是拿不到application的,只能通过监听器set给它 productTimerTask.setApplication(event.getServletContext()); //通过设置定时器,让首页的数据每个一小时同步一次(配置为守护线程) new Timer(true).schedule(productTimerTask, 0, 1000*60*60);//每个一小时执行一次productTimerTask任务,即更新一下后台数据 } } 关于InitDataListener监听器中原来的操作代码,可以对比上一节中的内容,其实就是ProductTimerTask中的更新后台数据,只不过现在放到TimerTask中去做了而已。这样我们就完成了使用线程和定时器定期同步首页数据,这个时间间隔可以自己设定。 其实CSDN博客里的部分首页数据也不是实时更新的,每天晚上会有个时间更新一次,例如左侧栏目中的博客排名,阅读排行后的显示的阅读量等,这些都是每天晚上更新一次,应该就是在后台设置了每天更新一次,原理跟这里应该是一样的。这样也减轻了服务器的压力。 (注:到最后提供整个项目的源码下载!欢迎大家收藏或关注) 相关阅读:http://blog.csdn.net/column/details/str2hiberspring.html _____________________________________________________________________________________________________________________________________________________ -----乐于分享,共同进步! -----更多文章请看:http://blog.csdn.net/eson_15
版权声明:尊重博主原创文章,转载请注明出处哦~http://blog.csdn.net/eson_15/article/details/51373403 目录(?)[+] 前面我们利用EasyUI和SSH搭建好了后台的基本框架,做好了后台的基本功能,包括对商品类别的管理和商品的管理等,这一节我们开始搭建前台页面。 做首页的思路:假设现在商品的业务逻辑都有了,首先我们需要创建一个监听器,在项目启动时将首页的数据查询出来放到application里,即在监听器里调用后台商品业务逻辑的方法。 1. 首页商品显示逻辑 在首页,我们只显示商品热点类别中的前几个商品,比如热点类别有儿童休闲类,女性休闲类,男性休闲类,那我们会有三个板块来显示不同的商品类,每个类别里再显示几个具体的商品。如果要实现这样的首页的话,我们需要将哪些数据查询出来呢?首先肯定是热点类别,即在数据库中查询类别是热点的项,然后再从数据库中根据热点类别级联查询该类别的商品,这样我们所要的数据就都有了。下面我们先在后台完成这些查询业务: [java] view plain copy //CategoryService接口 public interface CategoryService extends BaseService<Category> { //省略其他方法…… //根据boelen值查询热点或非热点类别 public List<Category> queryByHot(boolean hot); } @SuppressWarnings("unchecked") @Service("categoryService") public class CategoryServiceImpl extends BaseServiceImpl<Category> implements CategoryService { //省略其他方法…… @Override public List<Category> queryByHot(boolean hot) { String hql = "from Category c where c.hot=:hot"; return getSession().createQuery(hql) .setBoolean("hot", hot) .list(); } } [java] view plain copy //ProductService接口 public interface ProductService extends BaseService<Product> { //省略其他方法…… //根据热点类别查询推荐商品(仅仅查询前4个) public List<Product> querByCategoryId(int cid); } @SuppressWarnings("unchecked") @Service("productService") public class ProductServiceImpl extends BaseServiceImpl<Product> implements ProductService { //省略其他方法…… @Override public List<Product> querByCategoryId(int cid) { String hql = "from Product p join fetch p.category " + "where p.commend=true and p.open=true and p.category.id=:cid order by p.date desc"; return getSession().createQuery(hql) .setInteger("cid", cid) .setFirstResult(0) .setMaxResults(4) .list(); } } 2. 创建InitDataListener获取首页数据 后台完成了商品的显示逻辑业务,下面我们开始获取所需要的数据了。首先创建一个监听器InitDataListener继承ServletContextListener,关于监听器如何获取spring配置文件,请参考这篇博文:监听器如何获取Spring配置文件 [java] view plain copy //@Component //监听器是web层的组件,它是tomcat实例化的,不是Spring实例化的。不能放到Spring中 public class InitDataListener implements ServletContextListener { private ProductService productService = null; private CategoryService categoryService = null; private ApplicationContext context = null; @Override public void contextDestroyed(ServletContextEvent event) { // TODO Auto-generated method stub } @Override public void contextInitialized(ServletContextEvent event) { context = WebApplicationContextUtils.getWebApplicationContext(event.getServletContext()); categoryService = (CategoryService) context.getBean("categoryService");//加载类别信息 productService = (ProductService) context.getBean("productService");//加载商品信息 List<List<Product>> bigList = new ArrayList<List<Product>>(); //bigList中存放一个装有Category类的list // 1. 查询出热点类别 for(Category category : categoryService.queryByHot(true)) { //根据热点类别id获取推荐商品信息 List<Product> lst = productService.querByCategoryId(category.getId()); bigList.add(lst); //将装有category的list放到bigList中 } // 2. 把查询的bigList交给application内置对象 event.getServletContext().setAttribute("bigList", bigList); } } 并在web.xml中配置该监听器: 好了,现在数据全都放到bigList这个集合中了。 3.首页UI页面设计 UI首页我们会从美工那拿到模板,这个模板是html,我们要做的就是将其改成我们的jsp,然后将bigList集合中的数据显示在首页上。首先我们将模板所需要的图片和css拷贝到WebRoot目录下,然后在WebRoot/public/head.jspf中将这两个文件引入即可,因为head.jspf是其他页面都要包含进来的公共头: 然后将模板中的html嵌到前台首页index.jsp中去,使用jstl标签修改一下显示内容,如下所示(只截图显示商品那一部分): 现在我们进入之前做好的后台添加商品页面,在女性休闲类添加几个商品,然后启动tomcat,运行一下首页index.jsp,效果如下: 好了,前台的UI界面算是搭好了,接下来就是完成一些不同的业务了。 (注:到最后提供整个项目的源码下载!欢迎大家收藏或关注) 相关阅读:http://blog.csdn.net/column/details/str2hiberspring.html _____________________________________________________________________________________________________________________________________________________ -----乐于分享,共同进步! -----更多文章请看:http://blog.csdn.net/eson_15
版权声明:尊重博主原创文章,转载请注明出处哦~http://blog.csdn.net/eson_15/article/details/51366384 目录(?)[+] 上一节我们做完了添加和更新商品的功能,这两个部分里有涉及到商品图片的上传,并没有详细解说。为此,这篇文章详细介绍一下Struts2实现文件上传的功能。 1. 封装文件信息 我们首先得有一个Model来封装文件的信息,这个Model里需要有三个属性:文件、文件类型和文件名。针对我们要传的图片,我们新建一个Model如下: [java] view plain copy public class FileImage { private File file; private String contentType; private String filename; public File getFile() { return file; } public String getContentType() { return contentType; } public String getFilename() { return filename; } public void setUpload(File file) { //set方法可以不用和属性名一样,但是前台传进来时的参数得和set方法名相同。即前台传的参数为fileImage.upload this.file = file; } public void setUploadContentType(String contentType) { this.contentType = contentType; } public void setUploadFileName(String filename) { this.filename = filename; } } 这样Model就写好了,考虑到文件上传的逻辑不是单个Action所特有的,所以我们将文件上传的逻辑写到工具类中,这样可供所有的Action调用。所以我们新建一个文件上传工具类(为了面向接口编程,我们也将工具类抽出个接口): 2. 完成文件上传工具类 [java] view plain copy //文件上传工具类接口 public interface FileUpload { //实现文件上传的功能,返回上传后新的文件名称 public abstract String uploadFile(FileImage fileImage); } //文件上传工具类具体实现 @Component("fileUpload") public class FileUploadUtil implements FileUpload { private String filePath; @Value("#{prop.filePath}") //@Value表示去beans.xml文件中找id="prop"的bean,它是通过注解的方式读取properties配置文件的,然后去相应的配置文件中读取key=filePath的值 public void setFilePath(String filePath) { System.out.println(filePath); this.filePath = filePath; } //1. 通过文件名获取扩展名 private String getFileExt(String fileName) { return FilenameUtils.getExtension(fileName); } //2. 生成UUID随机数,作为新的文件名 private String newFileName(String fileName) { String ext = getFileExt(fileName); return UUID.randomUUID().toString() + "." + ext; } //实现文件上传的功能,返回上传后新的文件名称 @Override public String uploadFile(FileImage fileImage) { //获取新唯一文件名 String pic = newFileName(fileImage.getFilename()); try { FileUtil.copyFile(fileImage.getFile(), new File(filePath, pic));//第一个参数是上传的文件,第二个参数是将文件拷贝到新路径下 return pic; } catch (Exception e) { throw new RuntimeException(e); } finally { fileImage.getFile().delete(); } } } 上面有个@Value注解,是从properties文件中获取文件要存入的路径的,具体可参见:Spring获取配置文件信息 。 3. 在Action中注入封装文件类和工具类 写好了文件封装类和上传文件工具类后,我们需要将这两个对象注入到我们的Action中,这样就可以在Action中实现文件上传的功能了: [java] view plain copy @Controller("baseAction") @Scope("prototype") public class BaseAction<T> extends ActionSupport implements RequestAware,SessionAware,ApplicationAware,ModelDriven<T> { //封装了图片信息的类 protected FileImage fileImage; //上传文件工具类 @Resource protected FileUpload fileUpload; public FileImage getFileImage() { return fileImage; } public void setFileImage(FileImage fileImage) { this.fileImage = fileImage; } //省略其他无关代码…… } 4. 实现文件的上传 好了,现在我们可以在ProductAction中去实现文件上传了,工具类写好的话,在Action中的代码量就很少了,这也是封装带来的优点。 [java] view plain copy @Controller("productAction") @Scope("prototype") public class ProductAction extends BaseAction<Product> { //省略其他无关代码…… public void save() throws Exception { //fileUpload工具类被抽取了,uploadFile方法直接接受一个fileImage对象,返回新的图片名 String pic = fileUpload.uploadFile(fileImage); model.setPic(pic); model.setDate(new Date()); System.out.println(model); //商品信息入库 productService.save(model); } public void update() { String pic = fileUpload.uploadFile(fileImage); model.setPic(pic); model.setDate(new Date()); System.out.println(model); //更新商品 productService.update(model); } } 这样我们就完成了从前台上传文件的功能。 (注:到最后我会提供整个项目的源码下载!欢迎大家收藏或关注) 相关阅读:http://blog.csdn.net/column/details/str2hiberspring.html_____________________________________________________________________________________________________________________________________________________ -----乐于分享,共同进步! -----更多文章请看:http://blog.csdn.net/eson_15
版权声明:尊重博主原创文章,转载请注明出处哦~http://blog.csdn.net/eson_15/article/details/51394302 hibernate中如果出现了级联查询,就可能出现懒加载问题,比如我现在有个Account(管理员)类、Category(商品类别)和Product(商品)类,从左到右都是一对多的关系,而且从右到左都是设置了@ManyToOne(fetch=FetchType.LAZY)。我现在要把商品信息查出来打包成json格式传到前台,我在后台使用查询语句为: [sql] view plain copy from Product p left join fetch p.category where p.name like:name 这样就可以把Product查出来了,然后Product中的Category也放进去了,但是Category中的Account不是实际对象,是暂时的代理对象,这点很好理解,因为我就查了Product而且只级联了Category,至于Category和Account就根据实际配置了(LAZY)。 现在将查询出来的product放到Map中,然后转成json格式返回到前台肯定会出现懒加载问题,因为在转json的过程中会拿Account对象,但是此时session已经关闭了,所以会报错,有个很直接但是不太好的解决办法就是将Category中的LAZY改成EAGER,这样就能把Account的信息也查出来,但是这样不好。所以我们用另一种办法:在struts.xml中设置一下黑名单,在转json格式的时候使用正则表达式将category中的account过滤掉,就不会去查account对象了,就不会有懒加载问题了。如下: 到这里,应该就没问题了。但是在我的项目中还是报懒加载异常,也就是说我这样配置后根本没有起作用。但是理论上,这样配置后就OK了,就可以正常的把数据打包成json格式传给前台了。这问题困扰了我2天,后来索性先将LAZY改成EAGER,先把项目往下做。 今天我在另一个Hibernate异常中,联系到了这里的异常,解决了!Hibernate中今天我要调用get方法获取商品的信息,无法获取到,后台的控制台没有任何消息,由于我开启了dev模式,前台显示了错误信息: [plain] view plain copy java.lang.ClassCastException:cn.it.shop.model.Product_$$_javassist_0 cannot be cast to javassist.util.proxy.Proxy</span> 无法转成代理??为啥要转成代理呢?一般不都是代理无法转成实际对象么?于是我上网搜索了一下,这个问题可能是由于项目中的一个javassist的jar包冲突了,我去工程中检查一下,果不其然: 还真的冲突了哟喂……于是删掉struts包中的那个javassist-3.11.0.GA.jar即可,Hibernate这边没错了,可以正常拿出商品信息了。然后我联想到了2天前struts2转json的问题,于是回去将EAGER改回LAZY,问题也没了,也能正常转json了,郁闷,还真是jar包冲突惹的祸。因为当时根本没有报错,只是前台那边我查不到返回的json数据,只知道没有返回json数据,肯定是后台转json出了问题,根据已有的经验,90%是懒加载的问题,但是没想到是jar包冲突惹的祸。 后话:如果jar包没冲突,但是无法转json,那基本上是懒加载惹的祸,利用struts.xml中配置黑名单的方式将懒加载的对象过滤掉的方法很实用,不用修改POJO中的配置,我想把哪些字段转到json中就转哪些,不想就不转,很方便。 _____________________________________________________________________________________________________________________________________________________ -----乐于分享,共同进步! -----更多文章请看:http://blog.csdn.net/eson_15
版权声明:尊重博主原创文章,转载请注明出处哦~http://blog.csdn.net/eson_15/article/details/51383298 最近在写网上商城项目时,遇到一个问题:hibernate在执行save()或者update()方法后,并没有任何效果,数据库中没有任何改动,而且控制台也没有报任何错,这让我很无语…… 我在网上查了下,有的人说是主键的自增长问题,有的人说是没有开启事务,所以无法写入或更新数据库,我详细看了他们的分析,说的都有道理,但是这些解决方法对我都不管用,因为我的主键是没有问题的,事务是由spring管理的,在其他save操作都可以,都没有问题。 既然客观上都没有问题,于是我把焦点放在了具体要save或update的对象上了,对POJO做了仔细的分析,首先看一下我要save的对象对应数据库中的表: [sql] view plain copy /*=============================*/ /* Table: 商品表结构 */ /*=============================*/ create table product ( /* 商品编号,自动增长 */ id int primary key not null auto_increment, /* 商品名称 */ name varchar(50), /* 商品价格 */ price decimal(8,2), /* 商品图片 */ pic varchar(300), /* 商品简单介绍 */ remark longtext, /* 商品详细介绍 */ xremark longtext, /* 商品生产日期 */ date timestamp default CURRENT_TIMESTAMP, /* 是否为推荐商品,推荐商品才有可能显示在商城首页 */ commend bool, /* 是否为有效商品,有效商品才有可能显示在商城首页 */ open bool, /* 商品所在的类别编号*/ cid int, constraint cid_FK foreign key(cid) references category(id) ); 然后具体的POJO就不贴上来了,就是根据这张表生成的一些字段属性以及set和get方法。我觉得最有可能出问题的字段应该就是这个时间date,于是我看了下POJO里关于date的代码: [java] view plain copy @Entity public class Product implements java.io.Serializable { // Fields private Timestamp date; //省略其他无关代码…… @Column(name = "date", nullable = false, length = 19) public Timestamp getDate() { return this.date; } public void setDate(Timestamp date) { this.date = date; } } 于是我再一次上网搜索了这个Timestamp,发现问题就出在这,将Timestamp改成java.util.Date即可。然后传进来一个Date对象,Hibernate会自动转成Timestamp类型。 这个问题也给我一个启示:无法执行数据库操作也有可能是对象本身的问题,要从表的字段和POJO属性之间来排查。 _____________________________________________________________________________________________________________________________________________________ -----乐于分享,共同进步! -----更多文章请看:http://blog.csdn.net/eson_15
版权声明:尊重博主原创文章,转载请注明出处哦~http://blog.csdn.net/eson_15/article/details/51360804 目录(?)[+] 在第8节我们完成了查询和删除商品类别的功能,那么现在实现查询和删除商品的功能就很好做了,原理和第8节一模一样,只是修改一些参数,比如请求不同的action等。由于查询和删除商品不需要弹出新的UI窗口,所以我们只要完成完成query.jsp中相应的部分以及相应的后台即可。 1. 查询商品功能的实现 查询功能主要在查询框中实现,从上一节可知,查询框用的是一个text:"<input id='ss' name='serach' />",我们通过把普通的文本框转化为查询搜索文本框来实现,下面我们在query.jsp中添加相应部分的代码: [javascript] view plain copy $('#ss').searchbox({ //触发查询事件 searcher:function(value,name){ //value表示输入的值 //添加触发代码 $('#dg').datagrid('load',{//重新load,参数name指定为用户输入value name: value }); }, prompt:'请输入搜索关键字' }); 测试结果如下: 查询很简单,跟上一节load所有商品一样,只不过查询的时候参数设为用户输入的值,加载所有的时候参数设为空即可。 2. 删除商品功能的实现 接下来做删除商品功能,首先我们把query.jsp中相应部分的代码补全: [javascript] view plain copy { iconCls: 'icon-remove', text:'删除商品', handler: function(){ //添加触发代码 var rows = $("#dg").datagrid("getSelections");//判断是否有选中行记录,使用getSelections获取选中的所有行 //返回被选中的行,如果没有任何行被选中,则返回空数组 if(rows.length == 0) { //弹出提示信息 $.messager.show({ //语法类似于java中的静态方法,直接对象调用 title:'错误提示', msg:'至少要选择一条记录', timeout:2000, showType:'slide', }); } else { //提示是否确认删除,如果确认则执行删除的逻辑 $.messager.confirm('删除的确认对话框', '您确定要删除此项吗?', function(r){ if (r){ //1. 从获取的记录中获取相应的的id,拼接id的值,然后发送后台1,2,3,4 var ids = ""; for(var i = 0; i < rows.length; i ++) { ids += rows[i].id + ","; } ids = ids.substr(0, ids.lastIndexOf(",")); //2. 发送ajax请求 $.post("product_deleteByIds.action",{ids:ids},function(result){ if(result == "true") { //将刚刚选中的记录删除,要不然会影响后面更新的操作 $("#dg").datagrid("uncheckAll"); //刷新当前页,查询的时候我们用的是load,刷新第一页,reload是刷新当前页 $("#dg").datagrid("reload");//不带参数默认为上面的queryParams } else { $.messager.show({ title:'删除异常', msg:'删除失败,请检查操作', timeout:2000, showType:'slide', }); } },"text"); } }); } } } 从上面代码中可以看出,删除操作需要先选中至少一条记录,选中后,当确认删除时(即r为真),首先获取用户都勾选了哪些记录,将这些记录的id号拼接起来,然后想后台发送ajax请求,请求productAction中的deleteByIds方法,将拼接好的id作为参数带过去,如果删除成功,则返回一个字符串"true"到前台,然后前台将刚刚勾选记录清掉,以免影响后面更新操作,因为更新也要勾选记录,之后再刷新当前页,reload数据库所有商品信息。 流程很清楚明了,下面我们写后台程序,先从service层开始: [java] view plain copy public interface ProductService extends BaseService<Product> { //查询商品信息,级联类别 public List<Product> queryJoinCategory(String type, int page, int size); //使用商品的名称查询 //根据关键字查询总记录数 public Long getCount(String type); //根据ids删除多条记录 public void deleteByIds(String ids); } @SuppressWarnings("unchecked") @Service("productService") public class ProductServiceImpl extends BaseServiceImpl<Product> implements ProductService { //省略其他代码…… @Override public void deleteByIds(String ids) { String hql = "delete from Product p where p.id in (" + ids + ")"; getSession().createQuery(hql).executeUpdate(); } } 接下来完成productAction中的deleteByIds方法: [java] view plain copy @Controller("productAction") @Scope("prototype") public class ProductAction extends BaseAction<Product> { //省略其他代码…… public String deleteByIds() { System.out.println(ids); productService.deleteByIds(ids); //如果删除成功就会往下执行,我们将"true"以流的形式传给前台 inputStream = new ByteArrayInputStream("true".getBytes()); return "stream"; } } 和之前删除商品类的思路相同,下面在struts.xml中配置: [html] view plain copy <action name="product_*" class="productAction" method="{1}"> <!-- 省略其他配置 --> <result name="stream" type="stream"> <param name="inputName">inputStream</param> </result> </action> 这样字符串"true"就通过流传到前台了,接收到说明删除成功。看一下效果: 测试成功,至此,商品的搜索和删除功能做完了。 (注:到最后我会提供整个项目的源码下载!欢迎大家收藏或关注) 相关阅读:http://blog.csdn.net/column/details/str2hiberspring.html_____________________________________________________________________________________________________________________________________________________ -----乐于分享,共同进步! -----更多文章请看:http://blog.csdn.net/eson_15
版权声明:尊重博主原创文章,转载请注明出处哦~http://blog.csdn.net/eson_15/article/details/51373937 目录(?)[+] 我们在做项目的时候,会用到监听器去获取spring的配置文件,然后从中拿出我们需要的bean出来,比如做网站首页,假设商品的后台业务逻辑都做好了,我们需要创建一个监听器,在项目启动时将首页的数据查询出来放到application里,即在监听器里调用后台商品业务逻辑的方法,也就是说我们需要在监听器里获取Spring中配置的相应的bean。先把监听器创建出来: 1. 创建InitDataListener 创建一个监听器InitDataListener继承ServletContextListener: [java] view plain copy /** * @Description: TODO(用于项目启动的时候数据初始化) * @author eson_15 * */ //@Component //监听器是web层的组件,它是tomcat实例化的,不是Spring实例化的。不能放到Spring中 public class InitDataListener implements ServletContextListener { private ProductService productService = null;//productService中定义了跟商品相关的业务逻辑 @Override public void contextDestroyed(ServletContextEvent event) { } @Override public void contextInitialized(ServletContextEvent event) { } } 并在web.xml中配置该监听器: 如上,productService中定义了商品的一些业务逻辑,并且这个productService是交给Spring管理的,那么我们如何得到这个对象呢?首先肯定的一点是:我们不能自己new出来,因为new出来的话就跟Spring的IoC没有关系了……主要有三种方式可以实现,我们先一个个分析,最后比较优劣。 2. 直接加载beans.xml文件 这种方式比较简单粗暴,不是要加载配置文件么?那好,我加载就是了,如下: [java] view plain copy //@Component //监听器是web层的组件,它是tomcat实例化的,不是Spring实例化的。不能放到Spring中 public class InitDataListener implements ServletContextListener { private ProductService productService = null; //productService中定义了跟商品相关的业务逻辑 @Override public void contextDestroyed(ServletContextEvent event) { } @Override public void contextInitialized(ServletContextEvent event) { // 获取业务逻辑类productService查询商品信息 ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); productService = (ProductService) context.getBean("productService"); System.out.println(productService); //输出看看拿到了没有 //下面是具体productService相关操作…… } } 这种方法完全没问题,思路很清晰,先加载配置文件beans.xml,然后获取bean,但是启动tomcat后,我们看看控制台输出的信息: 到这里应该发现这种方式的弊端了,加载了两次配置文件,也就是说那些bean被实例化了两次,从打印的信息来看,是拿到我们自己加载配置文件是实例化的bean。这种方式明显不可取。 3. 从ServletContext中获取 从上面的方法中,我们最起码可以知道,Spring通过自己的监听器已经加载过一次配置文件了,我们没必要再加载一次,那么很容易想到,如果知道Spring加载后放到哪里了,那我们就可以从那地方获取该配置文件,下面我们看下Spring加载配置文件的过程: 上图中(省略了无关的代码),ContextLoaderListener就是web.xml中我们配置的Spring监听器,它也实现了ServletContextListener并继承了ContextLoader。在监听器中主要通过initWebApplicationContext方法来获取配置文件,并创建WebApplicationContext对象,在initWebApplicationContext方法里主要做两件事:一是拿到Spring的上下文,二是把Spring上下文放到ServletContext中,并且键为:WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE。那么如何拿到Spring的上下文呢?是通过获取web.xml中配置的Spring的路径,CONFIG_LOCATION_PARM其实是个字符串常量,就是上面web.xml中配置Spring监听器下面的: [html] view plain copy <context-param> <param-name>contextConfigLocation</param-name> <!--CONFIG_LOCATION_PARM就是contextConfigLocation--> <param-value>classpath:beans.xml</param-value> </context-param> 所以就很明显了,通过web.xml中配置的路径拿到beans.xml,然后加载这个配置文件,实例化bean。 现在我们既然知道了Spring在加载配置文件后,把它放在了ServletContext中,那么我们就可以去这里面直接拿! [java] view plain copy //@Component //监听器是web层的组件,它是tomcat实例化的,不是Spring实例化的。不能放到Spring中 public class InitDataListener implements ServletContextListener { private ProductService productService = null; @Override public void contextDestroyed(ServletContextEvent event) { // TODO Auto-generated method stub } @Override public void contextInitialized(ServletContextEvent event) { // 获取业务逻辑类查询商品信息 // 解决方案二,项目在启动时,把Spring配置文件通过Spring的监听器加载,存储到ServletContext中,我们只要在ServletContext中获取即可。 ApplicationContext context = (ApplicationContext) event.getServletContext() .getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); productService = (ProductService) context.getBean("productService"); System.out.println(productService); } } 这样我们就可以拿到produceService的实例化对象了,这种方法好是好,就是getAttribute中的参数太长,也不知道当时程序员的脑门子被夹了还是咋地,估计是想不到其他更合适的名字了吧~ 4. 通过Spring提供的工具类加载 也许开发Spring的大牛们也意识到了这个参数名字太长了,于是他们提供了一个方法类,可以加载配置文件: [java] view plain copy public class InitDataListener implements ServletContextListener { private ProductService productService = null; @Override public void contextDestroyed(ServletContextEvent event) { // TODO Auto-generated method stub } @Override public void contextInitialized(ServletContextEvent event) { // 获取业务逻辑类查询商品信息 WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(event.getServletContext()); productService = (ProductService) context.getBean("productService"); System.out.println(productService); } } 其实,这里的getWebApplicationContext方法就是把上面的那个方法封装了一下而已,我们看看这个方法的源码就知道了: [java] view plain copy public static WebApplicationContext getWebApplicationContext(ServletContext sc) { return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); } 这样更加方便程序员调用,仅此而已……所以一般我们使用第三种方法来获取Spring的配置文件,从而获取相应的实例化bean。 _____________________________________________________________________________________________________________________________________________________ -----乐于分享,共同进步! -----更多文章请看:http://blog.csdn.net/eson_15
版权声明:尊重博主原创文章,转载请注明出处哦~http://blog.csdn.net/eson_15/article/details/51365707 目录(?)[+] 在项目中如果有些参数经常需要修改,或者后期可能需要修改,那我们最好把这些参数放到properties文件中,源代码中读取properties里面的配置,这样后期只需要改动properties文件即可,不需要修改源代码,这样更加方便。在spring中也可以这么做,而且Spring有两种加载properties文件的方式:基于xml方式和基于注解方式。下面分别讨论下这两种方式。 1. 通过xml方式加载properties文件 我们以Spring实例化dataSource为例,我们一般会在beans.xml文件中进行如下配置: [html] view plain copy <!-- com.mchange.v2.c3p0.ComboPooledDataSource类在c3p0-0.9.5.1.jar包的com.mchange.v2.c3p0包中 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver" /> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/shop" /> <property name="user" value="root" /> <property name="password" value="root" /> </bean> 现在如果我们要改变dataSource,我们就得修改这些源代码,但是我们如果使用properties文件的话,只需要修改那里面的即可,就不管源代码的东西了。那么如何做呢? Spring中有个<context:property-placeholder location=""/>标签,可以用来加载properties配置文件,location是配置文件的路径,我们现在在工程目录的src下新建一个conn.properties文件,里面写上上面dataSource的配置: [plain] view plain copy dataSource=com.mchange.v2.c3p0.ComboPooledDataSource driverClass=com.mysql.jdbc.Driver jdbcUrl=jdbc\:mysql\://localhost\:3306/shop user=root password=root 现在只需要在beans.xml中做如下修改即可: [html] view plain copy <context:property-placeholder location="classpath:conn.properties"/><!-- 加载配置文件 --> <!-- com.mchange.v2.c3p0.ComboPooledDataSource类在c3p0-0.9.5.1.jar包的com.mchange.v2.c3p0包中 --> <bean id="dataSource" class="${dataSource}"> <!-- 这些配置Spring在启动时会去conn.properties中找 --> <property name="driverClass" value="${driverClass}" /> <property name="jdbcUrl" value="${jdbcUrl}" /> <property name="user" value="${user}" /> <property name="password" value="${password}" /> </bean> <context:property-placeholder location=""/>标签也可以用下面的<bean>标签来代替,<bean>标签我们更加熟悉,可读性更强: [html] view plain copy <!-- 与上面的配置等价,下面的更容易理解 --> <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <!-- PropertyPlaceholderConfigurer类中有个locations属性,接收的是一个数组,即我们可以在下面配好多个properties文件 --> <array> <value>classpath:conn.properties</value> </array> </property> </bean> 虽然看起来没有上面的<context:property-placeholder location=""/>简洁,但是更加清晰,建议使用后面的这种。但是这个只限于xml的方式,即在beans.xml中用${key}获取配置文件中的值value。 2. 通过注解方式加载properties文件 还有一种就是通过注解的方式,在Java代码中使用@Value注解来加载配置文件中的值。 我们来看一个例子:假如我们要在程序中获取某个文件的绝对路径,我们很自然会想到不能在程序中写死,那么我们也可以卸载properties文件中。还是在src目录下新建一个public.properties文件,假设里面写了一条记录: [plain] view plain copy filePath=E\:\\web\\apache-tomcat-8.0.26\\webapps\\E_shop\\image 如果想在java代码中通过注解来获取这个filePath的话,首先得在beans.xml文件中配置一下注解的方式: [html] view plain copy <!-- 第二种方式是使用注解的方式注入,主要用在java代码中使用注解注入properties文件中相应的value值 --> <bean id="prop" class="org.springframework.beans.factory.config.PropertiesFactoryBean"> <property name="locations"><!-- 这里是PropertiesFactoryBean类,它也有个locations属性,也是接收一个数组,跟上面一样 <array> <value>classpath:public.properties</value> </array> </property> </bean> 现在我们可以在java代码中使用注解来获取filePath的值了: [java] view plain copy @Component("fileUpload") public class FileUploadUtil implements FileUpload { private String filePath; @Value("#{prop.filePath}") //@Value表示去beans.xml文件中找id="prop"的bean,它是通过注解的方式读取properties配置文件的,然后去相应的配置文件中读取key=filePath的对应的value值 public void setFilePath(String filePath) { System.out.println(filePath); this.filePath = filePath; } 注意要有set方法才能被注入进来,注解写在set方法上即可。在setFilePath方法中通过控制台打印filePath是为了在启动tomcat的时候,观察控制台有没有输出来,如果有,说明Spring在启动时,已经将filePath给加载好了,我们看一下控制台的启动信息: 以上就是Spring加载properties配置文件的两种方式。实际上,上面基于xml方式中的PropertyPlaceholderConfigurer类和这里基于注解方式的PropertiesFactoryBean类都是继承PropertiesLoaderSupport,都是用来加载properties配置文件的。 如有错误之处,欢迎留言指正~ _____________________________________________________________________________________________________________________________________________________ -----乐于分享,共同进步! -----更多文章请看:http://blog.csdn.net/eson_15
版权声明:尊重博主原创文章,转载请注明出处哦~http://blog.csdn.net/eson_15/article/details/51322262 目录(?)[+] EasyUI中DataGrid以表格形式展示数据,并提供了丰富的选择、排序、分组和编辑数据的功能支持。DataGrid的设计用于缩短开发时间,并且使开发人员不需要具备特定的知识。它是轻量级的且功能丰富。单元格合并、多列标题、冻结列和页脚只是其中的一小部分功能。 1. 回顾一下第4节内容 在第4节中,我们使用EasyUI搭建好了左侧菜单栏,并且通过点击菜单选项在右边弹出对应的选项卡。这节我们来使用DataGrid把右边的选项卡部分做好。先看一下第4节中最后的aindex.jsp文件(也可参见第4节中的内容): 2. 创建DataGrid控件的几种方式 DataGrid显示数据是json格式的,所以我们首先要把从后台获取到的数据打包成Jason格式,然后传到前台来让DataGrid来显示,这一节我们先不从后台获取数据,先自己准备一个.json文件,里面有ison格式的数据,然后我们来让DataGird显示,先把显示功能做好,再请求后台数据。 我们先从EasyUI的参考文档中看一下DataGrid显示的格式是什么样的,如下图所示: 我们沿着参考文档往下看,我们发现DataGrid空间是通过<table>来创建的,有三种创建方式: 第一种:从现有的表格元素创建DataGrid,在HTML中定义列、行和数据。 第二种:通过<table>标签创建DataGrid控件。在表格内使用<th>标签定义列。 第三种:使用JavaScript去创建DataGrid控件。 我们采取第三种,用js去创建DataGrid控件,首先我们得先准备一个存储了json格式数据的文件,在WebRoot/jQuery-easyui-1.3.5/demo/datagrid/下面有几个json文件,我们选择一个datagrid_data1.json,拷贝到WebRoot目录下,修改一下参数,等会我们要来显示这个json文件里的数据。如下: [plain] view plain copy {"total":10,"rows":[ {"code":"FI-SW-01","productname":"Koi","price":10.00}, {"code":"K9-DL-01","productname":"Dalmation","price":12.00}, {"code":"RP-SN-01","productname":"Rattlesnake","price":12.00}, {"code":"RP-LI-02","productname":"Iguana","price":12.00}, {"code":"FL-DSH-01","productname":"Manx","price":12.00}, {"code":"FL-DSH-01","productname":"Manx","price":12.00}, {"code":"FL-DLH-02","productname":"Persian","price":12.00}, {"code":"FL-DLH-02","productname":"Persian","price":12.00}, {"code":"AV-CB-01","productname":"Amazon Parrot","price":92.00}, {"code":"AV-CB-03","productname":"Amazon Parrot","price":92.00} ]} 我们可以看到,json数据格式是:"key1": value1, "key2":value2。每个value里面又可以是数组,数组中保存新的Jason数据。 有了json文件,我们接下来就可以设计DataGrid控件了,整个DataGrid都是在query.jsp中设计的,因为要显示的内容就是query.jsp中的内容。我们来看看query.jsp页面: [javascript] view plain copy <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <%@ include file="/public/head.jspf" %> <script type="text/javascript"> $(function(){ $('#dg').datagrid({ //请求数据的url地址,后面会改成请求我们自己的url url:'datagrid_data.json', loadMsg:'Loading......', queryParams:{type:''},//参数 //width:300, fitColumns:true,//水平自动展开,如果设置此属性,则不会有水平滚动条,演示冻结列时,该参数不要设置 //显示斑马线 striped:true, //当数据多的时候不换行 nowrap:true, singleSelect:true, //如果为真,只允许单行显示,全显功能失效 //设置分页 pagination:true, rowStyler: function(index,row){ console.info("index" + index + "," + row) if(index % 2 == 0) { return 'background-color:#fff;'; } else { return 'background-color:#ff0;'; } }, //同列属性,但是这些列将会冻结在左侧,大小不会改变,当宽度大于250时,会显示滚动条,但是冻结的列不在滚动条内 frozenColumns:[[ {field:'checkbox',checkbox:true}, {field:'code',title:'编号',width:200} ]], //配置datagrid的列字段 //field:列字段的名称,与json的key捆绑 //title:列标题,是显示给人看的 columns:[[ {field:'productname',title:'类别名称',width:100, //用来格式化当前列的值,返回的是最终的数据 formatter: function(value,row,index){ return "<span title=" + value + ">" + value + "</span>"; } }, {field:'price',title:'价格',width:100, styler: function(value,row,index){ //设置当前单元格的样式,返回的字符串直接交给 style属性 //console.info("val:" + value + ",row:" + row + ",index:" + index) if (value < 20){ return 'color:red;'; } } } ]] }); }); </script> </head> <body> <table id="dg"></table> </body> </html> 3. DataGrid控件的属性 我们可以看到,使用js去创建DataGrid控件的话,只要一个<table>标签即可,主要都是在js中完成。DataGrid的控件很强大,这里我们主要做一下基本的显示,更多其他的功能可以参照EasyUI的开发文档。我们现在针对上面的query.jsp文件做一下分析: 首先DataGrid控件有两种属性:一个是DataGrid属性,还有一个是列属性。顾名思义,DataGrid属性是给整个DataGrid控件添加的属性,而列属性是针对某一列的。每中属性有很多,这里只做了一些基本的常用的属性。 DataGrid属性里最重要的是columns属性,它是一个数组,可以创建多列,见下面的截图: 我们来看下columns属性中有哪些细节: 列属性中,field表示字段名称,对应与json数据的key,然后title是要显示给用户看的标题,见query.jsp文件中,还有其他一些基本属性可以参照EasyUI文档。列属性中比较重要的也比较常用的两个属性是formatter和styler,分别是用来格式化当前列的值和设置单元格样式的,我们主要来看一下这两个属性: 我们具体来分析一下上面query.jsp中的columns属性中,如何使用这两个列属性的: [javascript] view plain copy {field:'productname',title:'类别名称',width:100, //用来格式化当前列的值,返回的是最终的数据 formatter: function(value,row,index){ return "<span title=" + value + ">" + value + "</span>";//设置为鼠标放上去显示value值 } }, {field:'price',title:'价格',width:100, styler: function(value,row,index){ //设置当前单元格的样式,返回的字符串直接交给 style属性 //console.info("val:" + value + ",row:" + row + ",index:" + index) if (value < 20){ //如果value值小于20 return 'color:red;'; //将value值显示为红色 } } } 然后我们再看看DataGrid控件的一些属性: url表示要显示的数据来源,这里设置成datagrid_data.json表示数据来源是这个json文件,放在WebRoot目录下了; loadMsg表示加载数据过程中显示的信息; queryParams表示传给后台的参数,在这里用不到,因为我们目前还没有和后台关联上,只是显示一个json文件,后面会用到; fitColums设置为true后表示水平自动展开,自适应网格的宽度,如此设置,水平方向就不会有滚动条了,也不用设置宽度了; width是宽度,如果数据过长显示不下,水平方向就会出现滚动条; striped设置为true后表示显示斑马线,这是一个显示样式,试一下便知; nowrap设置为true后表示当数据多的时候不换行,否则某一行数据多的时候会换行,会比较难看; pagination设置为true后表示开启分页功能; singleSelect设置为true时,只允许勾选单行,全选功能失效,主要用于最前面一列的复选框; frozenColums是为了设置冻结列,在frozenColums中设置的列,不会改变大小。里面如果设置了{field:'checkbox',checkbox:true},表示这是个复选框列,给用户勾选用的,如果设置了上面的singleSelect,那么只能选择一项,不能全选; rowStyler是设置所有行的样式的,两个参数为行索引和行,上面设置的是偶数行是白色,奇数行是黄色。 等等……还有其他DataGrid控件的属性,可以参考EasyUI的技术文档,在这里就不一一解说了。 4. DataGrid数据显示的效果 好了,完成了query.jsp后,我们重启tomcat,然后进入到后台,点击左侧菜单栏的类别管理,就会在右边出现一个类别管理的选项卡,然后就会显示我们指定的json数据,这个Jason数据是我们自己放在WebRoot目录下的,后面我们将会把json和struts整合,动态的获取从后台传过来的json数据。 (注:到最后我会提供整个项目的源码下载!欢迎大家收藏或关注) 相关阅读:http://blog.csdn.net/column/details/str2hiberspring.html_____________________________________________________________________________________________________________________________________________________ -----乐于分享,共同进步! -----更多文章请看:http://blog.csdn.net/eson_15