首页 文章 精选 留言 我的

精选列表

搜索[基础软件],共10000篇文章
优秀的个人博客,低调大师

java并发(一):线程基础篇

线程的创建很简单,一般是集成Thread类或者实现Runnable接口,我就不细说了。然后,要牢记多线程的3大特性:多线程的三个特性:原子性、可见性、有序性 原子性:是指一个操作是不可中断的。即使是多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。比如,对于一个静态全局变量int i,两个线程同时对它赋值,线程A给他赋值为1,线程B给他赋值为-1。那么不管这两个线程以何种方式。何种步调工作,i的值要么是1,要么是-1.线程A和线程B之间是没有干扰的。这就是原子性的一个特点,不可被中断。 可见性:是指当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道这个修改。显然,对于串行来说,可见性问题是不存在的。 有序性:在并发时,程序的执行可能会出现乱序。给人的直观感觉就是:写在前面的代码,会在后面执行。有序性问题的原因是因为程序在执行时,可能会进行指令重排,重排后的指令与原指令的顺序未必一致。 而共享变量的写操作出错,最重要的是原子性,一般多线程的问题主要抓住这个。 线程安全问题 一般多线程编程都会遇到线程安全的问题,线程安全总体来说是因为多个线程竞争共享资源造成的。比如: public class Test{ private int num = 0; public void add(int value){ this.num = this.num + value; } } 两个线程分别加了2和3到count变量上,两个线程执行结束后count变量的值应该等于5。如果两个线程同时执行这个对象的add()方法,会造成这种现象:线程A先读到num为0,此时恰好线程B也读到num为0,然后A,B同时执行加2和加3的操作,如果A先赋值num为2,然后B又赋值num为3,会造成最后结果为3;或者反过来,造成num为2,使得最后的结果无法预料。 如果线程并没有共享资源,那么多线程执行的代码是安全的,比如:类方法中局部变量或者局部对象引用 public class Test{ public void add(int value){ int num = 0; String a = new String("aa"); num = num + value; } } 还有一种安全的方法,就是每个线程都是执行同一个类不同对象的方法,虽然代码相同,但是不同的对象空间,也不会出现问题,如servlet。 线程状态 线程的状态实现通过 Thread.State 常量类实现,有 6 种线程状态:new(新建)、runnnable(可运行)、blocked(阻塞)、waiting(等待)、time waiting (定时等待)和 terminated(终止)。状态转换图如下: 线程状态流程大致如下: 线程创建后,进入 new 状态 调用 start 或者 run 方法,进入 runnable 状态 JVM 按照线程优先级及时间分片等执行 runnable 状态的线程。开始执行时,进入 running 状态 如果线程执行 sleep、wait、join,或者进入 IO 阻塞等。进入 wait 或者 blocked 状态 线程执行完毕后,线程被线程队列移除。最后为 terminated 状态。 ThreadLocal ThreadLocal与线程同步无关,它虽然提供了一种解决多线程环境下成员变量的问题,但是它并不是解决多线程共享变量的问题。 它的API介绍如下: 该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。 所以ThreadLocal与线程同步机制不同,线程同步机制是多个线程共享同一个变量,而ThreadLocal是为每一个线程创建一个单独的变量副本,故而每个线程都可以独立地改变自己所拥有的变量副本,而不会影响其他线程所对应的副本。可以说ThreadLocal为多线程环境下变量问题提供了另外一种解决思路。 ThreadLocal定义了四个方法: get():返回此线程局部变量的当前线程副本中的值。 initialValue():返回此线程局部变量的当前线程的“初始值”。 remove():移除此线程局部变量当前线程的值。 set(T value):将此线程局部变量的当前线程副本中的值设定为指定值。 除了这四个方法,ThreadLocal内部还有一个静态内部类ThreadLocalMap,该内部类才是实现线程隔离机制的关键,get()、set()、remove()都是基于该内部类操作。ThreadLocalMap提供了一种用键值对方式存储每一个线程的变量副本的方法,key为当前ThreadLocal对象,value则是对应线程的变量副本。 对于ThreadLocal需要注意的有两点: ThreadLocal实例本身是不存储值,它只是提供了一个在当前线程中找到副本值得key。 是ThreadLocal包含在Thread中,而不是Thread包含在ThreadLocal中,有些小伙伴会弄错他们的关系。 下图是Thread、ThreadLocal、ThreadLocalMap的关系 ThreadLocal示例 package com.xushu.multi; public class Test{ private static ThreadLocal<Integer> count = new ThreadLocal<Integer>(){ // 实现initialValue() @Override protected Integer initialValue() { return 0; //这里返回了一个0 } }; public int nextSeq(){ count.set(count.get() + 1); return count.get(); } private static class SeqThread implements Runnable{ private Test te; SeqThread(Test te) { this.te = te; } @Override public void run() { for(int i = 0; i < 3; i++){ System.out.println(Thread.currentThread().getName() + " seqCount :" + te.nextSeq()); } } } public static void main(String[] args) { Test te = new Test(); Thread t1 = new Thread(new SeqThread(te)); Thread t2 = new Thread(new SeqThread(te)); Thread t3 = new Thread(new SeqThread(te)); Thread t4 = new Thread(new SeqThread(te)); t1.start(); t2.start(); t3.start(); t4.start(); } } 可以看出,每个线程都有自己的一个变量副本,所以从根本上避免了读同一个变量。但是,如果在initialValue()方法中,如果return的是一个共有变量,那就是所有的线程都访问同一个变量了,所以ThreadLocal就失效了。这篇文章有解析。 ThreadLocal源码解析 ThreadLocal虽然解决了这个多线程变量的复杂问题,但是它的源码实现却是比较简单的。ThreadLocalMap是实现ThreadLocal的关键,我们先从它入手。 ThreadLocalMap ThreadLocalMap其内部利用Entry来实现key-value的存储,如下: static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } 从上面代码中可以看出Entry的key就是ThreadLocal,而value就是值。同时,Entry也继承WeakReference,所以说Entry所对应key(ThreadLocal实例)的引用为一个弱引用(关于弱引用这里就不多说了,感兴趣的可以关注这篇博客Java 理论与实践: 用弱引用堵住内存泄漏) ThreadLocalMap的源码稍微多了点,我们就看两个最核心的方法getEntry()、set(ThreadLocal> key, Object value)方法。set(ThreadLocal> key, Object value) private void set(ThreadLocal<?> key, Object value) { ThreadLocal.ThreadLocalMap.Entry[] tab = table; int len = tab.length; // 根据 ThreadLocal 的散列值,查找对应元素在数组中的位置 int i = key.threadLocalHashCode & (len-1); // 采用“线性探测法”,寻找合适位置 for (ThreadLocal.ThreadLocalMap.Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); // key 存在,直接覆盖 if (k == key) { e.value = value; return; } // key == null,但是存在值(因为此处的e != null),说明之前的ThreadLocal对象已经被回收了 if (k == null) { // 用新元素替换陈旧的元素 replaceStaleEntry(key, value, i); return; } } // ThreadLocal对应的key实例不存在也没有陈旧元素,new 一个 tab[i] = new ThreadLocal.ThreadLocalMap.Entry(key, value); int sz = ++size; // cleanSomeSlots 清楚陈旧的Entry(key == null) // 如果没有清理陈旧的 Entry 并且数组中的元素大于了阈值,则进行 rehash if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); } 这个set()操作和我们在集合了解的put()方式有点儿不一样,虽然他们都是key-value结构,不同在于他们解决散列冲突的方式不同。集合Map的put()采用的是拉链法,而ThreadLocalMap的set()则是采用开放定址法(具体请参考散列冲突处理系列博客)。掌握了开放地址法该方法就一目了然了。 set()操作除了存储元素外,还有一个很重要的作用,就是replaceStaleEntry()和cleanSomeSlots(),这两个方法可以清除掉key == null 的实例,防止内存泄漏。在set()方法中还有一个变量很重要:threadLocalHashCode,定义如下: private final int threadLocalHashCode = nextHashCode(); 从名字上面我们可以看出threadLocalHashCode应该是ThreadLocal的散列值,定义为final,表示ThreadLocal一旦创建其散列值就已经确定了,生成过程则是调用nextHashCode(): private static AtomicInteger nextHashCode = new AtomicInteger(); private static final int HASH_INCREMENT = 0x61c88647; private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); } nextHashCode表示分配下一个ThreadLocal实例的threadLocalHashCode的值,HASH_INCREMENT则表示分配两个ThradLocal实例的threadLocalHashCode的增量,从nextHashCode就可以看出他们的定义。 private Entry getEntry(ThreadLocal<?> key) { int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; if (e != null && e.get() == key) return e; else return getEntryAfterMiss(key, i, e); } 由于采用了开放定址法,所以当前key的散列值和元素在数组的索引并不是完全对应的,首先取一个探测数(key的散列值),如果所对应的key就是我们所要找的元素,则返回,否则调用getEntryAfterMiss(),如下: private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) { Entry[] tab = table; int len = tab.length; while (e != null) { ThreadLocal<?> k = e.get(); if (k == key) return e; if (k == null) expungeStaleEntry(i); else i = nextIndex(i, len); e = tab[i]; } return null; } 这里有一个重要的地方,当key == null时,调用了expungeStaleEntry()方法,该方法用于处理key == null,有利于GC回收,能够有效地避免内存泄漏。 get() 返回当前线程所对应的线程变量 public T get() { // 获取当前线程 Thread t = Thread.currentThread(); // 获取当前线程的成员变量 threadLocal ThreadLocalMap map = getMap(t); if (map != null) { // 从当前线程的ThreadLocalMap获取相对应的Entry ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") // 获取目标值 T result = (T)e.value; return result; } } return setInitialValue(); } 首先通过当前线程获取所对应的成员变量ThreadLocalMap,然后通过ThreadLocalMap获取当前ThreadLocal的Entry,最后通过所获取的Entry获取目标值result。 getMap()方法可以获取当前线程所对应的ThreadLocalMap,如下: ThreadLocalMap getMap(Thread t) { return t.threadLocals; } set(T value) 设置当前线程的线程局部变量的值。 public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } 获取当前线程所对应的ThreadLocalMap,如果不为空,则调用ThreadLocalMap的set()方法,key就是当前ThreadLocal,如果不存在,则调用createMap()方法新建一个,如下: void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } initialValue() 返回该线程局部变量的初始值。 protected T initialValue() { return null; } 该方法定义为protected级别且返回为null,很明显是要子类实现它的,所以我们在使用ThreadLocal的时候一般都应该覆盖该方法。该方法不能显示调用,只有在第一次调用get()或者set()方法时才会被执行,并且仅执行1次。 remove() 将当前线程局部变量的值删除。 public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); } 该方法的目的是减少内存的占用。当然,我们不需要显示调用该方法,因为一个线程结束后,它所对应的局部变量就会被垃圾回收。 参考文献 1.并发编程网

优秀的个人博客,低调大师

Android基础回顾(二)| 关于Activity

参考书籍:《第一行代码》 第二版 郭霖 开发工具:AndroidStudio 3.2 Stable Channel 如有错漏,请批评指出! Activity 定义:Activity是Android的四大组件之一,它是一种可以包含用户界面的组件,主要用于和用户进行交互。 手动创建一个Activity的三个步骤 step 1:创建Activity 在项目目录的 app\src\main\java\包名 右键->New->Activity->Empty Activity 创建Activity 先取消Generate Layout File选项的勾选,输入Activity Name ,然后点击 Finish 按钮。这样就完成了第一步。 补充说明: 勾选Generate Layout File表示自动为这个Activity创建一个对应的布局文件(默认勾选)。 勾选Launcher Activity表示自动将这个Activity设置为当前项目的主Activity(每个项目只能有一个主Activity,主Activity即启动app时显示的第一个Activity)。 勾选Backwards Compatibility(AppCompat)表示会为项目启用向下兼容的模式(默认勾选)。 Step 2:创建和加载布局文件 在项目目录的 app\src\main\res\layout 右键->New->XML->Layout XML File 创建布局文件 接下来输入Layout File Name,Root Tags 表示布局文件的根布局,这里默认LinearLayout(线性布局),后面会介绍关于布局的内容。 然后在NewActivity.java文件中为这个活动加载我们创建的布局(添加圈出的代码): 加载布局 其实就是将我们创建的Activity和布局文件关联起来,这样我们打开这个Activity 时显示的就是activity_new这个布局文件所编写的内容。 Step3:注册Activity 打开项目目录中 app\src\main\AndroidManifest.xml 文件,在这里为我们创建的Activity进行注册(实际上,在第一步我们创建这个Activity时就已经自动注册了)。 注册Activity 看起来这一步似乎是多余的,这是因为Android Studio自动帮我们注册了Activity。但是,如果我们的Activity不是通过上面的步骤创建的,而是从别的地方复制到项目中来的,那么我们就需要谨记要自己在AndroidManifest文件中为这个活动进行注册,否则就会出错。 这样,我们就完成了手动创建一个Activity的流程。实际上,在第一步我们只要勾选上Generate Layout File 选项,后面两步AndroidStudio都会自动为我们完成。 补充说明: 关于主Activity,在AndroidManifest.xml文件(上图)中我们可以看到,在MainActivity的标签下,有这样几行代码: <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> 实际上,在<activity>标签内部加入<intent-filter>标签,并添加上面的两句声明,就是将这个Activity指定为主Activity。我们可以在创建Activity时勾选上Launcher Activity,Android Studio就会自动为我们配置这个Activity为主Activity,当然,也可以自己在需要指定为主Activity对应的注册标签<activity>中添加上面的代码。 Toast的使用 Toast 是Android系统提供的一种非常好的提醒方式,在程序中可以使用它将一些短小的信息通知给用户,这些信息会在一段时间后自动消失,并且不会占用任何屏幕空间。 Toast的用法非常简单,通过静态方法makeText()创建出一个Toast对象,然后调用show()方法将Toast显示出来就可以了。 @Override public void onClick(View v) { switch (v.getId()){ case R.id.show_toast_button: Toast.makeText(SecondActivity.this, "You clicked button", Toast.LENGTH_SHORT).show(); break; default: break; } } 上面的代码表示通过点击我们指定的Button触发Toast弹出,我们可以看到makeText()方法有三个参数。 第一个参数是Context,即Toast要求的上下文,我们暂且只需要知道Context是一个抽象类,而Activity是它的子类,因此我们指定的SecondActivity.this就是一个Context对象。 第二个参数是我们要显示的提示信息。 第三个参数是Toast显示的时长,有两个内置常量Toast.LENGTH_SHORT和Toast.LENGTH_LONG可以选择。 效果图如下: 效果图.gif Menu的使用 由于手机屏幕空间十分有限,因此Android提供了Menu,用于展示菜单的同时,也不占用过多屏幕空间。 使用方法: step1:创建菜单布局 首先在res目录下新建一个menu文件夹,用于存放Menu布局文件,右键res->New->Directory,输入文件夹名menu;然后右键menu->New-Menu resource file,输入文件名,点击OK完成。 创建Menu布局文件 接下来根据需要创建菜单项,这里我们创建三个菜单项,代码如下: <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/add_item" android:title="@string/add"/> <item android:id="@+id/del_item" android:title="@string/del"/> <item android:id="@+id/modify_item" android:title="@string/modify"/> </menu> <item>标签就是用来创建具体的某个菜单项; android:id标签是用来给这个菜单项指定一个唯一的标识符; android:title标签是给这个菜单项指定一个名称。 step2:为当前活动创建菜单——重写onCreateOptionsMenu()方法 由于我们要给SecondActivity添加Menu,因此我们在SecondActivity.java文件中重写onCreateOptionsMenu()方法。 @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.second, menu); return true; } 通过getMenuInflater()方法获取到MenuInflater对象,再调用它的inflate()方法,给当前Activity创建菜单。inflate()方法接收两个参数: 第一个参数 用来指定第一步创建的布局文件; 第二个参数用于指定我们的菜单项添加到那个Menu对象中,这里自然是onCreateOptionsMenu()方法传入的menu对象,表示当前Activity的Menu。 返回值为true表示允许菜单显示;否则菜单不显示。 step3:为菜单定义响应事件——重写onOptionsItemSelected()方法 在SecondActivity中重写onOptionsItemSelected()方法,通过item.getItenId()方法来判断我们点击的是哪个菜单项,并为其添加逻辑处理,这里我们弹出一个Toast。 @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()){ case R.id.add_item: Toast.makeText(SecondActivity.this, "添加", Toast.LENGTH_SHORT).show(); break; case R.id.del_item: Toast.makeText(SecondActivity.this, "删除", Toast.LENGTH_SHORT).show(); break; case R.id.modify_item: Toast.makeText(SecondActivity.this, "修改", Toast.LENGTH_SHORT).show(); break; default: break; } return true; } 效果图如下: 效果图 使用Intent实现活动间跳转 显式Intent 显式Intent的使用十分简单,直接上代码: @Override public void onClick(View v) { Intent intent = null; switch (v.getId()){ default: break; case R.id.ll_button: intent = new Intent(MainActivity.this,FirstActivity.class); startActivity(intent); break; case R.id.l2_button: intent = new Intent(MainActivity.this,SecondActivity.class); startActivity(intent); break; } } 首先,我们直接new一个Intent对象,构造方法接收两个参数,第一个参数是上下文,因为当前Activity是MainActivity,所以我们传入MainActivity.this,第二个参数指定想要启动的目标活动,这里实际上有两个button,我们分别传入FirstActivity.class和SecondActivity.class,所以点击两个button时会分别跳转到FirstActivity和SecondActivity。下面看效果: 效果图.gif 隐式Intent 相比于显式Intent,隐式Intent则含蓄了很多,它并明确指出我们要启动那个Activity,而是通过指定一系列action和category等信息,然后由系统进行分析,继而启动合适的活动。 在这里我用两个Activity进行对比,通过下面的步骤对要启动的Activity进行配置,并实现隐式启动: step1:为Activity配置action和category 在AndroidManifest文件中,我们给ThirdActivity和NewActivity分别配置<action>标签和<category>标签: <activity android:name=".ThirdActivity" android:label="@string/thirdactivity"> <intent-filter> <action android:name="com.example.aboutactivity.MY_ACTION"/> <category android:name="android.intent.category.DEFAULT"/> </intent-filter> </activity> <activity android:name=".NewActivity" android:label="@string/new_activity"> <intent-filter> <action android:name="com.example.aboutactivity.MY_ACTION"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.MY_CATEGORY"/> </intent-filter> </activity> 我们给ThirdActivity指定了一个<action>标签和一个<category>标签,给NewActivity指定了一个<action>标签和两个<category>标签,下面我们通过Intent其它的构造方法来隐式指定对应的标签。 step2:给Intent添加<action>和<category>标签 @Override public void onClick(View v) { Intent intent = null; switch (v.getId()){ case R.id.l3_button: intent = new Intent("com.example.aboutactivity.MY_ACTION"); startActivity(intent); break; case R.id.l4_button: intent = new Intent("com.example.aboutactivity.MY_ACTION"); intent.addCategory("android.intent.category.MY_CATEGORY"); startActivity(intent); break; default: break; } } 从上面的代码可以看出来,我们给第一个intent添加了“com.example.aboutactivity.MY_ACTION”这个action,给第二个intent添加了“com.example.aboutactivity.MY_ACTION”这个action和“android.intent.category.MY_CATEGORY”这个category,细心的话可能会注意到,两个intent添加的action是相同的,并且第一个intent没有添加category,这是因为“android.intent.category.DEFAULT”这个category是默认category,会自动添加。而且因为我们给第二个intent添加了额外的category标签,因此两个Activity的action和category可以区分开来,这一点是为了说明,我们需要给不同的Activity指定可以区分的<action>标签和<category>标签,否则系统识别不出我们要启动的Activity。下面看效果吧! 隐式Intent.gif 从上面的效果图我们可以看出,由于两个Activity都具有相同的<action>和默认的<category>所以当我们点击ThirdActivity这个button时,系统询问我们要启动哪个Activity,我想这个现象在我们使用手机的时候也遇到过,而由于NewActivity具有自己独有的<category>标签“android.intent.category.MY_CATEGORY”,因此我们点击NewActivity这个bitton时,系统准确的启动了NewActivity,这就是隐式Intent的奇妙之处了! Activity之间的数据传递 向下一个Activity传递数据 前面我们知道Intent可以用来启动Activity,其实通过Intent我们还可以向下一个Activity传递数据,Intent提供了一系列putExtra()方法的重载,我们可以把想要传递的数据暂存在Intent中,启动另一个Activity后,我们只需要再将数据取出来,就可以了。接下来我们结合Toast以及前面创建好的MainActivity和SecondActivity来做一个演示。 step1:在第一个Activity中将数据暂存到Intent中 首先我们就在MainActivity中将数据暂存到Intent中,代码如下: @Override public void onClick(View v) { Intent intent = null; switch (v.getId()){ case R.id.l2_button: intent = new Intent(MainActivity.this,SecondActivity.class); intent.putExtra("Integer",1024); intent.putExtra("String","Hello World!"); startActivity(intent); break; default: break; } } 数据以键值对的形式暂存在Intent中,除了基本数据类型,还可以传递数组、字符串以及实现了serializable和Parcelable接口的对象。 step2:在启动的Activity中将数据取出 要取出数据,我们首先要通过getIntent()方法获取到用于启动SecondActivity的Intent对象,然后调用对应数据类型的get方法将数据取出。代码如下: @Override public void onClick(View v) { switch (v.getId()){ case R.id.show_toast_button: Intent intent = getIntent(); int num = 0; String str = null; if(intent != null){ num = intent.getIntExtra("Integer", 0); str = intent.getStringExtra("String"); } Toast.makeText(SecondActivity.this, str+"\t"+num , Toast.LENGTH_SHORT).show(); break; default: break; } } 我们在SecondActivity里面通过一个button触发Toast将传递过来的数据展示出来,下面看效果: 向下一个Activity传递数据.gif 返回数据给上一个活动 向下一个Activity传递数据十分简单,接下来说一说如何返回数据给上一个Activity。我们可以模拟这样一个场景:在前面我们通过MainActivity启动了SecondActivity,并同时向其传递了一些数据,那么假设我们在SecondActivity中收到数据时,要返回一个接收到数据的确认消息给MainActivity该怎么实现呢?Activity中有一个startActivityForResult()方法,显然这就是我们所需要的。 step1:通过startActivityFprResult()方法启动Activity 我们来修改MainActivity中的代码: @Override public void onClick(View v) { Intent intent = null; switch (v.getId()){ case R.id.l2_button: intent = new Intent(MainActivity.this,SecondActivity.class); intent.putExtra("Integer",1024); intent.putExtra("String","Hello World!"); startActivityForResult(intent,1024); break; default: break; } } startActivityForResult()方法接收两个参数,第一个是intent,第二个是请求码,用于在后面判断数据的来源。我们先往下看。 step2:在启动的Activity中添加返回数据的逻辑 我们首先在SecondActivity中添加一个button,然后为其添加点击事件。在这里我们要构建一个Intent用于传递数据,将要传递的数据存放到Intent中,然后调用setResult()方法。代码如下: @Override public void onClick(View v) { switch (v.getId()){ case R.id.data_back_button: Intent intent = new Intent(); intent.putExtra("Confirm","收到数据"); setResult(RESULT_OK, intent); finish(); break; default: break; } } 上面的逻辑就是将要返回的数据存放在Intent中,然后调用setResult()方法将数据返回给上一个Activity,setResult()方法接收两个参数,第一个参数用于向上一个Activity返回处理结果,一般只用RESULT_OK和RESULT_CANCELED这两个值。然后调用finish()方法将当前Activity结束,返回上一个Activity。当然,触发条件是点击刚才定义的Button。 step3:获取返回的数据 既然数据已经返回到MainActivity了,并且我们现在也回到MainActivity里面了,那么我们怎么知道返回的数据呢?因为我们是通过startActivityForResult()方法启动的SecondActivity,所以当我们再次回到MainActivity时,会回调onActivityResult()方法。也就是说,通过重写onActivityResult()方法,我们可以对返回的数据进行处理。下面看代码: @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { switch (requestCode){ case 1024: if (resultCode == RESULT_OK && data != null ){ String str = data.getStringExtra("Confirm"); Toast.makeText(MainActivity.this, str, Toast.LENGTH_SHORT).show(); } break; default: break; } } onActivityResult()方法的三个参数分别是我们前面传入的请求码、返回数据时传入的处理结果和携带返回数据的Intent对象。可以看到,前面我们提到的请求码和结果码都派了用场,下面看效果: 向上一个Activity返回数据.gif Activity实际场景应用 判断当前是在哪个Activity 当我们在看别人的项目时,经常会遇上这么一个问题:这个app这么多页面,究竟每一个对应哪个Activity呢?其实想要解决这个问题并不难,下面我们来学习一个小技巧。 step1:创建一个BaseActivity类 为什么是BaseActivity类呢?因为这个BaseActivity并不是一个要显示出来的Activity,它只是作为我们Activity的基类,提供一些方法,从而实现我们的目的,并且让它继承自AppCompatActivity。右键包名->New->JavaClass 创建BaseActivity 然后重写onCreate()方法,这里为了演示效果,我们用Toast弹出当前Activity实例的类名。(在实际项目中我们用Log.d()打印出来就行了)代码如下: @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); Toast.makeText(BaseActivity.this, getClass().getSimpleName(), Toast.LENGTH_SHORT).show(); } step2:让项目中所有的Activity都继承自BaseActivity 由于我们的BaseActivity是继承自AppCompatActivity的,因此这个修改不会带来什么影响。我们直接看运行效果吧! 知晓当前Activity.gif 随时退出应用 当我们在写一个由很多Activity组成的项目时,很可能要面临一个问题,从ActivityA打开ActivityB,然后从ActivityB打开ActivityC,这时如果我们按Back键,会依次回退到ActivityB->ActivityA->主界面,按Home键也只是返回桌面,并没有关闭应用。但是如果我们在ActivityC需要直接关闭掉这个应用呢?其实也不难,我们只需要创建一个ActivityCollector类作为Activity管理器,这个问题就迎刃而解啦! step1:创建ActivityCollector类,并添加相关方法实现 public class ActivityCollector { private static List<Activity> activities = new ArrayList<>(); public static void addActivity(Activity activity){ activities.add(activity); } public static void removeActivity(Activity activity){ activities.remove(activity); } public static void finishAllActivity(){ for (Activity activity : activities){ if (!activity.isFinishing()){ activity.finish(); } } activities.clear(); } } step2:重写BaseActivity中的相关方法 既然创建了ActivityCollector类,那么我们每创建一个Activity,就要添加到我们的Activity管理器中,每销毁一个Activity,也要从Activity管理器中将之移除。下面是代码: public class BaseActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); Toast.makeText(BaseActivity.this, getClass().getSimpleName(), Toast.LENGTH_SHORT).show(); ActivityCollector.addActivity(this); } @Override protected void onDestroy() { super.onDestroy(); ActivityCollector.removeActivity(this); } } step3:在需要直接退出程序的Activity中添加相关逻辑 例如,我们要在某个Activity中通过点击某个button来关闭应用,那么我们只需要在这个Button的点击事件中调用ActivityCollector.finishAllActivity()就可以啦!关于这个就不写相关示例和演示了,自己动手,印象更深刻! 关于Activity的生命周期和启动模式我单独写了两篇博客:Android笔记(一) | Activity的生命周期Android笔记(二) | Activity的启动模式

优秀的个人博客,低调大师

Go语言基础语法(package)-5

章节 package 作用及定义 1.package 作用及定义 package 是 最基本的分发单位 和 工程管理中依赖关系 的体现 每个GO语言源代码文件开头都拥有一个package声明,表示源码文件所属的代码包 要生成GO语言可执行程序,必须要有main的package包,且必须在该包下有main()函数 同一个路径下只能存在一个package,一个package可以拆成多个源文件组成 2.实践-验证上述package作用及定义 2.1 必须要有 package main 包 且 必须在该包下有main函数 package 包名不为main package main1 import "fmt" func main1() { fmt.Println("测试") } 运行结果如下图所示: package name->main1 2.2 package main 包中没有main函数-func main1() {...} package main1 import "fmt" func main1() { fmt.Println("测试") } 运行结果如下所示: func name->main1 所以package main 下面需要有main函数,否则生成不了可执行文件 2.3 package 名不为main //package 包名与文件夹名要求保持一致 package learn02 import "fmt" func test02() { fmt.Println("i am test") } package learn02 与 项目文件夹结构目录保持一致 package learn02 与 项目文件夹结构目录保持一致 test02.go 源代码编译情况如下所示: 源代码文件可正常编译 test02.go 源文件可正常编译 2.4 同一个路径下只能存在一个package,一个package可以拆成多个源文件组成 目录结构下新建 main02.go 同一个路径下只存在一个package 运行情况 go build main02.go main02.go 可正常编译、但未生成可执行文件 2.5 一个package可以包含多个源文件 项目目录结构如下所示: 一个package可以包含多个源文件

优秀的个人博客,低调大师

Python3基础——装饰器

1、Python是一种面向对象的编程语言,在Python中所有的都可以是Python的对象。即可以在函数内创建函数——函数也可以是变量!(亦可称之为:内嵌函数) 2、如果内部函数引用了外部函数定义的对象(即某函数调用的是该函数以外定义的对象,但该对象不是全局变量),那么此时内部函数叫做:闭包函数,所引用的外部变量叫:自由变量。 什么是闭包函数?——函数内部定义函数;并且引用了外部变量但不是全局变量。 3、Python装饰器 Python装饰器本质上是一个函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外的功能,装饰器返回的值也是一个函数对象。 3.1 变量与引用示例: 结果: 3.2 装饰器示例一: 运行结果: 3.3 装饰器示例二: 步骤1:执行代码第一行 定义变量为author 的函数srartEnd(即函数srartEnd就是装饰器) 步骤2:获取变量 author 的值,即author的值为 Jsh 步骤3:获取到author的值后,执行函数srartEnd 步骤4:执行函数srartEnd的内容,即定义变量为 fun 的函数a 步骤5:返回函数a的值 步骤6:@符号是装饰器的语法糖,在定义函数的时候使用,避免再一次被赋值 即:变量 author的值依旧为 Jsh 步骤7:定义变量为name的函数b( 即闭包函数 b ) 步骤8:返回函数b的值 步骤9:@符号是装饰器的语法糖,在定义函数的时候使用,避免再一次被赋值 即:变量 author的值依旧为 Jsh 步骤10:申明函数 hello 的变量name的值为 Xiaohai 步骤11:打印 This author is Jsh 步骤12:打印 Start 步骤13:执行变量为name的函数fun 此函数的返回值为函数a 的变量 步骤14、15:打印函数hello的结果 步骤16:打印 End 运行结果:

优秀的个人博客,低调大师

1-AVI--Fragment基础使用

零、前言 [1].Fragment静态使用 [2].Fragment动态使用 一、Fragment静态使用 静态fragment.jpg 1.蓝色布局:blue.xml <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/colorPrimary" android:orientation="vertical" > <TextView android:id="@+id/blue_tv" android:layout_centerInParent="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:text="蓝色Fragment" /> </RelativeLayout> 2.黄色布局:yellow.xml <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/yellow" android:orientation="vertical" > <TextView android:id="@+id/yellow_tv" android:layout_centerInParent="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:text="黄色Fragment" /> </RelativeLayout> 3.黄色Fragment:YellowFragment.java /** * 作者:张风捷特烈<br/> * 时间:2018/8/28 0028:13:07<br/> * 邮箱:1981462002@qq.com<br/> * 说明:黄色Fragment */ public class YellowFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.yellow, container, false); } } 4.绿色Fragment:BlueFragment.java /** * 作者:张风捷特烈<br/> * 时间:2018/8/28 0028:13:07<br/> * 邮箱:1981462002@qq.com<br/> * 说明:绿色Fragment */ public class BlueFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_bottom, container, false); } } 5.主布局:layout/activity_static.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout 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" android:orientation="vertical" tools:context=".activity.ActFragmentActivity"> <fragment android:id="@+id/yellow_fragment1" android:name="com.toly1994.avi_fragment.staticFg.YellowFragment" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"/> <View android:background="@color/gray_8f" android:layout_width="match_parent" android:layout_height="1dp"/> <fragment android:id="@+id/yellow_fragment2" android:name="com.toly1994.avi_fragment.staticFg.YellowFragment" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"/> <fragment android:id="@+id/blue_fragment" android:name="com.toly1994.avi_fragment.staticFg.BlueFragment" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="4"/> </LinearLayout> 6.使用:StaticFragmentActivity.java public class StaticFragmentActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_static); } } 二、动态使用 public class ActFragmentActivity extends FragmentActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_acti); initFragment(); } /** * 动态加载Fragment */ private void initFragment() { FragmentManager fm = getFragmentManager();//1.获取FragmentManager FragmentTransaction ft = fm.beginTransaction();//2.fm开启事务 //3.动态添加 (控件id,fragment对象) ft.add(R.id.fl_title, new YellowFragment()); ft.add(R.id.fl_content, new BlueFragment()); ft.commit();//4.提交事务 } } 动态使用fragment.png 后记、 1.声明: [1]本文由张风捷特烈原创,转载请注明 [2]欢迎广大编程爱好者共同交流 [3]个人能力有限,如有不正之处欢迎大家批评指证,必定虚心改正 [4]你的喜欢与支持将是我最大的动力 2.连接传送门: 更多安卓技术欢迎访问:安卓技术栈 我的github地址:欢迎star 简书首发,腾讯云+社区同步更新张风捷特烈个人网站,编程笔记请访问:http://www.toly1994.com 3.联系我 QQ:1981462002 邮箱:1981462002@qq.com 微信:zdl1994328 4.欢迎关注我的微信公众号,最新精彩文章,及时送达: 公众号.jpg

优秀的个人博客,低调大师

Docker基础操作手册

Docker操作总结 @Date 2018.08.24 基本操作 登录远程镜像仓库 docker login --username=hua registry.cn-beijing.aliyuncs.com 镜像操作 根据指定路径的Dockerfile打镜像, 指定本地Tag名 docker build -t tag -f docker-config/Dockerfile . 修改本地tag改为远程专有地址仓库tag docker tag local_tag registry.cn-beijing.aliyuncs.com/[命名空间]/[仓库名称]:[版本] PUSH本地镜像到远程仓库 docker push registry.cn-beijing.aliyuncs.com/[命名空间]/[仓库名称]:[版本] 删除无容器运行的镜像 docker rmi [imageID] 利用全拼删除有重复镜像ID的镜像 docker rmi [REPOSITORY]:TAG 删除所有镜像 docker rmi $(docker images -q) 容器操作 启动容器,支持传入参数 docker run -it -d -p 7001:7001 -e spring.profiles.active="dev" [REPOSITORY]:[TAG] 停止容器 docker stop [容器ID] 停止所有容器 docker stop $(docker ps -a -q) 删除已停止的容器 docker container rm [容器ID] 删除所有容器 docker rm $(docker ps -a -q) 使用docker-compose编排模板 # 使用目录下默认的docker-compose.yaml创建容器 docker-compose up -d # 使用指定的yaml文件创建容器 docker-compose -f xxx.yaml up -d # 指定容器ID创建多个scale docker-compose -f xxx.yaml scale id=数量

优秀的个人博客,低调大师

JavaScript 基础排序的实现(二)

继上一篇O(n^2)的排序算法后,这一篇主要记录O(n*logn)的排序算法 1.快排(快速排序) 这一算法的核心思想为,先随机选一个数作为标兵或者说是标记(这个数一般来说选择该无序数组的中间那个元素;此处笔者选取第一个实现算法,当选取完毕后以此标兵为参照将比这个数大的放到他的右边,比他小的放到左边.这样一趟排序过后就能让这个标记左边的数比他小,右边的数都比他大.下一趟排序则分别选取他的左半边数组和右半边数组重复之前的操作(此操作一般由递归实现)当传入的区间只有一个元素的时候那么开始返回不再继续调用函数自身(即为递归的终止条件)因为只有一个数字是必定有序的 递归实现代码如下: //快速排序(递归) function QuickSort(arr,start = 0,end = arr.length-1){ if(start<end){//递归结束条件 let [key,l,r] = [parseInt((start+end)/2),start,end];//key为标记的值,l和r 分别表示左边的下标和右边的下标 while(l<r){//当左右下标相遇即完成一趟排序,停止循环 while(l<r && arr[r] >= key){//从后往前找,如果对应下标的数比标记大则不做操作,下标继续前移 r--; }//循环完成此时arr[r] //而arr[l]的的值已经被key保存所以下一步则应该找出一个比key大却在key的左边的数放入arr[r]的位置 while(l<r && arr[l] <= key){ l++; } [arr[r],arr[l]] = [arr[l],arr[r]]; //这样一次循环就可以让一个在key左边比key大的数放到key的右边,比key小的放到左边 } QuickSort(arr,start,l-1);//递归调用 -1 是为了避免区间重复 QuickSort(arr,l+1,end); } } 下图为将十万倒序数组变为正序的耗费时间: 下面列出非递归版本的快排: function QuickSort2(arr,start = 0,end = arr.length-1){ let mid = [start,end];//建立一个数组用于存放需要快排的区间的参数,这样循环调用即可 if(start<end){ while(mid.length){//当参数数组中没有参数则停止循环 let [l,r] = [mid.shift(),mid.shift()];//取出数组中最前面的两个数,并对此区间上的数进行快排 let [low,height] = [l,r]; let key = arr[parseInt((l+r)/2)];//选取标兵 while(l<r){ while(l<r && arr[r] >= key){ r--; } arr[l] = arr[r]; while(l<r && arr[l] <= key){ l++; } arr[r] = arr[l]; arr[l] = key; } //当两个参数不相等,即该区间不止一个数时,则将下一个区间的参数存入参数数组 if(low<l) mid.push(low,l); if(l+1<height) mid.push(l+1,height); } } } 对十万逆序数组排序耗时如下: 由于非递归版本数组方法调用较多,故此花费时间略久于递归版本 2.希尔排序 希尔排序和快排一样都是不稳定排序,快速排序的不稳定是因为,标兵的选取并不能直接选取到刚好是区间中中间大小的数,所以当每个区间的第一个数刚好都是该区间最小的数,那么达到快排的最坏情况(以区间的第一个数为标兵为例).而希尔排序其核心是插入排序所以当数组刚好为逆序时其效率最低,此时也就是希尔排序的最坏情况但与快排不同的是最坏情况的希尔排序速率并不会下降明显. 希尔排序算法的和心在于,利用了当待排序数组基本有序的时候插入排序效率极高的原理,即选取不同的步长对其进行插排,当步长为一时则与普通的插排没有区别,而效率的提升在于经过前期数次的插排使数组基本有序 其代码如下: function ShellSort(arr) { let steep = parseInt(arr.length / 2);//计算步长 let length = arr.length; let tarr = [];//用于存放按步长分出的数组的临时数组 while (steep) { //只有步长大于1才分组 否则直接插排 if ( steep > 1) { for (let i = 0; i < steep; i++) { //此循环将数组分组 for (let j = i; j < length; j = j + steep) { tarr.push(arr[j]); } InsertionSort(tarr);//将分好组的数组进行插排 for (let j = i; j < length; j = j + steep) { arr[j] = tarr.shift(); } //排好序的数组映射回原数组 tarr.length = 0;//清空临时数组 } }else InsertionSort(arr); steep = parseInt(steep / 2); } } 普通插排耗时如下: 希尔排序耗时如下: 3.归并排序 与快排和希尔排序不同的是归并排序是稳定排序 其核心思想在于当数组只有一个数时肯定是有序的,所以现将数组拆分为一个一个的数,然后在合并的过程中对齐排序,其弊端在于需要占用一个与原数组等长的临时数组空间来存放变量 递归版代码如下: //归并排序(递归) function Merge(arr,start,mid,end,temp) {//此函数用于合并数组 let [l,r] = [start,mid+1];//l标示左边需合并数组的初始下标,r同理标示右边数组的初始下标 while(l<=mid && r <= end){ if(arr[l]<=arr[r]) temp.push(arr[l++]); else temp.push(arr[r++]); } while(l<=mid){ temp.push(arr[l++]); } while(r<=end){ temp.push(arr[r++]); } while(start<=end){//将排好的临时数组中的数据复制回原数组 arr[start++] = temp.shift(); } } function RecursiveSort(arr,start = 0,end = arr.length-1,temp = []){ if(start < end){ let mid = parseInt((start+end)/2);//将数组分为两半 RecursiveSort(arr,start,mid,temp); RecursiveSort(arr,mid+1,end,temp); Merge(arr,start,mid,end,temp);//递归的回调途中进行数组的合并 } } 递归版耗时: 非递归代码: function RecursiveSort2(arr,start = 0,end = arr.length-1){ let mid = [start,end]; let pra = []; while(mid.length){//获取参数列表 let [l,r] = [mid.shift(),mid.shift()]; pra.push(l,r); let m = parseInt((l+r)/2); if(m>l) mid.push(l,m); if(r>m+1) mid.push(m+1,r); } while(pra.length){ let [r,l] = [pra.pop(),pra.pop()]; let m = parseInt((l+r)/2); Merge(arr,l,m,r,[]); //此函数见上文 } } 非递归耗时如下: 总结: 快排在数据量大时的优势较为明显,希尔排序适用于中量数据,而当空间要求不限时归并也不失为一种选择

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

用户登录
用户注册