这篇文章给大家介绍Android中如何实现异步消息机制,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。Android中的异步消息机制分为四个部分:Message、Handler、MessageQueue和Looper。其中,Message是线程之间传递的消息,其what、arg1、arg2字段可以携带整型数据,obj字段可以携带一个Object对象。Handler是处理者,主要用于发送消息和处理消息。发送消息的方法是sendMessage;处理消息的方法是handleMessage(),Message字段携带的信息在该方法中用作判别。MessageQueue是消息队列,存放所有Handler发送的消息。Looper是消息队列的“管家”,将消息从消息队列中一条条取出,并分派到Handler的handleMessage()方法中。异步消息处理的流程为:①首先,需要在主线程中创建一个Handler对象,并重写handleMessage()方法。②当子线程处理完耗时操作,需要将处理结果反馈到UI中时,先创建一个Message对象,并让其what字段携带一个int值,然后通过Handler对象发送出去。③之后该消息会被添加到MessageQueue中等待被处理,而Looper会一直尝试从MessageQueue中取出待处理消息,最后分发回Handler对象中的handleMessage()方法中。由于Handler对象是在主线程中创建的,所以可以在handleMessage()方法中安心地进行UI操作。通过一个例子来验证一下:活动MainActivity中有一个按钮和一个TextView。TextView初始化显示“Hello World!”,之后点击按钮,进行耗时操作;耗时操作结束后,TextView显示“Nice to meet you”。根据以上的分析,我无比自然地写出了以下代码:package com.shaking.androidthreadtest;import android.os.Handler;import android.os.Message;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.TextView;public class MainActivity extends AppCompatActivity implements View.OnClickListener{ private static final int UPDATE_TEXT=1; private String data; private TextView textView; private Handler handler=new Handler(){ @Override public void handleMessage(Message msg) { switch (msg.what){ case UPDATE_TEXT: textView.setText(data); } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main_layout); Button button=findViewById(R.id.button); textView=findViewById(R.id.text_view); button.setOnClickListener(this); } @Override public void onClick(View view) { new Thread(new Runnable() { @Override public void run() { //假设此处进行了耗时操作,最终得到结果字符串data data="Nice to meet you"; Message message=new Message(); message.what=UPDATE_TEXT; handler.sendMessage(message); } }).start(); } }首先,这么写,是肯定没有错误的!程序也可以正常运行。但是IDE给出了警告:“This Handler class should be static or leaks might occur”。这个警告的意思是:我们使用Handler这个类时,应该将其声明为静态,否则会导致内存泄露。那么,为什么会发生内存泄露呢?原因是:第一:当我们通过Handler对象的sendMessage()方法发送一个Message对象时,该Message对象持有对该Handler对象的引用(正是依靠这个引用,Looper在消息队列中取出该Message对象后,才能准确地将该Message对象分派回该Handler对象!)。第二,我们在主线程中创建Handler对象时,为了重写其handleMessage()方法,使用了匿名内部类的方式来创建该Handler对象。而匿名内部类和非静态内部类都是隐性地持有一个对外部类的引用!所以,该Handler对象持有外部类MainActivity的引用。以上两个结合在一起,问题就来了:Message对象持有Handler对象引用,Handler对象持有MainActivity的引用。所以,MainActivity该活动永远无法被内存回收,直到Message被回收为止!如果Message对象在子线程中被发送至消息队列,然后一直没有被处理,该活动所在的主线程也会一直挂着,而不会被内存回收。所以,会导致内存泄露。知道了原因,那么解决方法是什么?其实之前的警告,已经给出了解决方案。那就是通过静态内部类的方式创建Handler对象,因为静态内部类不会持有对外部类对象的引用。这时候,我又自然而然地创建一个静态内部类,继承自Handler类,然后重写其handleMessage方法。private static class MyHandler extends Handler{ @Override public void handleMessage(Message msg) { } }但是,此处又出现了一个问题!如果我不持有对外部类的引用了,那么我怎么使用外部类的方法和对象?毕竟我是要在handleMessage()方法中进行UI操作的。对于这种使用了静态内部类来避免内存泄露,同时又需要调用外部类的方法的情况:可以使用弱引用!即我们在该内部类中声明一个对外部类对象的弱引用。这样即可以调用外部类的方法,又不会导致内存泄露。具体修改后的代码,如下:package com.shaking.androidthreadtest;import android.os.Handler;import android.os.Message;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.TextView;import java.lang.ref.WeakReference;public class MainActivity extends AppCompatActivity implements View.OnClickListener{ private static final int UPDATE_TEXT=1; private String data; private TextView textView; private static class MyHandler extends Handler{ //使该内部类持有对外部类的弱引用 private WeakReference<MainActivity> weakReference; //构造器中完成弱引用初始化 MyHandler(MainActivity activity){ weakReference=new WeakReference<>(activity); } @Override public void handleMessage(Message msg) { //通过弱引用的get()方法获得外部类对象的引用 MainActivity activity=weakReference.get(); activity.textView.setText(activity.data); } } //创建Handler对象 private MyHandler handler=new MyHandler(this); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main_layout); Button button=findViewById(R.id.button); textView=findViewById(R.id.text_view); button.setOnClickListener(this); } @Override public void onClick(View view) { new Thread(new Runnable() { @Override public void run() { //假设此处进行了耗时操作,最终得到结果字符串data data="Nice to meet you"; Message message=new Message(); message.what=UPDATE_TEXT; handler.sendMessage(message); } }).start(); } }关于Android中如何实现异步消息机制就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。...
这期内容当中小编将会给大家带来有关Windows 10中怎么使用Android应用程序,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。Bluestacks 4Bluestacks 4是目前适用于Win10的***的Android应用程序播放器。该程序是在考虑到游戏的基础上构建的,它承诺的性能比标准的Android智能手机高出六倍。由于它主要是一个应用程序播放器,您将无法访问核心Android体验,但您应该能够尝试许多应用程序和游戏。它集成了对Google Play的支持,以便安全地下载您想要的内容而不会有任何风险。如何安装程序:--转到该计划的官方网站。--单击下载按钮并将安装程序保存在PC上。--转到“下载”文件夹并找到安装程序。--双击它以开始安装过程。--单击立即安装按钮--等待安装完成。--启动该程序有两种下载游戏的方法。 您可以直接在应用内下载它们并立即开始播放,或使用Google Play帐户登录,以便从Play商店下载应用。 您还可以下载Play游戏应用程序以获得成就并播放某些多人游戏而不会出现问题。上述就是小编为大家分享的Windows 10中怎么使用Android应用程序了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注辰讯云资讯频道。...
本篇文章给大家分享的是有关VB.NET中怎么删除文件夹,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。VB.NET删除文件夹的操作是采用递归算法删除带有多级子目录的目录Option Explicit Private Sub Command1_Click() Dim strPathName As String strPathName = "" strPathName = InputBox("请输入需要删除的文件夹名称∶", "删除文件夹") If strPathName = "" Then Exit Sub On Error GoTo ErrorHandle SetAttr strPathName, vbNormal '此行主要是为了检查文件夹名称的有效性 RecurseTree strPathName Label1.Caption = "文件夹" & strPathName & "已经删除!" Exit Sub ErrorHandle: MsgBox "无效的文件夹名称:" & strPathName End Sub Sub RecurseTree(CurrPath As String) Dim sFileName As String Dim newPath As String Dim sPath As String Static oldPath As String sPath = CurrPath & "\" sFileName = Dir(sPath, 31) '31的含义∶31=vbNormal+vbReadOnly+vbHidden+vbSystem+vbVolume+vbDirectory Do While sFileName <> "" If sFileName <> "." And sFileName <> ".." Then If GetAttr(sPath & sFileName) And vbDirectory Then '如果是目录和文件夹 newPath = sPath & sFileName RecurseTree newPath sFileName = Dir(sPath, 31) Else SetAttr sPath & sFileName, vbNormal Kill (sPath & sFileName) Label1.Caption = sPath & sFileName '显示删除过程 sFileName = Dir End If Else sFileName = Dir End If DoEvents Loop SetAttr CurrPath, vbNormal RmDir CurrPath Label1.Caption = CurrPath End Sub 以上就是VB.NET中怎么删除文件夹,小编相信有部分知识点可能是我们日常工作会见到或用到的。希望你能通过这篇文章学到更多知识。更多详情敬请关注辰讯云资讯频道。...
这篇文章将为大家详细讲解有关.NET中怎么删除空白字符串,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。检查空白字符检查空白字符很简单。所有你需要的代码就是:char wp = ' '; char a = 'a'; Assert.True(char.IsWhiteSpace(wp)); Assert.False(char.IsWhiteSpace(a));但是,当我实现手动优化删除方法时,我意识到这并不像预期得那么好。一些源代码在微软的参考源代码库的char.cs挖掘找到:public static bool IsWhiteSpace(char c) { if (IsLatin1(c)) { return (IsWhiteSpaceLatin1(c)); } return CharUnicodeInfo.IsWhiteSpace(c); } 然后CharUnicodeInfo.IsWhiteSpace成了: internal static bool IsWhiteSpace(char c) { UnicodeCategory uc = GetUnicodeCategory(c); // In Unicode 3.0, U+2028 is the only character which is under the category "LineSeparator". // And U+2029 is th eonly character which is under the category "ParagraphSeparator". switch (uc) { case (UnicodeCategory.SpaceSeparator): case (UnicodeCategory.LineSeparator): case (UnicodeCategory.ParagraphSeparator): return (true); } return (false); }GetUnicodeCategory()方法调用InternalGetUnicodeCategory()方法,而且实际上相当快,但现在我们依次已经有了4个方法调用!以下这段代码是由一位评论者提供的,可用于快速实现定制版本和JIT默认内联:// whitespace detection method: very fast, a lot faster than Char.IsWhiteSpace [MethodImpl(MethodImplOptions.AggressiveInlining)] // if it's not inlined then it will be slow!!! public static bool isWhiteSpace(char ch) { // this is surprisingly faster than the equivalent if statement switch (ch) { case '\u0009': case '\u000A': case '\u000B': case '\u000C': case '\u000D': case '\u0020': case '\u0085': case '\u00A0': case '\u1680': case '\u2000': case '\u2001': case '\u2002': case '\u2003': case '\u2004': case '\u2005': case '\u2006': case '\u2007': case '\u2008': case '\u2009': case '\u200A': case '\u2028': case '\u2029': case '\u202F': case '\u205F': case '\u3000': return true; default: return false; } }删除字符串的不同方法我用各种不同的方法来实现删除字符串中的所有空白。分离合并法这是我一直在用的一个非常简单的方法。根据空格字符分离字符串,但不包括空项,然后将产生的碎片重新合并到一起。这方法听上去有点傻乎乎的,而事实上,乍一看,很像是一个非常浪费的解决方式:public static string TrimAllWithSplitAndJoin(string str) { return string.Concat(str.Split(default(string[]), StringSplitOptions.RemoveEmptyEntries)); }LINQ这是优雅地声明式地实现这个过程的方法:public static string TrimAllWithLinq(string str) { return new string(str.Where(c => !isWhiteSpace(c)).ToArray()); }正则表达式正则表达式是非常强大的力量,任何程序员都应该意识到这一点。static Regex whitespace = new Regex(@"\s+", RegexOptions.Compiled); public static string TrimAllWithRegex(string str) { return whitespace.Replace(str, ""); }字符数组原地转换法该方法将输入的字符串转换成字符数组,然后原地扫描字符串去除空白字符(不创建中间缓冲区或字符串)。***,经过“删减”的数组会产生新的字符串。public static string TrimAllWithInplaceCharArray(string str) { var len = str.Length; var src = str.ToCharArray(); int dstIdx = 0; for (int i = 0; i < len; i++) { var ch = src[i]; if (!isWhiteSpace(ch)) src[dstIdx++] = ch; } return new string(src, 0, dstIdx); }字符数组复制法这种方法类似于字符数组原地转换法,但它使用Array.Copy复制连续非空白“字符串”的同时跳过空格。***,它将创建一个适当尺寸的字符数组,并用相同的方式返回一个新的字符串。public static string TrimAllWithCharArrayCopy(string str) { var len = str.Length; var src = str.ToCharArray(); int srcIdx = 0, dstIdx = 0, count = 0; for (int i = 0; i < len; i++) { if (isWhiteSpace(src[i])) { count = i - srcIdx; Array.Copy(src, srcIdx, src, dstIdx, count); srcIdx += count + 1; dstIdx += count; len--; } } if (dstIdx < len) Array.Copy(src, srcIdx, src, dstIdx, len - dstIdx); return new string(src, 0, len); }循环交换法用代码实现循环,并使用StringBuilder类,通过依靠StringBuilder的内在优化来创建新的字符串。为了避免任何其他因素对本实施产生干扰,不调用其他的方法,并且通过缓存到本地变量避免访问类成员。***通过设置StringBuilder.Length将缓冲区调整到合适大小。// Code suggested by http://www.codeproject.com/Members/TheBasketcaseSoftware public static string TrimAllWithLexerLoop(string s) { int length = s.Length; var buffer = new StringBuilder(s); var dstIdx = 0; for (int index = 0; index < s.Length; index++) { char ch = s[index]; switch (ch) { case '\u0020': case '\u00A0': case '\u1680': case '\u2000': case '\u2001': case '\u2002': case '\u2003': case '\u2004': case '\u2005': case '\u2006': case '\u2007': case '\u2008': case '\u2009': case '\u200A': case '\u202F': case '\u205F': case '\u3000': case '\u2028': case '\u2029': case '\u0009': case '\u000A': case '\u000B': case '\u000C': case '\u000D': case '\u0085': length--; continue; default: break; } buffer[dstIdx++] = ch; } buffer.Length = length; return buffer.ToString();; }循环字符法这种方法几乎和前面的循环交换法相同,不过它采用if语句来调用isWhiteSpace(),而不是乱七八糟的switch伎俩 :)。public static string TrimAllWithLexerLoopCharIsWhitespce(string s) { int length = s.Length; var buffer = new StringBuilder(s); var dstIdx = 0; for (int index = 0; index < s.Length; index++) { char currentchar = s[index]; if (isWhiteSpace(currentchar)) length--; else buffer[dstIdx++] = currentchar; } buffer.Length = length; return buffer.ToString();; }原地改变字符串法(不安全)这种方法使用不安全的字符指针和指针运算来原地改变字符串。我不推荐这个方法,因为它打破了.NET框架在生产中的基本约定:字符串是不可变的。public static unsafe string TrimAllWithStringInplace(string str) { fixed (char* pfixed = str) { char* dst = pfixed; for (char* p = pfixed; *p != 0; p++) if (!isWhiteSpace(*p)) *dst++ = *p; /*// reset the string size * ONLY IT DIDN'T WORK! A GARBAGE COLLECTION ACCESS VIOLATION OCCURRED AFTER USING IT * SO I HAD TO RESORT TO RETURN A NEW STRING INSTEAD, WITH ONLY THE PERTINENT BYTES * IT WOULD BE A LOT FASTER IF IT DID WORK THOUGH... Int32 len = (Int32)(dst - pfixed); Int32* pi = (Int32*)pfixed; pi[-1] = len; pfixed[len] = '\0';*/ return new string(pfixed, 0, (int)(dst - pfixed)); } }原地改变字符串法V2(不安全)这种方法几乎和前面那个相同,不过此处使用类似数组的指针访问。我很好奇,不知道这两种哪种存储访问会更快。public static unsafe string TrimAllWithStringInplaceV2(string str) { var len = str.Length; fixed (char* pStr = str) { int dstIdx = 0; for (int i = 0; i < len; i++) if (!isWhiteSpace(pStr[i])) pStr[dstIdx++] = pStr[i]; // since the unsafe string length reset didn't work we need to resort to this slower compromise return new string(pStr, 0, dstIdx); } }String.Replace(“”,“”)这种实现方法很天真,由于它只替换空格字符,所以它不使用空白的正确定义,因此会遗漏很多其他的空格字符。虽然它应该算是本文中最快的方法,但功能不及其他。但如果你只需要去掉真正的空格字符,那就很难用纯.NET写出胜过string.Replace的代码。大多数字符串方法将回退到手动优化本地C ++代码。而String.Replace本身将用comstring.cpp调用C ++方法:FCIMPL3(Object*, COMString::ReplaceString, StringObject* thisRefUNSAFE, StringObject* oldValueUNSAFE, StringObject* newValueUNSAFE)下面是基准测试套件方法:public static string TrimAllWithStringReplace(string str) { // This method is NOT functionaly equivalent to the others as it will only trim "spaces" // Whitespace comprises lots of other characters return str.Replace(" ", ""); }关于.NET中怎么删除空白字符串就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。...
Java中怎么删除ArrayList的重复元素,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。方法1:使用HashSet删除ArrayList中重复的元素在该方法中,我们使用HashSet来删除重复的元素。如你所知,HashSet不允许有重复的元素。我们使用HashSet的这个属性来删除已建 成的ArrayList中的重复元素。但是,这种方法有一个缺点。那就是,它会删除ArrayList中元素的插入顺序。这意味着,删除重复的元素后,元 素的插入顺序就不对了。先来看下面这个例子。import java.util.ArrayList;import java.util.HashSet;public class MainClass{ public static void main(String[] args) { //Constructing An ArrayListArrayList<String> listWithDuplicateElements = new ArrayList<String>();listWithDuplicateElements.add("JAVA");listWithDuplicateElements.add("J2EE");listWithDuplicateElements.add("JSP");listWithDuplicateElements.add("SERVLETS");listWithDuplicateElements.add("JAVA");listWithDuplicateElements.add("STRUTS");listWithDuplicateElements.add("JSP");//Printing listWithDuplicateElementsSystem.out.print("ArrayList With Duplicate Elements :");System.out.println(listWithDuplicateElements);//Constructing HashSet using listWithDuplicateElementsHashSet<String> set = new HashSet<String>(listWithDuplicateElements);//Constructing listWithoutDuplicateElements using setArrayList<String> listWithoutDuplicateElements = new ArrayList<String>(set);//Printing listWithoutDuplicateElementsSystem.out.print("ArrayList After Removing Duplicate Elements :");System.out.println(listWithoutDuplicateElements); }}输出:ArrayList With Duplicate Elements :[JAVA, J2EE, JSP, SERVLETS, JAVA, STRUTS, JSP]ArrayList After Removing Duplicate Elements :[JAVA, SERVLETS, JSP, J2EE, STRUTS]注意输出结果。你会发现,在删除重复元素之后,元素重新洗牌。不再按照插入顺序排列。如果你想在删除重复的元素之后依然保持元素的插入顺序,那么不 建议使用此方法。还有另一种方法,可以保证在删除重复的元素之后也不改变元素的插入顺序。那就是使用LinkedHashSet。方法2:使用LinkedHashSet删除ArrayList中重复的元素在该方法中,我们使用LinkedHashSet删除ArrayList中重复的元素。正如你知道的,LinkedHashSet不允许重复元素, 同时保持元素的插入顺序。LinkedHashSet的这两个属性可以确保在删除ArrayList中的重复元素之后,依然保持元素的插入顺序。参见下面 的例子。import java.util.ArrayList;import java.util.LinkedHashSet;public class MainClass{ public static void main(String[] args) { //Constructing An ArrayListArrayList<String> listWithDuplicateElements = new ArrayList<String>();listWithDuplicateElements.add("JAVA");listWithDuplicateElements.add("J2EE");listWithDuplicateElements.add("JSP");listWithDuplicateElements.add("SERVLETS");listWithDuplicateElements.add("JAVA");listWithDuplicateElements.add("STRUTS");listWithDuplicateElements.add("JSP");//Printing listWithDuplicateElementsSystem.out.print("ArrayList With Duplicate Elements :");System.out.println(listWithDuplicateElements);//Constructing LinkedHashSet using listWithDuplicateElementsLinkedHashSet<String> set = new LinkedHashSet<String>(listWithDuplicateElements);//Constructing listWithoutDuplicateElements using setArrayList<String> listWithoutDuplicateElements = new ArrayList<String>(set);//Printing listWithoutDuplicateElementsSystem.out.print("ArrayList After Removing Duplicate Elements :");System.out.println(listWithoutDuplicateElements); }}输出:ArrayList With Duplicate Elements :[JAVA, J2EE, JSP, SERVLETS, JAVA, STRUTS, JSP]ArrayList After Removing Duplicate Elements :[JAVA, J2EE, JSP, SERVLETS, STRUTS]注意输出。你可以发现在删除ArrayList中的重复元素后,依然保持了元素的插入顺序。看完上述内容是否对您有帮助呢?如果还想对相关知识有进一步的了解或阅读更多相关文章,请关注辰讯云资讯频道,感谢您对辰讯云的支持。...
Java中怎么删除Excel的行和列,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。Jar文件获取及导入:方法1:通过官方网站下载获取jar包。解压后将lib文件夹下的Spire.Xls.jar文件导入Java程序。(如下图)方法2:通过maven仓库安装导入。具体安装详解参见此网页。【示例1】删除指定的行和列import com.spire.xls.ExcelVersion; import com.spire.xls.Workbook; import com.spire.xls.Worksheet; public class DeleteSpecificRowsAndColumns { public static void main(String[] args) { //创建一个Workbook类的对象,并加载测试文档 Workbook wb = new Workbook(); wb.loadFromFile("D:\\Desktop\\Sample.xlsx"); //获取工作表 Worksheet sheet = wb.getWorksheets().get(0); //通过索引删除指定的行 sheet.deleteRow(4); //通过索引删除指定的列 sheet.deleteColumn(5); //保存文档 wb.saveToFile("output/DeleteSpecificRowsAndColumns.xlsx", ExcelVersion.Version2016); } }删除效果:【示例2】删除空白的行和列import com.spire.xls.ExcelVersion; import com.spire.xls.Workbook; import com.spire.xls.Worksheet; public class DeleteBlankRowsAndColumns { public static void main(String[] args) { //创建一个Workbook类的对象,并加载测试文档 Workbook wb = new Workbook(); wb.loadFromFile("D:\\Desktop\\Sample2.xlsx"); //获取工作表 Worksheet sheet = wb.getWorksheets().get(0); //遍历所有列 for (int i = sheet.getLastRow(); i >= 1; i--) { //删除空白列 if (sheet.getRows()[i-1].isBlank()) { //移除空白列 sheet.deleteRow(i); } } //遍历所有行 for (int j = sheet.getLastColumn(); j >= 1; j--) { //删除空白行 if (sheet.getColumns()[j-1].isBlank()) { //移除空白行 sheet.deleteColumn(j); } } //保存文档 wb.saveToFile("output/DeleteBlankRowsAndColumns.xlsx", ExcelVersion.Version2016); } }删除效果: 【示例3】删除含有关键字的行import com.spire.xls.*; import java.util.ArrayList; import java.util.List; public class DeleteRowsByKeywords { public static void main(String[] args) { //创建一个Workbook类的对象,并加载测试文档 Workbook wb = new Workbook(); wb.loadFromFile("D:\\Desktop\\Sample3.xlsx"); //获取工作表 Worksheet sheet = wb.getWorksheets().get(0); //通过getRowList方法获取含有关键字的列 List<Integer> list = getRowList(sheet,"你好,世界!"); //遍历所有工作表 for (int i = 0; i < list.size(); i++) { //删除特定列 sheet.deleteRow(list.get(i)); } //保存文档 wb.saveToFile("output/DeleteRowsByKeywords.xlsx",ExcelVersion.Version2016); } //创建getRowList方法获取含有关键字的列数 static List<Integer> getRowList(Worksheet sheet, String keyword) { int rowNum; List<Integer> rowList = new ArrayList<>(); for (int i = sheet.getLastRow(); i >= 1; i--) { rowNum = i; for (int j = sheet.getLastColumn(); j >= 1; j--) { if (sheet.get(i, j).getText().contains(keyword)) { rowList.add(rowNum); break; } } } return rowList; } }删除效果:关于Java中怎么删除Excel的行和列问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注辰讯云资讯频道了解更多相关知识。...
Linux中如何使用sed文本处理命令,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。sed对文本的处理很强大,并且sed非常小,参数少,容易掌握,他的操作方式根awk有点像。sed按顺序逐行读取文件。然后,它执行为该行指定的所有操作,并在完成请求的修改之后的内容显示出来,也可以存放到文件中。完成了一行上的所有操作之后,它读取文件的下一行,然后重复该过程直到它完成该文件。在这里要注意一点,源文件(默认地)保持不被修改。sed 默认读取整个文件并对其中的每一行进行修改。说白了就是一行一行的操作。参数sed -h -n, --quiet, --silent 取消自动打印模式空间 -e 脚本, --expression=脚本 添加“脚本”到程序的运行列表 -f 脚本文件, --file=脚本文件 添加“脚本文件”到程序的运行列表 --follow-symlinks 直接修改文件时跟随软链接 -i[扩展名], --in-place[=扩展名] 直接修改文件(如果指定扩展名就备份文件) -l N, --line-length=N 指定“l”命令的换行期望长度 --posix 关闭所有 GNU 扩展 -r, --regexp-extended 在脚本中使用扩展正则表达式 -s, --separate 将输入文件视为各个独立的文件而不是一个长的连续输入 -u, --unbuffered 从输入文件读取最少的数据,更频繁的刷新输出 --help 打印帮助并退出 --version 输出版本信息并退出例1测试文件代码如下:root:x:0:0:root:/root:/bin/bashbin:x:1:1:bin:/bin:/bin/falsedaemon:x:2:2:daemon:/sbin:/bin/falsemail:x:8:12:mail:/var/spool/mail:/bin/falseftp:x:14:11:ftp:/home/ftp:/bin/false&nobody:$:99:99:nobody:/:/bin/falsezhangy:x:1000:100:,,,:/home/zhangy:/bin/bashhttp:x:33:33::/srv/http:/bin/falsedbus:x:81:81:System message bus:/:/bin/falsehal:x:82:82:HAL daemon:/:/bin/falsemysql:x:89:89::/var/lib/mysql:/bin/falseaaa:x:1001:1001::/home/aaa:/bin/bashba:x:1002:1002::/home/zhangy:/bin/bashtest:x:1003:1003::/home/test:/bin/bash@zhangying:*:1004:1004::/home/test:/bin/bashpolicykit:x:102:1005:Po例a,这个例子,把test文件中的root替换成tankzhang,只不过只替换一次及终止在这一行的操作,转到下一行代码如下:[zhangy@BlackGhost mytest]# sed 's/root/tankzhang/' test |grep tanktankzhang:x:0:0:root:/root:/bin/bash例b,这个例子,用tankzhang把文件test中的root全部替换掉,请注意g这个字母,global的缩写代码如下:[zhangy@BlackGhost mytest]# sed 's/root/tankzhang/g' test |grep zhangtankzhang:x:0:0:tankzhang:/tankzhang:/bin/bashzhangy:x:1000:100:,,,:/home/zhangy:/bin/bashba:x:1002:1002::/home/zhangy:/bin/bash@zhangying:*:1004:1004::/home/test:/bin/bash例c,加了-n p后表示只打印那些发生替换的行(部分替换),上面的例子,我并没有加上grep代码如下:[zhangy@BlackGhost mytest]# sed -n 's/root/tankzhang/p' testtankzhang:x:0:0:root:/root:/bin/bash例d,加了-n pg后表示只打印那些发生替换的行(全部替换),上面的例子,我并没有加上grep代码如下:[zhangy@BlackGhost mytest]# sed -n 's/root/tankzhang/pg' testtankzhang:x:0:0:tankzhang:/tankzhang:/bin/bash例e,在第二行,到第八行之间,替换以zhang开头的行,用ying来替换,并显示替换的行代码如下:[zhangy@BlackGhost mytest]# cat test | sed -ne '2,8s/^zhang/ying/gp'yingy:x:1000:100:,,,:/home/zhangy:/bin/bash例f,当有多个命令要执行时,可以用分号来分开,并且分隔符可以自定义,默认是/。上面的例子意思是在第二行,到第八行之间,替换以zhang开头的行,用ying来替换,在5,到10间,用goodbay来替换dbus,并显示替换的行代码如下:[zhangy@BlackGhost mytest]# cat test | sed -n '2,8s/^zhang/ying/gp;5,10s#dbus#goodbay#gp'yingy:x:1000:100:,,,:/home/zhangy:/bin/bashgoodbay:x:81:81:System message bus:/:/bin/false例g,这个例子根上面的那个例子一样,只不过有一点不同,那就是-e来充当了分号的作用,-e也能分割多个命令。代码如下:[zhangy@BlackGhost mytest]# cat test | sed -ne '2,8s/zhang/ying/gp' -ne '5,10s#dbus#goodbay#gp'yingy:x:1000:100:,,,:/home/yingy:/bin/bashgoodbay:x:81:81:System message bus:/:/bin/false例h,正则的用法,在sed里面用括号的话要加上\的,不然会报错的。代码如下:[zhangy@BlackGhost mytest]# sed -ne '2,8s/^\(zhangy\)/\1ing/gp' testzhangying:x:1000:100:,,,:/home/zhangy:/bin/bash[root@masters ~]# sed -ne '2,8s/^\(zhangy\)/&ing/gp' testzhangying:x:1000:100:,,,:/home/zhangy:/bin/bash例i,&的用处是,在找到的字符串后加上&后面的字符串,zhang后都加上了ying代码如下:[zhangy@BlackGhost mytest]# sed -ne '2,15s/zhang/&ying/gp' testzhangyingy:x:1000:100:,,,:/home/zhangyingy:/bin/bashba:x:1002:1002::/home/zhangyingy:/bin/bash@zhangyingying:*:1004:1004::/home/test:/bin/bash例j,这个例子是说,在以zhang开头的行开始,到匹配Po的行结束,在他们之间进行替换代码如下:[zhangy@BlackGhost mytest]# sed -ne '/^zhang/,/Po/s/zhang/ying/gp' testyingy:x:1000:100:,,,:/home/yingy:/bin/bashba:x:1002:1002::/home/yingy:/bin/bash@yingying:*:1004:1004::/home/test:/bin/bash例k,n;这里的n是next的缩写,找到root的行后,将其下一行的中的bin换成tank代码如下:[zhangy@BlackGhost mytest]$ sed '/root/{n;s/bin/tank/}' testroot:x:0:0:root:/root:/bin/bashtank:x:1:1:bin:/bin:/bin/false例m,y的作用是将匹配的字符换成大写,不过替换字符和被替换字符长度要一样代码如下:[zhangy@BlackGhost mytest]$ sed -e '1,2y/root/ROOT/' testROOT:x:0:0:ROOT:/ROOT:/bin/bashbin:x:1:1:bin:/bin:/bin/false例n,h的作用是将找到的行,放到一个缓存区,G的作用是将缓存区中的内容放到最后一行代码如下:[zhangy@BlackGhost mytest]$ sed -e '/root/h' -e '$G' test.............................................................ba:x:1002:1002::/home/zhangy:/bin/bashtest:x:1003:1003::/home/test:/bin/bash@zhangying:*:1004:1004::/home/test:/bin/bashroot:x:0:0:root:/root:/bin/bash例o,行替换,用匹配root的行,来替换匹配zhangy的行代码如下:[zhangy@BlackGhost mytest]$ sed -e '/root/h' -e '/zhangy/g' testroot:x:0:0:root:/root:/bin/bashbin:x:1:1:bin:/bin:/bin/falsedaemon:x:2:2:daemon:/sbin:/bin/falsemail:x:8:12:mail:/var/spool/mail:/bin/falseftp:x:14:11:ftp:/home/ftp:/bin/false&nobody:$:99:99:nobody:/:/bin/falseroot:x:0:0:root:/root:/bin/bashhttp:x:33:33::/srv/http:/bin/falsedbus:x:81:81:System message bus:/:/bin/falsehal:x:82:82:HAL daemon:/:/bin/falsemysql:x:89:89::/var/lib/mysql:/bin/falseaaa:x:1001:1001::/home/aaa:/bin/bashroot:x:0:0:root:/root:/bin/bashtest:x:1003:1003::/home/test:/bin/bashroot:x:0:0:root:/root:/bin/bash例p,这个例子是说,在以zhang开头的行开始,到匹配Po的行结束,在他们之间进行替换代码如下:[zhangy@BlackGhost mytest]# sed -ne '/^zhang/,/Po/s/zhang/ying/gp' testyingy:x:1000:100:,,,:/home/yingy:/bin/bashba:x:1002:1002::/home/yingy:/bin/bash@yingying:*:1004:1004::/home/test:/bin/bash例q,3q的意思是到第三行的时候,退出代码如下:[zhangy@BlackGhost mytest]$ sed -e 's/bin/tank/g;3q' testroot:x:0:0:root:/root:/tank/bashtank:x:1:1:tank:/tank:/tank/falsedaemon:x:2:2:daemon:/stank:/tank/false例r,特殊匹配匹配数字别忘了中括号外面还有一个中括号。[:alnum:] 字母数字 [a-z A-Z 0-9][:alpha:] 字母 [a-z A-Z][:blank:] 空格或制表键[:cntrl:] 任何控制字符[:digit:] 数字 [0-9][:graph:] 任何可视字符(无空格)[:lower:] 小写 [a-z][:print:] 非控制字符[:punct:] 标点字符[:space:] 空格[:upper:] 大写 [A-Z][:xdigit:] 十六进制数字 [0-9 a-f A-F]代码如下:[zhangy@BlackGhost mytest]# sed -ne '2,15s/zhangy.*[[:digit:]]/=======/gp' test=======:,,,:/home/zhangy:/bin/bash@=======::/home/test:/bin/bash例2例a,删除1,14行代码如下:[zhangy@BlackGhost test]$ sed -e '1,14d' test@zhangying:*:1004:1004::/home/test:/bin/bashpolicykit:x:102:1005:Po例b,删除4以后的行,包括第4行,把$当成最大行数就行了。代码如下:[zhangy@BlackGhost mytest]$ sed -e '4,$d' testroot:x:0:0:root:/root:/bin/bashbin:x:1:1:bin:/bin:/bin/falsedaemon:x:2:2:daemon:/sbin:/bin/false例c,删除包括false的行,或者包括bash的行,别忘了加\代码如下:[zhangy@BlackGhost mytest]$ sed -e '/\(false\|bash\)$/d' testpolicykit:x:102:1005:Po例d,删除从匹配root的行,到匹配以test开头的行,中间的行代码如下:[zhangy@BlackGhost mytest]$ sed -e '/root/,/^test/d' test@zhangying:*:1004:1004::/home/test:/bin/bashpolicykit:x:102:1005:Po例3例a,读取test2的内容,并将其写入到匹配行的下面代码如下:[zhangy@BlackGhost mytest]$ sed -e '/^root/r test2' testroot:x:0:0:root:/root:/bin/bash=============-------------+++++++++++++bin:x:1:1:bin:/bin:/bin/falsedaemon:x:2:2:daemon:/sbin:/bin/false例b,将匹配数字的行,写入test2中代码如下:[zhangy@BlackGhost mytest]$ sed '/[[:digit:]]/w test2' test例c,将要插入的东西,插入匹配行的下面代码如下:[zhangy@BlackGhost mytest]$ sed '/root/a\\ ===aaaa====' testroot:x:0:0:root:/root:/bin/bash===aaaa====bin:x:1:1:bin:/bin:/bin/false例d,正好根a相反,将要插入的东西,插入到匹配行的上面代码如下:[zhangy@BlackGhost mytest]$ sed '/^daemon/i\\=================' testroot:x:0:0:root:/root:/bin/bashbin:x:1:1:bin:/bin:/bin/false=================daemon:x:2:2:daemon:/sbin:/bin/falsemail:x:8:12:mail:/var/spool/mail:/bin/false例4#取得一个文件(或目录)路径的父目录,s@@@为替换格式,\(/.*/\)是指一个"/"后面跟了任意字符又跟了一个"/",其中\(\)是用来把匹配内容作为一个整体后向引用,[^/]\{1,\}是指一个非"/"字符出现了一次,两次,或多次;/\?是指"/"出现了0次或1次,\1是后向引用前面匹配的内容代码如下:[root@practice ~]# echo "/usr/local/bin/" |sed 's@\(/.*/\)[^/]\{1,\}/\?@\1@'/usr/local/#使用扩展正则表达式后,亦可如此:[root@practice ~]# echo "/etc/rc.d/rc.sysinit" | sed -r 's@(/.*/)[^/]+/?@\1@'/etc/rc.d/看完上述内容,你们掌握Linux中如何使用sed文本处理命令的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注辰讯云资讯频道,感谢各位的阅读!...
本篇文章为大家展示了bootstrap中怎么实现自适应,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。Boostrap自适应功能的基础就是“栅栏"模式,它是将浏览器以行列形式去划分:一共12列,行数自定义,根据你所要显示的元素,确定每个元素显示的大小即需要的列数,如果超过范围,就会自动转行。每列的大小是Boostrap根据当前浏览器的大小自动平均分配。Boostrap的自适应功能其实理解栅栏模式之后,自适应功能就简单很多了,根据浏览器的大小,Boostrap有四种栅栏类名提供使用,用法与Css样式表类名选择器样式调用是一样的:xs:col-xs-1 ~ col-xs-12,多列始终在一行内。sm:col-sm-1 ~ col-sm-12,多列在浏览器像素宽度大于等于768px时才在一行内。md:col-md-1 ~ col-md-12,多列在浏览器像素宽度大于等于992px时才在一行内。lg:col-lg-1 ~ col-lg-12,多列在浏览器像素宽度大于等于1200px时才在一行内。我贴一段伪代码:<div class="row"> <div class="col-sm-4 col-md-1"></div> <div class="col-sm-4 col-md-1"></div> <div class="col-sm-4 col-md-10"></div></div>这段的意思是当浏览器的宽度在768——992之间时,采取4:4:4的模式呈现,当浏览器宽度大于992时,就按1:1:10的模式呈现。当然你也可以四种都是用,细分的在仔细点,不管怎么样,Boostrap会根据浏览器的宽度自动分配列宽度,从而匹配你所想要的呈现模式。至于它怎么做到的,你不用关心,你只需要考虑你在手机上的网页呈现样式,或者PC上的呈现样式的设计就行了,接下来,就交给Boostrap吧。上述内容就是bootstrap中怎么实现自适应,你们学到知识或技能了吗?如果还想学到更多技能或者丰富自己的知识储备,欢迎关注辰讯云资讯频道。...
bootstrap中怎么实现圆角,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。Bootstrap提供了四种用于<img>类的样式,分别是:.img-rounded:圆角 (IE8 不支持),添加 border-radius:6px 来获得图片圆角;.img-circle:圆形 (IE8 不支持),添加 border-radius:50% 来让整个图片变成圆形。.img-thumbnail:缩略图功能,添加一些内边距(padding)和一个灰色的边框。.img-responsive:图片响应式 (将很好地扩展到父元素)。使用:将类样式直接添加到class中即可:<img class="img-circle" src="img.jpg" alt="头像"/>效果如下:从图中可以看到使用各种样式得到的效果,处理起图片来非常的简单方便。看完上述内容,你们掌握bootstrap中怎么实现圆角的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注辰讯云资讯频道,感谢各位的阅读!...
Http类中怎么实现依赖注入,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。从入口文件出发 当访问一个 ThinkPHP 搭建的站点,框架最先是从入口文件开始的,然后才是应用初始化、路由解析、控制器调用和响应输出等操作。 入口文件主要代码如下: // 引入自动加载器,实现类的自动加载功能(PSR4标准) // 对比Laravel、Yii2、Thinkphp的自动加载实现,它们基本就都一样 // 具体实现可参考我之前写的Laravel的自动加载实现: // @link: https://learnku.com/articles/20816 require __DIR__ . '/../vendor/autoload.php'; // 这一句和分为两部分分析,App的实例化和调用「http」,具体见下文分析 $http = (new App())->http; $response = $http->run(); $response->send(); $http->end($response); App 实例化 执行 new App() 实例化时,首先会调用它的构造函数。 public function __construct(string $rootPath = '') { // thinkPath目录:如,D:\dev\tp6\vendor\topthink\framework\src\ $this->thinkPath = dirname(__DIR__) . DIRECTORY_SEPARATOR; // 项目根目录,如:D:\dev\tp6\ $this->rootPath = $rootPath ? rtrim($rootPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : $this->getDefaultRootPath(); $this->appPath = $this->rootPath . 'app' . DIRECTORY_SEPARATOR; $this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR; // 如果存在「绑定类库到容器」文件 if (is_file($this->appPath . 'provider.php')) { //将文件里的所有映射合并到容器的「$bind」成员变量中 $this->bind(include $this->appPath . 'provider.php'); } //将当前容器实例保存到成员变量「$instance」中,也就是容器自己保存自己的一个实例 static::setInstance($this); // 保存绑定的实例到「$instances」数组中,见对应分析 $this->instance('app', $this); $this->instance('think\Container', $this); } 构造函数实现了项目各种基础路径的初始化,并读取了 provider.php 文件,将其类的绑定并入 $bind 成员变量,provider.php 文件默认内容如下: return [ 'think\Request' => Request::class, 'think\exception\Handle' => ExceptionHandle::class, ]; $bind 的值是一组类的标识到类的映射。从这个实现也可以看出,我们不仅可以在 provider.php 文件中添加标识到类的映射,而且可以覆盖其原有的映射,也就是将某些核心类替换成自己定义的类。 static::setInstance($this) 实现的作用。 think\App 类的 $instance 成员变量指向 think\App 类的一个实例,也就是类自己保存自己的一个实例。 instance() 方法的实现: public function instance(string $abstract, $instance) { //检查「$bind」中是否保存了名称到实际类的映射,如 'app'=> 'think\App' //也就是说,只要绑定了这种对应关系,通过传入名称,就可以找到实际的类 if (isset($this->bind[$abstract])) { //$abstract = 'app', $bind = "think\App" $bind = $this->bind[$abstract]; //如果「$bind」是字符串,重走上面的流程 if (is_string($bind)) { return $this->instance($bind, $instance); } } //保存绑定的实例到「$instances」数组中 //比如,$this->instances["think\App"] = $instance; $this->instances[$abstract] = $instance; return $this; } Http 类的实例化以及依赖注入原理 这里,$http = (new App())->http,前半部分好理解,后半部分乍一看有点让人摸不着头脑,App 类并不存在 http 成员变量,这里何以大胆调用了一个不存在的东东呢? 原来,App 类继承自 Container 类,而 Container 类实现了__get() 魔术方法,在 PHP 中,当访问到的变量不存在,就会触发__get() 魔术方法。该方法的实现如下: public function __get($name) { return $this->get($name); } 实际上是调用 get() 方法: public function get($abstract) { //先检查是否有绑定实际的类或者是否实例已存在 //比如,$abstract = 'http' if ($this->has($abstract)) { return $this->make($abstract); } // 找不到类则抛出类找不到的错误 throw new ClassNotFoundException('class not exists: ' . $abstract, $abstract); } 然而,实际上,主要是 make() 方法: public function make(string $abstract, array $vars = [], bool $newInstance = false) { //如果已经存在实例,且不强制创建新的实例,直接返回已存在的实例 if (isset($this->instances[$abstract]) && !$newInstance) { return $this->instances[$abstract]; } //如果有绑定,比如 'http'=> 'think\Http',则 $concrete = 'think\Http' if (isset($this->bind[$abstract])) { $concrete = $this->bind[$abstract]; if ($concrete instanceof Closure) { $object = $this->invokeFunction($concrete, $vars); } else { //重走一遍make函数,比如上面http的例子,则会调到后面「invokeClass()」处 return $this->make($concrete, $vars, $newInstance); } } else { //实例化需要的类,比如'think\Http' $object = $this->invokeClass($abstract, $vars); } if (!$newInstance) { $this->instances[$abstract] = $object; } return $object; } 然而,然而,make() 方法主要靠 invokeClass() 来实现类的实例化。该方法具体分析: public function invokeClass(string $class, array $vars = []) { try { //通过反射实例化类 $reflect = new ReflectionClass($class); //检查是否有「__make」方法 if ($reflect->hasMethod('__make')) { //返回的$method包含'__make'的各种信息,如公有/私有 $method = new ReflectionMethod($class, '__make'); //检查是否是公有方法且是静态方法 if ($method->isPublic() && $method->isStatic()) { //绑定参数 $args = $this->bindParams($method, $vars); //调用该方法(__make),因为是静态的,所以第一个参数是null //因此,可得知,一个类中,如果有__make方法,在类实例化之前会首先被调用 return $method->invokeArgs(null, $args); } } //获取类的构造函数 $constructor = $reflect->getConstructor(); //有构造函数则绑定其参数 $args = $constructor ? $this->bindParams($constructor, $vars) : []; //根据传入的参数,通过反射,实例化类 $object = $reflect->newInstanceArgs($args); // 执行容器回调 $this->invokeAfter($class, $object); return $object; } catch (ReflectionException $e) { throw new ClassNotFoundException('class not exists: ' . $class, $class, $e); } } 以上代码可看出,在一个类中,添加__make() 方法,在类实例化时,会最先被调用。以上最值得一提的是 bindParams() 方法: protected function bindParams($reflect, array $vars = []): array { //如果参数个数为0,直接返回 if ($reflect->getNumberOfParameters() == 0) { return []; } // 判断数组类型 数字数组时按顺序绑定参数 reset($vars); $type = key($vars) === 0 ? 1 : 0; //通过反射获取函数的参数,比如,获取Http类构造函数的参数,为「App $app」 $params = $reflect->getParameters(); $args = []; foreach ($params as $param) { $name = $param->getName(); $lowerName = self::parseName($name); $class = $param->getClass(); //如果参数是一个类 if ($class) { //将类型提示的参数实例化 $args[] = $this->getObjectParam($class->getName(), $vars); } elseif (1 == $type && !empty($vars)) { $args[] = array_shift($vars); } elseif (0 == $type && isset($vars[$name])) { $args[] = $vars[$name]; } elseif (0 == $type && isset($vars[$lowerName])) { $args[] = $vars[$lowerName]; } elseif ($param->isDefaultValueAvailable()) { $args[] = $param->getDefaultValue(); } else { throw new InvalidArgumentException('method param miss:' . $name); } } return $args; } 而这之中,又最值得一提的是 getObjectParam() 方法: protected function getObjectParam(string $className, array &$vars) { $array = $vars; $value = array_shift($array); if ($value instanceof $className) { $result = $value; array_shift($vars); } else { //实例化传入的类 $result = $this->make($className); } return $result; } getObjectParam() 方法再一次光荣地调用 make() 方法,实例化一个类,而这个类,正是从 Http 的构造函数提取的参数,而这个参数又恰恰是一个类的实例 ——App 类的实例。到这里,程序不仅通过 PHP 的反射类实例化了 Http 类,而且实例化了 Http 类的依赖 App 类。假如 App 类又依赖 C 类,C 类又依赖 D类…… 不管多少层,整个依赖链条依赖的类都可以实现实例化。 总的来说,整个过程大概是这样的:需要实例化 Http 类 ==> 提取构造函数发现其依赖 App 类 ==> 开始实例化 App 类(如果发现还有依赖,则一直提取下去,直到天荒地老)==> 将实例化好的依赖(App 类的实例)传入 Http 类来实例化 Http 类。 这个过程,起个装逼的名字就叫做「依赖注入」,起个摸不着头脑的名字,就叫做「控制反转」。 这个过程,如果退回远古时代,要实例化 Http 类,大概是这样实现的(假如有很多层依赖): $e = new E(); $d = new D($e); $c = new D($d); $app = new App($c); $http = new Http($app);看完上述内容,你们掌握Http类中怎么实现依赖注入的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注辰讯云资讯频道,感谢各位的阅读!...